diff --git a/cypress.json b/cypress.json new file mode 100644 index 00000000..f16085b5 --- /dev/null +++ b/cypress.json @@ -0,0 +1 @@ +{"baseUrl": "http://localhost:3000"} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 00000000..da18d935 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/cypress/fixtures/profile.json b/cypress/fixtures/profile.json new file mode 100644 index 00000000..b6c355ca --- /dev/null +++ b/cypress/fixtures/profile.json @@ -0,0 +1,5 @@ +{ + "id": 8739, + "name": "Jane", + "email": "jane@example.com" +} \ No newline at end of file diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json new file mode 100644 index 00000000..79b699aa --- /dev/null +++ b/cypress/fixtures/users.json @@ -0,0 +1,232 @@ +[ + { + "id": 1, + "name": "Leanne Graham", + "username": "Bret", + "email": "Sincere@april.biz", + "address": { + "street": "Kulas Light", + "suite": "Apt. 556", + "city": "Gwenborough", + "zipcode": "92998-3874", + "geo": { + "lat": "-37.3159", + "lng": "81.1496" + } + }, + "phone": "1-770-736-8031 x56442", + "website": "hildegard.org", + "company": { + "name": "Romaguera-Crona", + "catchPhrase": "Multi-layered client-server neural-net", + "bs": "harness real-time e-markets" + } + }, + { + "id": 2, + "name": "Ervin Howell", + "username": "Antonette", + "email": "Shanna@melissa.tv", + "address": { + "street": "Victor Plains", + "suite": "Suite 879", + "city": "Wisokyburgh", + "zipcode": "90566-7771", + "geo": { + "lat": "-43.9509", + "lng": "-34.4618" + } + }, + "phone": "010-692-6593 x09125", + "website": "anastasia.net", + "company": { + "name": "Deckow-Crist", + "catchPhrase": "Proactive didactic contingency", + "bs": "synergize scalable supply-chains" + } + }, + { + "id": 3, + "name": "Clementine Bauch", + "username": "Samantha", + "email": "Nathan@yesenia.net", + "address": { + "street": "Douglas Extension", + "suite": "Suite 847", + "city": "McKenziehaven", + "zipcode": "59590-4157", + "geo": { + "lat": "-68.6102", + "lng": "-47.0653" + } + }, + "phone": "1-463-123-4447", + "website": "ramiro.info", + "company": { + "name": "Romaguera-Jacobson", + "catchPhrase": "Face to face bifurcated interface", + "bs": "e-enable strategic applications" + } + }, + { + "id": 4, + "name": "Patricia Lebsack", + "username": "Karianne", + "email": "Julianne.OConner@kory.org", + "address": { + "street": "Hoeger Mall", + "suite": "Apt. 692", + "city": "South Elvis", + "zipcode": "53919-4257", + "geo": { + "lat": "29.4572", + "lng": "-164.2990" + } + }, + "phone": "493-170-9623 x156", + "website": "kale.biz", + "company": { + "name": "Robel-Corkery", + "catchPhrase": "Multi-tiered zero tolerance productivity", + "bs": "transition cutting-edge web services" + } + }, + { + "id": 5, + "name": "Chelsey Dietrich", + "username": "Kamren", + "email": "Lucio_Hettinger@annie.ca", + "address": { + "street": "Skiles Walks", + "suite": "Suite 351", + "city": "Roscoeview", + "zipcode": "33263", + "geo": { + "lat": "-31.8129", + "lng": "62.5342" + } + }, + "phone": "(254)954-1289", + "website": "demarco.info", + "company": { + "name": "Keebler LLC", + "catchPhrase": "User-centric fault-tolerant solution", + "bs": "revolutionize end-to-end systems" + } + }, + { + "id": 6, + "name": "Mrs. Dennis Schulist", + "username": "Leopoldo_Corkery", + "email": "Karley_Dach@jasper.info", + "address": { + "street": "Norberto Crossing", + "suite": "Apt. 950", + "city": "South Christy", + "zipcode": "23505-1337", + "geo": { + "lat": "-71.4197", + "lng": "71.7478" + } + }, + "phone": "1-477-935-8478 x6430", + "website": "ola.org", + "company": { + "name": "Considine-Lockman", + "catchPhrase": "Synchronised bottom-line interface", + "bs": "e-enable innovative applications" + } + }, + { + "id": 7, + "name": "Kurtis Weissnat", + "username": "Elwyn.Skiles", + "email": "Telly.Hoeger@billy.biz", + "address": { + "street": "Rex Trail", + "suite": "Suite 280", + "city": "Howemouth", + "zipcode": "58804-1099", + "geo": { + "lat": "24.8918", + "lng": "21.8984" + } + }, + "phone": "210.067.6132", + "website": "elvis.io", + "company": { + "name": "Johns Group", + "catchPhrase": "Configurable multimedia task-force", + "bs": "generate enterprise e-tailers" + } + }, + { + "id": 8, + "name": "Nicholas Runolfsdottir V", + "username": "Maxime_Nienow", + "email": "Sherwood@rosamond.me", + "address": { + "street": "Ellsworth Summit", + "suite": "Suite 729", + "city": "Aliyaview", + "zipcode": "45169", + "geo": { + "lat": "-14.3990", + "lng": "-120.7677" + } + }, + "phone": "586.493.6943 x140", + "website": "jacynthe.com", + "company": { + "name": "Abernathy Group", + "catchPhrase": "Implemented secondary concept", + "bs": "e-enable extensible e-tailers" + } + }, + { + "id": 9, + "name": "Glenna Reichert", + "username": "Delphine", + "email": "Chaim_McDermott@dana.io", + "address": { + "street": "Dayna Park", + "suite": "Suite 449", + "city": "Bartholomebury", + "zipcode": "76495-3109", + "geo": { + "lat": "24.6463", + "lng": "-168.8889" + } + }, + "phone": "(775)976-6794 x41206", + "website": "conrad.com", + "company": { + "name": "Yost and Sons", + "catchPhrase": "Switchable contextually-based project", + "bs": "aggregate real-time technologies" + } + }, + { + "id": 10, + "name": "Clementina DuBuque", + "username": "Moriah.Stanton", + "email": "Rey.Padberg@karina.biz", + "address": { + "street": "Kattie Turnpike", + "suite": "Suite 198", + "city": "Lebsackbury", + "zipcode": "31428-2261", + "geo": { + "lat": "-38.2386", + "lng": "57.2232" + } + }, + "phone": "024-648-3804", + "website": "ambrose.net", + "company": { + "name": "Hoeger LLC", + "catchPhrase": "Centralized empowering task-force", + "bs": "target end-to-end models" + } + } +] \ No newline at end of file diff --git a/cypress/integration/example_spec.js b/cypress/integration/example_spec.js new file mode 100644 index 00000000..a5dac3f5 --- /dev/null +++ b/cypress/integration/example_spec.js @@ -0,0 +1,1683 @@ +/* eslint-disable */ + +// +// **** Kitchen Sink Tests **** +// +// This app was developed to demonstrate +// how to write tests in Cypress utilizing +// all of the available commands +// +// Feel free to modify this spec in your +// own application as a jumping off point + +// Please read our "Introduction to Cypress" +// https://on.cypress.io/introduction-to-cypress + +describe('Kitchen Sink', function() { + it('.should() - assert that is correct', function() { + // https://on.cypress.io/visit + cy.visit('https://example.cypress.io') + + // Here we've made our first assertion using a '.should()' command. + // An assertion is comprised of a chainer, subject, and optional value. + + // https://on.cypress.io/should + // https://on.cypress.io/and + + // https://on.cypress.io/title + cy.title().should('include', 'Kitchen Sink') + // ↲ ↲ ↲ + // subject chainer value + }) + + context('Querying', function() { + beforeEach(function() { + // Visiting our app before each test removes any state build up from + // previous tests. Visiting acts as if we closed a tab and opened a fresh one + cy.visit('https://example.cypress.io/commands/querying') + }) + + // Let's query for some DOM elements and make assertions + // The most commonly used query is 'cy.get()', you can + // think of this like the '$' in jQuery + + it('cy.get() - query DOM elements', function() { + // https://on.cypress.io/get + + // Get DOM elements by id + cy.get('#query-btn').should('contain', 'Button') + + // Get DOM elements by class + cy.get('.query-btn').should('contain', 'Button') + + cy.get('#querying .well>button:first').should('contain', 'Button') + // ↲ + // Use CSS selectors just like jQuery + }) + + it('cy.contains() - query DOM elements with matching content', function() { + // 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') + + // `.contains()` will favor input[type='submit'], + // button, a, and label over deeper elements inside them + // this will not yield the <span> inside the button, + // but the <button> itself + cy + .get('.query-button') + .contains('Save Form') + .should('have.class', 'btn') + }) + + it('.within() - query DOM elements within a specific element', function() { + // https://on.cypress.io/within + cy.get('.query-form').within(function() { + 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', function() { + // https://on.cypress.io/root + // By default, root is the document + cy.root().should('match', 'html') + + cy.get('.query-ul').within(function() { + // In this within, the root is now the ul DOM element + cy.root().should('have.class', 'query-ul') + }) + }) + }) + + context('Traversal', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/traversal') + }) + + // Let's query for some DOM elements and make assertions + + it('.children() - get child DOM elements', function() { + // https://on.cypress.io/children + cy + .get('.traversal-breadcrumb') + .children('.active') + .should('contain', 'Data') + }) + + it('.closest() - get closest ancestor DOM element', function() { + // 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', function() { + // https://on.cypress.io/eq + cy + .get('.traversal-list>li') + .eq(1) + .should('contain', 'siamese') + }) + + it('.filter() - get DOM elements that match the selector', function() { + // https://on.cypress.io/filter + cy + .get('.traversal-nav>li') + .filter('.active') + .should('contain', 'About') + }) + + it('.find() - get descendant DOM elements of the selector', function() { + // https://on.cypress.io/find + cy + .get('.traversal-pagination') + .find('li') + .find('a') + .should('have.length', 7) + }) + + it('.first() - get first DOM element', function() { + // https://on.cypress.io/first + cy + .get('.traversal-table td') + .first() + .should('contain', '1') + }) + + it('.last() - get last DOM element', function() { + // https://on.cypress.io/last + cy + .get('.traversal-buttons .btn') + .last() + .should('contain', 'Submit') + }) + + it('.next() - get next sibling DOM element', function() { + // https://on.cypress.io/next + cy + .get('.traversal-ul') + .contains('apples') + .next() + .should('contain', 'oranges') + }) + + it('.nextAll() - get all next sibling DOM elements', function() { + // 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', function() { + // https://on.cypress.io/nextuntil + cy + .get('#veggies') + .nextUntil('#nuts') + .should('have.length', 3) + }) + + it('.not() - remove DOM elements from set of DOM elements', function() { + // 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', function() { + // https://on.cypress.io/parent + cy + .get('.traversal-mark') + .parent() + .should('contain', 'Morbi leo risus') + }) + + it('.parents() - get parent DOM elements from DOM elements', function() { + // https://on.cypress.io/parents + cy + .get('.traversal-cite') + .parents() + .should('match', 'blockquote') + }) + + it('.parentsUntil() - get parent DOM elements from DOM elements until el', function() { + // 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', function() { + // https://on.cypress.io/prev + cy + .get('.birds') + .find('.active') + .prev() + .should('contain', 'Lorikeets') + }) + + it('.prevAll() - get all previous sibling DOM elements', function() { + // 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', function() { + // https://on.cypress.io/prevUntil + cy + .get('.foods-list') + .find('#nuts') + .prevUntil('#veggies') + }) + + it('.siblings() - get all sibling DOM elements', function() { + // https://on.cypress.io/siblings + cy + .get('.traversal-pills .active') + .siblings() + .should('have.length', 2) + }) + }) + + context('Actions', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/actions') + }) + + // Let's perform some actions on DOM elements + // https://on.cypress.io/interacting-with-elements + + it('.type() - type into a DOM element', function() { + // 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', function() { + // 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', function() { + // https://on.cypress.io/blur + cy + .get('.action-blur') + .type("I'm about to blur") + .blur() + .should('have.class', 'error') + .prev() + .should('have.attr', 'style', 'color: red;') + }) + + it('.clear() - clears an input or textarea element', function() { + // https://on.cypress.io/clear + cy + .get('.action-clear') + .type('We are going to clear this text') + .should('have.value', 'We are going to clear this text') + .clear() + .should('have.value', '') + }) + + it('.submit() - submit a form', function() { + // 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', function() { + // https://on.cypress.io/click + cy.get('.action-btn').click() + + // You can clock 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 + // like whether the element is visible, clickable or disabled + // this button below is covered by another element. + cy.get('.action-opacity>.btn').click({force: true}) + }) + + it('.dblclick() - double click on a DOM element', function() { + // Our app has a listener on 'dblclick' event in our 'scripts.js' + // that hides the div and shows an input on double click + + // https://on.cypress.io/dblclick + cy + .get('.action-div') + .dblclick() + .should('not.be.visible') + cy.get('.action-input-hidden').should('be.visible') + }) + + it('cy.check() - check a checkbox or radio element', function() { + // By default, .check() will check all + // matching checkbox or radio elements in succession, one after another + + // https://on.cypress.io/check + 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 + // that checks only checkboxes or radios + // with matching values + cy + .get('.action-radios [type="radio"]') + .check('radio1') + .should('be.checked') + + // .check() accepts an array of values + // that checks only checkboxes or radios + // with matching values + cy + .get('.action-multiple-checkboxes [type="checkbox"]') + .check(['checkbox1', 'checkbox2']) + .should('be.checked') + + // Ignore error checking prior to checking + // like whether the element is visible, clickable or disabled + // this checkbox below is disabled. + 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', function() { + // By default, .uncheck() will uncheck all matching + // checkbox elements in succession, one after another + + // https://on.cypress.io/uncheck + cy + .get('.action-check [type="checkbox"]') + .not('[disabled]') + .uncheck() + .should('not.be.checked') + + // .uncheck() accepts a value argument + // that unchecks only checkboxes + // with matching values + cy + .get('.action-check [type="checkbox"]') + .check('checkbox1') + .uncheck('checkbox1') + .should('not.be.checked') + + // .uncheck() accepts an array of values + // that unchecks only checkboxes or radios + // with matching values + cy + .get('.action-check [type="checkbox"]') + .check(['checkbox1', 'checkbox3']) + .uncheck(['checkbox1', 'checkbox3']) + .should('not.be.checked') + + // Ignore error checking prior to unchecking + // like whether the element is visible, clickable or disabled + // this checkbox below is disabled. + cy + .get('.action-check [disabled]') + .uncheck({force: true}) + .should('not.be.checked') + }) + + it('.select() - select an option in a <select> element', function() { + // https://on.cypress.io/select + + // Select option with matching text content + cy.get('.action-select').select('apples') + + // Select option with matching value + cy.get('.action-select').select('fr-bananas') + + // Select options with matching text content + cy.get('.action-select-multiple').select(['apples', 'oranges', 'bananas']) + + // Select options with matching values + cy + .get('.action-select-multiple') + .select(['fr-apples', 'fr-oranges', 'fr-bananas']) + }) + + it('.scrollIntoView() - scroll an element into view', function() { + // 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('cy.scrollTo() - scroll the window or element to a position', function() { + // 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}) + }) + + it('.trigger() - trigger an event on a DOM element', function() { + // To interact with a range input (slider), we need to set its value and + // then trigger the appropriate event to signal it has changed + + // Here, we invoke jQuery's val() method to set the value + // and trigger the 'change' event + + // Note that some implementations may rely on the 'input' event, + // which is fired as a user moves the slider, but is not supported + // by some browsers + + // https://on.cypress.io/trigger + cy + .get('.trigger-input-range') + .invoke('val', 25) + .trigger('change') + .get('input[type=range]') + .siblings('p') + .should('have.text', '25') + + // See our example recipes for more examples of using trigger + // https://on.cypress.io/examples + }) + }) + + context('Window', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/window') + }) + + it('cy.window() - get the global window object', function() { + // https://on.cypress.io/window + cy.window().should('have.property', 'top') + }) + + it('cy.document() - get the document object', function() { + // https://on.cypress.io/document + cy + .document() + .should('have.property', 'charset') + .and('eq', 'UTF-8') + }) + + it('cy.title() - get the title', function() { + // https://on.cypress.io/title + cy.title().should('include', 'Kitchen Sink') + }) + }) + + context('Viewport', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/viewport') + }) + + it('cy.viewport() - set the viewport size and dimension', function() { + cy.get('#navbar').should('be.visible') + + // https://on.cypress.io/viewport + 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's 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 is set in cypress.json) + }) + }) + + context('Location', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/location') + }) + + // We look at the url to make assertions + // about the page's state + + it('cy.hash() - get the current URL hash', function() { + // https://on.cypress.io/hash + cy.hash().should('be.empty') + }) + + it('cy.location() - get window.location', function() { + // https://on.cypress.io/location + cy.location().should(function(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', function() { + // https://on.cypress.io/url + cy.url().should('eq', 'https://example.cypress.io/commands/location') + }) + }) + + context('Navigation', function() { + beforeEach(function() { + 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", function() { + cy.location('pathname').should('include', 'navigation') + + // https://on.cypress.io/go + cy.go('back') + cy.location('pathname').should('not.include', 'navigation') + + cy.go('forward') + cy.location('pathname').should('include', 'navigation') + + // equivalent to clicking back + cy.go(-1) + cy.location('pathname').should('not.include', 'navigation') + + // equivalent to clicking forward + cy.go(1) + cy.location('pathname').should('include', 'navigation') + }) + + it('cy.reload() - reload the page', function() { + // https://on.cypress.io/reload + cy.reload() + + // reload the page without using the cache + cy.reload(true) + }) + + it('cy.visit() - visit a remote url', function() { + // Visit any sub-domain of your current domain + // https://on.cypress.io/visit + + // Pass options to the visit + cy.visit('https://example.cypress.io/commands/navigation', { + timeout: 50000, // increase total time for the visit to resolve + onBeforeLoad: function(contentWindow) { + // contentWindow is the remote page's window object + }, + onLoad: function(contentWindow) { + // contentWindow is the remote page's window object + } + }) + }) + }) + + context('Assertions', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/assertions') + }) + + describe('Implicit Assertions', function() { + it('.should() - make an assertion about the current subject', function() { + // https://on.cypress.io/should + cy + .get('.assertion-table') + .find('tbody tr:last') + .should('have.class', 'success') + }) + + it('.and() - chain multiple assertions together', function() { + // https://on.cypress.io/and + cy + .get('.assertions-link') + .should('have.class', 'active') + .and('have.attr', 'href') + .and('include', 'cypress.io') + }) + }) + + describe('Explicit Assertions', function() { + it('expect - make an assertion about a specified subject', function() { + // We can use Chai's BDD style assertions + expect(true).to.be.true + + // Pass a function to should that can have any number + // of explicit assertions within it. + cy + .get('.assertions-p') + .find('p') + .should(function($p) { + // return an array of texts from all of the p's + var texts = $p.map(function(i, el) { + // https://on.cypress.io/$ + return Cypress.$(el).text() + }) + + // jquery map returns jquery object + // and .get() convert this to simple array + texts = texts.get() + + // array should have length of 3 + expect(texts).to.have.length(3) + + // set this specific subject + expect(texts).to.deep.eq([ + 'Some text from first p', + 'More text from second p', + 'And even more text from third p' + ]) + }) + }) + }) + }) + + context('Misc', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/misc') + }) + + it('.end() - end the command chain', function() { + // cy.end is useful when you want to end a chain of commands + // and force Cypress to re-query from the root element + + // https://on.cypress.io/end + cy.get('.misc-table').within(function() { + // 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', function() { + // cy.exec allows you to execute a system command. + // so you can take actions necessary for your test, + // but outside the scope of Cypress. + + // https://on.cypress.io/exec + cy + .exec('echo Jane Lane') + .its('stdout') + .should('contain', 'Jane Lane') + + 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', function() { + // 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') + }) + + it('cy.screenshot() - take a screenshot', function() { + // https://on.cypress.io/screenshot + cy.screenshot('my-image') + }) + + it('cy.wrap() - wrap an object', function() { + // https://on.cypress.io/wrap + cy + .wrap({foo: 'bar'}) + .should('have.property', 'foo') + .and('include', 'bar') + }) + }) + + context('Connectors', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/connectors') + }) + + it('.each() - iterate over an array of elements', function() { + // https://on.cypress.io/each + cy.get('.connectors-each-ul>li').each(function($el, index, $list) { + console.log($el, index, $list) + }) + }) + + it('.its() - get properties on the current subject', function() { + // 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', function() { + // 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', function() { + // https://on.cypress.io/spread + var arr = ['foo', 'bar', 'baz'] + + cy.wrap(arr).spread(function(foo, bar, baz) { + expect(foo).to.eq('foo') + expect(bar).to.eq('bar') + expect(baz).to.eq('baz') + }) + }) + + it('.then() - invoke a callback function with the current subject', function() { + // https://on.cypress.io/then + cy.get('.connectors-list>li').then(function($lis) { + expect($lis).to.have.length(3) + expect($lis.eq(0)).to.contain('Walk the dog') + expect($lis.eq(1)).to.contain('Feed the cat') + expect($lis.eq(2)).to.contain('Write JavaScript') + }) + }) + }) + + context('Aliasing', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/aliasing') + }) + + // We alias a DOM element for use later + // We don't have to traverse to the element + // later in our code, we just reference it with @ + + it('.as() - alias a route or DOM element for later use', function() { + // this is a good use case for an alias, + // we don't want to write this long traversal again + + // https://on.cypress.io/as + cy + .get('.as-table') + .find('tbody>tr') + .first() + .find('td') + .first() + .find('button') + .as('firstBtn') + + // maybe do some more testing here... + + // when we reference the alias, we place an + // @ in front of it's name + cy.get('@firstBtn').click() + + cy + .get('@firstBtn') + .should('have.class', 'btn-success') + .and('contain', 'Changed') + }) + }) + + context('Waiting', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/waiting') + }) + // BE CAREFUL of adding unnecessary wait times. + + // https://on.cypress.io/wait + it('cy.wait() - wait for a specific amount of time', function() { + 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) + }) + + // Waiting for a specific resource to resolve + // is covered within the cy.route() test below + }) + + context('Network Requests', function() { + beforeEach(function() { + 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', function() { + // https://on.cypress.io/server + cy.server().should(function(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', function() { + // https://on.cypress.io/request + cy + .request('https://jsonplaceholder.typicode.com/comments') + .should(function(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.route() - route responses to matching requests', function() { + var message = "whoa, this comment doesn't exist" + cy.server() + + // **** GET comments route **** + + // https://on.cypress.io/route + cy.route(/comments\/1/).as('getComment') + + // we have code that fetches a comment when + // the button is clicked in scripts.js + cy.get('.network-btn').click() + + // **** Wait **** + + // Wait for a specific resource to resolve + // continuing to the next command + + // https://on.cypress.io/wait + cy + .wait('@getComment') + .its('status') + .should('eq', 200) + + // **** POST comment route **** + + // Specify the route to listen to method 'POST' + 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') + + // get the route + cy.get('@postComment').then(function(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()' + ) + }) + + // **** Stubbed PUT comment route **** + cy + .route({ + method: 'PUT', + url: /comments\/\d+/, + 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) + }) + }) + + context('Files', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/files') + }) + it('cy.fixture() - load a fixture', function() { + // Instead of writing a response inline you can + // connect a response with a fixture file + // located in fixtures folder. + + cy.server() + + // https://on.cypress.io/fixture + cy.fixture('example.json').as('comment') + + cy.route(/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(/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(/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.readFile() - read a files contents', function() { + // You can read a file and yield its contents + // The filePath is relative to your project's root. + + // https://on.cypress.io/readfile + cy.readFile('cypress.json').then(function(json) { + expect(json).to.be.an('object') + }) + }) + + it('cy.writeFile() - write to a file', function() { + // You can write to a file with the specified contents + + // Use a response from a request to automatically + // generate a fixture file for use later + cy + .request('https://jsonplaceholder.typicode.com/users') + .then(function(response) { + // https://on.cypress.io/writefile + cy.writeFile('cypress/fixtures/users.json', response.body) + }) + cy.fixture('users').should(function(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(function(profile) { + expect(profile.name).to.eq('Jane') + }) + }) + }) + + context('Local Storage', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/commands/local-storage') + }) + // Although local storage is automatically cleared + // to maintain a clean state in between tests + // sometimes we need to clear the local storage manually + + it('cy.clearLocalStorage() - clear all data in local storage', function() { + // https://on.cypress.io/clearlocalstorage + cy + .get('.ls-btn') + .click() + .should(function() { + 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(function(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(function() { + 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(function(ls) { + expect(ls.getItem('prop1')).to.be.null + expect(ls.getItem('prop2')).to.eq('blue') + expect(ls.getItem('prop3')).to.eq('magenta') + }) + + // **** Clear key's matching regex in Local Storage **** + cy + .get('.ls-btn') + .click() + .should(function() { + 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(function(ls) { + expect(ls.getItem('prop1')).to.be.null + expect(ls.getItem('prop2')).to.be.null + expect(ls.getItem('prop3')).to.eq('magenta') + }) + }) + }) + + context('Cookies', function() { + beforeEach(function() { + 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', function() { + // 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', function() { + // 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(function(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', function() { + // 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', function() { + // 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', function() { + // 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') + }) + }) + + context('Spies, Stubs, and Clock', function() { + it('cy.spy() - wrap a method in a spy', function() { + // https://on.cypress.io/spy + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + + var obj = { + foo() {} + } + + var spy = cy.spy(obj, 'foo').as('anyArgs') + + obj.foo() + + expect(spy).to.be.called + }) + + it('cy.stub() - create a stub and/or replace a function with a stub', function() { + // https://on.cypress.io/stub + cy.visit('https://example.cypress.io/commands/spies-stubs-clocks') + + var obj = { + foo() {} + } + + var stub = cy.stub(obj, 'foo').as('foo') + + obj.foo('foo', 'bar') + + expect(stub).to.be.called + }) + + it('cy.clock() - control time in the browser', function() { + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + var now = new Date(Date.UTC(2017, 2, 14)).getTime() + + // https://on.cypress.io/clock + 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', function() { + // create the date in UTC so its always the same + // no matter what local timezone the browser is running in + var now = new Date(Date.UTC(2017, 2, 14)).getTime() + + // https://on.cypress.io/tick + 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') + }) + }) + + context('Utilities', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/utilities') + }) + + it('Cypress._.method() - call a lodash method', function() { + // use the _.chain, _.map, _.take, and _.value functions + // https://on.cypress.io/_ + cy + .request('https://jsonplaceholder.typicode.com/users') + .then(function(response) { + var ids = Cypress._ + .chain(response.body) + .map('id') + .take(3) + .value() + + expect(ids).to.deep.eq([1, 2, 3]) + }) + }) + + it('Cypress.$(selector) - call a jQuery method', function() { + // https://on.cypress.io/$ + var $li = Cypress.$('.utility-jquery li:first') + + cy + .wrap($li) + .should('not.have.class', 'active') + .click() + .should('have.class', 'active') + }) + + it('Cypress.moment() - format or parse dates using a moment method', function() { + // use moment's format function + // https://on.cypress.io/cypress-moment + var time = Cypress.moment() + .utc('2014-04-25T19:38:53.196Z') + .format('h:mm A') + + cy + .get('.utility-moment') + .contains('3:38 PM') + .should('have.class', 'badge') + }) + + it('Cypress.Blob.method() - blob utilities and base64 string conversion', function() { + cy.get('.utility-blob').then(function($div) { + // https://on.cypress.io/blob + // https://github.com/nolanlawson/blob-util#imgSrcToDataURL + // get the dataUrl string for the javascript-logo + return Cypress.Blob + .imgSrcToDataURL( + 'https://example.cypress.io/assets/img/javascript-logo.png', + undefined, + 'anonymous' + ) + .then(function(dataUrl) { + // create an <img> element and set its src to the dataUrl + var 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('new Cypress.Promise(function) - instantiate a bluebird promise', function() { + // https://on.cypress.io/promise + var waited = false + + function waitOneSecond() { + // return a promise that resolves after 1 second + return new Cypress.Promise(function(resolve, reject) { + setTimeout(function() { + // set waited to true + waited = true + + // resolve with 'foo' string + resolve('foo') + }, 1000) + }) + } + + cy.then(function() { + // return a promise to cy.then() that + // is awaited until it resolves + return waitOneSecond().then(function(str) { + expect(str).to.eq('foo') + expect(waited).to.be.true + }) + }) + }) + }) + + context('Cypress.config()', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/cypress-api/config') + }) + + it('Cypress.config() - get and set configuration options', function() { + // https://on.cypress.io/config + var 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.env()', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/cypress-api/env') + }) + + // We can set environment variables for highly dynamic values + + // https://on.cypress.io/environment-variables + it('Cypress.env() - get environment variables', function() { + // 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.Cookies', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/cypress-api/cookies') + }) + + // https://on.cypress.io/cookies + it('Cypress.Cookies.debug() - enable or disable debugging', function() { + 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('Cypress.Cookies.preserveOnce() - preserve cookies by key', function() { + // 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('Cypress.Cookies.defaults() - set defaults for all cookies', function() { + // 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.dom', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/cypress-api/dom') + }) + + // https://on.cypress.io/dom + it('Cypress.dom.isHidden() - determine if a DOM element is hidden', function() { + var hiddenP = Cypress.$('.dom-p p.hidden').get(0) + var 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.Server', function() { + beforeEach(function() { + cy.visit('https://example.cypress.io/cypress-api/server') + }) + + // Permanently override server options for + // all instances of cy.server() + + // https://on.cypress.io/cypress-server + it('Cypress.Server.defaults() - change default config of server', function() { + Cypress.Server.defaults({ + delay: 0, + force404: false, + whitelist: function(xhr) { + // handle custom logic for whitelisting + } + }) + }) + }) +}) diff --git a/cypress/integration/homepage_spec.js b/cypress/integration/homepage_spec.js new file mode 100644 index 00000000..c23493a6 --- /dev/null +++ b/cypress/integration/homepage_spec.js @@ -0,0 +1,20 @@ +/* global beforeEach, cy, describe, it */ + +describe('The Home Page', () => { + beforeEach(() => { + cy.visit('/') + }) + + it('has the correct title', () => { + cy + .title() + .should( + 'equal', + 'Dittly - When it comes to home‑improvement, do it together.' + ) + }) + + it('displays the login link', () => { + cy.get('a:contains("Login")').should('be.visible') + }) +}) diff --git a/cypress/screenshots/my-image.png b/cypress/screenshots/my-image.png new file mode 100644 index 00000000..d5a19198 Binary files /dev/null and b/cypress/screenshots/my-image.png differ diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 00000000..c1f5a772 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This is will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 00000000..d68db96d --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/package.json b/package.json index 5ca886e1..9ea15d27 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "license": "MIT", "scripts": { "build": "next build", + "cypress:open": "cypress open", "dev": "node -r @std/esm index.js", "lint": "eslint '**/*.js'", "precommit": "lint-staged", @@ -85,6 +86,7 @@ "babel-plugin-root-import": "^5.1.0", "babel-plugin-styled-components": "^1.2.0", "coveralls": "^3.0.0", + "cypress": "^1.0.1", "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.1", "eslint": "^4.6.1", diff --git a/yarn.lock b/yarn.lock index f14fbcc9..79e18ae8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,19 @@ # yarn lockfile v1 +"@cypress/listr-verbose-renderer@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +"@cypress/xvfb@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.0.4.tgz#e7391d534baef2b180bd84fdc2ffea1020f7d6a5" + "@hypnosphi/fuse.js@^3.0.9": version "3.0.9" resolved "https://registry.yarnpkg.com/@hypnosphi/fuse.js/-/fuse.js-3.0.9.tgz#ea99f6121b4a8f065b4c71f85595db2714498807" @@ -523,6 +536,12 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +async@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4" + dependencies: + lodash "^4.14.0" + async@^1.4.0, async@~1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1821,6 +1840,10 @@ block-stream@*: dependencies: inherits "~2.0.0" +bluebird@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -1958,6 +1981,10 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + buffer-alloc-unsafe@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-0.1.1.tgz#ffe1f67551dd055737de253337bfe853dfab1a6a" @@ -2081,6 +2108,14 @@ chain-function@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" +chalk@2.1.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -2091,14 +2126,6 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - cheerio@^1.0.0-rc.2: version "1.0.0-rc.2" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" @@ -2276,7 +2303,7 @@ commander@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" -common-tags@^1.4.0: +common-tags@1.4.0, common-tags@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0" dependencies: @@ -2297,7 +2324,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.5.2, concat-stream@^1.6.0: +concat-stream@1.6.0, concat-stream@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -2673,6 +2700,35 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" +cypress@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-1.0.1.tgz#ee732759a832a3bdbd527d12d1cd20ab9990ace5" + dependencies: + "@cypress/listr-verbose-renderer" "0.4.1" + "@cypress/xvfb" "1.0.4" + bluebird "3.5.0" + chalk "2.1.0" + commander "2.9.0" + common-tags "1.4.0" + debug "2.6.8" + dev-null "0.1.1" + extract-zip "1.6.5" + fs-extra "4.0.1" + getos "2.8.4" + glob "7.1.2" + is-ci "1.0.10" + is-installed-globally "0.1.0" + listr "0.12.0" + lodash "4.17.4" + minimist "1.2.0" + progress "1.1.8" + ramda "0.24.1" + request "2.81.0" + request-progress "0.3.1" + tmp "0.0.31" + url "0.11.0" + yauzl "2.8.0" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -2819,6 +2875,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +dev-null@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dev-null/-/dev-null-0.1.1.tgz#5a205ce3c2b2ef77b6238d6ba179eb74c6a0e818" + diff@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.0.tgz#056695150d7aa93237ca7e378ac3b1682b7963b9" @@ -3430,6 +3490,15 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" +extract-zip@1.6.5: + version "1.6.5" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.5.tgz#99a06735b6ea20ea9b705d779acffcc87cff0440" + dependencies: + concat-stream "1.6.0" + debug "2.2.0" + mkdirp "0.5.0" + yauzl "2.4.1" + extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -3484,6 +3553,12 @@ fbjs@^0.8.16: setimmediate "^1.0.5" ua-parser-js "^0.7.9" +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + dependencies: + pend "~1.2.0" + figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -3770,6 +3845,12 @@ get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" +getos@2.8.4: + version "2.8.4" + resolved "https://registry.yarnpkg.com/getos/-/getos-2.8.4.tgz#7b8603d3619c28e38cb0fe7a4f63c3acb80d5163" + dependencies: + async "2.1.4" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -3886,6 +3967,17 @@ glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + 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@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -3896,6 +3988,12 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.0.tgz#10d34039e0df04272e262cf24224f7209434df4f" + dependencies: + ini "^1.3.4" + global@^4.3.0, global@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" @@ -4288,7 +4386,7 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" -ini@^1.2.0, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: +ini@^1.3.4, ini@~1.3.0: version "1.3.4" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" @@ -4371,7 +4469,7 @@ is-callable@^1.1.1, is-callable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" -is-ci@^1.0.10: +is-ci@1.0.10, is-ci@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" dependencies: @@ -4443,6 +4541,13 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-installed-globally@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + is-my-json-valid@^2.12.4: version "2.16.1" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.1.tgz#5a846777e2c2620d1e69104e5d3a03b1f6088f11" @@ -5113,7 +5218,7 @@ listr-verbose-renderer@^0.4.0: date-fns "^1.27.2" figures "^1.7.0" -listr@^0.12.0: +listr@0.12.0, listr@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a" dependencies: @@ -5296,7 +5401,7 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@4.x.x, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1: +lodash@4.17.4, lodash@4.x.x, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1, lodash@^4.6.1: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -5538,6 +5643,12 @@ mkdirp-then@1.2.0: any-promise "^1.1.0" mkdirp "^0.5.0" +mkdirp@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + dependencies: + minimist "0.0.8" + "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -5556,6 +5667,10 @@ moment@^2.11.2: version "2.18.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -6206,6 +6321,10 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + performance-now@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" @@ -6584,6 +6703,10 @@ process@~0.5.1: version "0.5.2" resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" +progress@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + progress@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" @@ -6725,6 +6848,10 @@ railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" +ramda@0.24.1: + version "0.24.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857" + randexp@^0.4.2: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" @@ -7145,34 +7272,7 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" -request@^2.74.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@^2.79.0, request@^2.81.0: +request@2.81.0, request@^2.79.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -7250,6 +7350,12 @@ request@~2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" +request-progress@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-0.3.1.tgz#0721c105d8a96ac6b2ce8b2c89ae2d5ecfcf6b3a" + dependencies: + throttleit "~0.0.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -7988,6 +8094,10 @@ throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" +throttleit@~0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" + through2@^2.0.0, through2@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" @@ -8009,7 +8119,7 @@ timers-browserify@^2.0.2: dependencies: setimmediate "^1.0.4" -tmp@^0.0.31: +tmp@0.0.31, tmp@^0.0.31: version "0.0.31" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" dependencies: @@ -8633,6 +8743,19 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" +yauzl@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + dependencies: + fd-slicer "~1.0.1" + +yauzl@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.8.0.tgz#79450aff22b2a9c5a41ef54e02db907ccfbf9ee2" + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.0.1" + zen-observable-ts@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.4.0.tgz#a74bc9fe59747948a577bd513d438e70fcfae7e2"