diff --git a/.codeclimate.yml b/.codeclimate.yml index c6e09732..81b14939 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -19,4 +19,5 @@ exclude_paths: - config/ - node_modules/ - test/ -- build +- build/ +- cypress/ diff --git a/.gitignore b/.gitignore index f84f6b06..05235606 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,7 @@ test/unit/coverage/ test/unit/coverage/ karma-*/ + +#Ignore Cypress screenshots +cypress/screenshots +.Trash-0/ diff --git a/.travis.yml b/.travis.yml index 95774768..90c1aca3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,18 +18,19 @@ notifications: slack: gppmds8-github:sJE6QlBfPGMkfUf48wuTsKa7 before_install: - - docker build -t alaxalves/front -f Dockerfile.test . - openssl aes-256-cbc -K $encrypted_fb7d710cc1aa_key -iv $encrypted_fb7d710cc1aa_iv -in .travis/alax-digitalocean-key.enc -out .travis/alax-digitalocean-key -d before_script: - npm install coveralls - - docker build -t alaxalves/front -f Dockerfile.dev . + - docker-compose -f docker-compose.test.yml up -d script: - - docker run -v /home/travis/build/fga-gpp-mds/Falko-2017.2-FrontEnd:/Falko-2017.2-FrontEnd alaxalves/front /bin/sh -c "npm run unit" + - docker exec -it falko-front npm run unit + - docker exec -it falko-front npx cypress run after_script: - "./node_modules/coveralls/bin/coveralls.js < ./test/unit/coverage/lcov.info" + - docker-compose down --remove-orphans after_success: - eval "$(ssh-agent -s)" diff --git a/Dockerfile b/Dockerfile index 75838e61..1a8fb790 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,5 +22,6 @@ RUN rm -rf ./test RUN rm -rf ./src RUN chmod +x start-homolog.sh +EXPOSE 80 ENTRYPOINT ["./start-homolog.sh"] diff --git a/Dockerfile.dev b/Dockerfile.dev index ee49e53b..99891ab6 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,7 +3,7 @@ FROM node:slim MAINTAINER alaxallves@gmail.com RUN apt-get update -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && apt-get install -y nodejs tree libfontconfig bzip2 && npm install --quiet --global vue-cli +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && apt-get install -y nodejs tree libfontconfig bzip2 xvfb libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 && npm install --quiet --global vue-cli RUN mkdir /Falko-2017.2-FrontEnd diff --git a/Dockerfile.test b/Dockerfile.test index 139f0918..89ebd166 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -3,7 +3,7 @@ FROM node:slim MAINTAINER alaxallves@gmail.com RUN apt-get update -RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && apt-get install -y nodejs tree libfontconfig bzip2 && npm install --quiet --global vue-cli +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && apt-get install -y nodejs tree libfontconfig bzip2 xvfb libgtk2.0-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 && npm install --quiet --global vue-cli RUN mkdir /Falko-2017.2-FrontEnd diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..39066309 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,27 @@ +## Comportamento esperado + +Descreva qual o comportamento que o sistema deveria possuir. + +## *Comportamento atual + +Descreva como o sistema se comporta atualmente. + +## *Passos para reproduzir o comportamento + +Em caso de bugs, cite quais passos foram executados para chegar no problema. + +## **Critérios de Aceitação + +Diga quais os critérios o sistema deve possuir para que seja enviado o Pull Request. + + + +## Checklist + +- [ ] A issue possui Labels. +- [ ] A issue possui prints de tela quando necessário. +- [ ] A issue possui nome significativo. + +**Exclua caso a issue seja um bug + +*Exclua caso a issue seja uma história diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js index 5f6722da..b26517f9 100644 --- a/build/webpack.prod.conf.js +++ b/build/webpack.prod.conf.js @@ -54,6 +54,7 @@ var webpackConfig = merge(baseWebpackConfig, { // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ + favicon: 'src/assets/logo.png', filename: process.env.NODE_ENV === 'testing' ? 'index.html' : config.build.index, diff --git a/cypress.json b/cypress.json new file mode 100644 index 00000000..dfca4140 --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "videoRecording": false +} 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/authorization_spec.js b/cypress/integration/authorization_spec.js new file mode 100644 index 00000000..f6b3b982 --- /dev/null +++ b/cypress/integration/authorization_spec.js @@ -0,0 +1,163 @@ +describe('Authorization tests', function (){ + beforeEach(function(){ + cy.visit('localhost:8080/#/') + + cy.server() + + // Stubing server response + cy.route({ + method: 'POST', + url: '/authenticate', + status: 200, + response: { + 'auth_token': 'token123', + 'user': { + 'id': 1, + 'name': 'Carla', + 'email': 'carla@gmail.com' + } + } + }).as('login') + }) + + it('should access homepage', function(){ + + cy.title().should('include', 'Falko') + + cy.get('#loginRegisterComponent').within(function(){ + cy.get('#loginForm').within(function(){ + cy.get('.falko-button').eq(0).contains('Log In') + }) + }) + + cy.get('h1').eq(0).contains('Start now!') + cy.get('h1').eq(1).contains('Free Software') + cy.get('h1').eq(2).contains('See beyond') + }) + + it('should log in user', function(){ + cy.get('form').within(function () { + cy.get('input:first').eq(0).should('have.attr', 'placeholder', 'Email') + .type('carla@gmail.com').should('have.value', 'carla@gmail.com') + + cy.get('input:last').eq(0).should('have.attr', 'placeholder', 'Password') + .type('123456789').should('have.value', '123456789') + }) + + cy.get('.falko-button').eq(0).click() + + cy.url().should('eq', 'http://localhost:8080/#/projects') + }) + + it('should not log in invalid user', function(){ + cy.route({ + method: 'POST', + url: '/authenticate', + status: 401, + response: { + "error": { + "user_authentication": [ + "invalid credentials" + ] + } + } + }).as('invalidLogin') + + cy.get('form').within(function () { + cy.get('input:first').eq(0).should('have.attr', 'placeholder', 'Email') + .type('invalid@gmail').should('have.value', 'invalid@gmail') + + cy.get('.text-danger').contains('The email field must be a valid email.') + + cy.get('input:first').eq(0).should('have.attr', 'placeholder', 'Email') + .type('.com').should('have.value', 'invalid@gmail.com') + + cy.get('input:last').eq(0).should('have.attr', 'placeholder', 'Password') + .type('12345').should('have.value', '12345') + + cy.get('.text-danger').contains('The password field must be at least 6 characters.') + + cy.get('input:last').eq(0).should('have.attr', 'placeholder', 'Password') + .type('6').should('have.value', '123456') + }) + + cy.get('.falko-button').eq(0).click() + + cy.get('.text-danger').contains('Wrong Credentials') + + cy.url().should('eq', 'http://localhost:8080/#/') + }) + + it('should register user', function(){ + // Stubing server response + cy.route({ + method: 'POST', + url: '/users', + status: 200, + response: { + 'auth_token': 'token123', + 'user': { + 'id': 1, + 'name': 'Carla', + 'email': 'carla@gmail.com' + } + } + }).as('register') + + cy.get('#pills-register-tab').click() + + cy.get('form').within(function () { + cy.get('input').eq(2).should('have.attr', 'placeholder', 'Username') + .type('Carla').should('have.value', 'Carla') + + cy.get('input').eq(3).should('have.attr', 'placeholder', 'Enter email') + .type('carla@gmail.com').should('have.value', 'carla@gmail.com') + + cy.get('input').eq(4).should('have.attr', 'placeholder', 'Password') + .type('123456789').should('have.value', '123456789') + + cy.get('input').eq(5).should('have.attr', 'placeholder', 'Confirm Password') + .type('123456789').should('have.value', '123456789') + + cy.get('input').eq(6).should('have.attr', 'placeholder', 'GitHub') + .type('carlaGit').should('have.value', 'carlaGit') + }) + + cy.get('.falko-button').eq(1).click() + + cy.url().should('eq', 'http://localhost:8080/#/projects') + }) + + it('should not register invalid user', function(){ + // Stubing server response + cy.route({ + method: 'POST', + url: '/users', + status: 401, + response: {} + }).as('invalidRegister') + + cy.get('#pills-register-tab').click() + + cy.get('form').within(function () { + cy.get('input').eq(2).should('have.attr', 'placeholder', 'Username') + .type('Carla').should('have.value', 'Carla') + + cy.get('input').eq(3).should('have.attr', 'placeholder', 'Enter email') + .type('carla@gmail').should('have.value', 'carla@gmail') + + cy.get('input').eq(4).should('have.attr', 'placeholder', 'Password') + .type('12345').should('have.value', '12345') + + cy.get('input').eq(5).should('have.attr', 'placeholder', 'Confirm Password') + .type('1234').should('have.value', '1234') + + cy.get('input').eq(6).should('have.attr', 'placeholder', 'GitHub') + .type('carlaGit').should('have.value', 'carlaGit') + }) + + cy.get('.falko-button').eq(1).click() + + cy.url().should('eq', 'http://localhost:8080/#/') + }) +}) diff --git a/cypress/integration/example_spec.js b/cypress/integration/example_spec.js new file mode 100644 index 00000000..bae4b3bc --- /dev/null +++ b/cypress/integration/example_spec.js @@ -0,0 +1,1478 @@ +// +// **** 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.skip('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/projects_spec.js b/cypress/integration/projects_spec.js new file mode 100644 index 00000000..878c9f05 --- /dev/null +++ b/cypress/integration/projects_spec.js @@ -0,0 +1,147 @@ +describe('Projects tests', function(){ + + function login(){ + cy.route({ + method: 'POST', + url: '/authenticate', + status: 200, + response: { + 'auth_token': 'token123', + 'user': { + 'id': 1, + 'name': 'Carla', + 'email': 'carla@gmail.com' + } + } + }).as('login') + + cy.get('form').within(function () { + cy.get('input:first').eq(0).should('have.attr', 'placeholder', 'Email') + .type('carla@gmail.com').should('have.value', 'carla@gmail.com') + + cy.get('input:last').eq(0).should('have.attr', 'placeholder', 'Password') + .type('123456789').should('have.value', '123456789') + }) + + cy.get('.falko-button').eq(0).click() + } + + beforeEach(function(){ + cy.visit('localhost:8080/#/') + + cy.server() + + cy.route({ + method: 'GET', + url: '/users\/1/projects', + status: 200, + response: [ + { + "id": 1, + "name": "Owla", + "description": "Improving classes", + "user_id": 2, + }, + { + "id": 2, + "name": "Falko", + "description": "Agile Projects Manager", + "user_id": 2, + } + ] + }).as('getProjects') + }) + + it('should get projects', function(){ + + login() + + cy.get('.card-header').eq(0).contains('Owla') + cy.get('.card-body').within(function(){ + cy.get('.card-text').eq(3).contains('Improving classes') + }) + + cy.get('.card-header').eq(1).contains('Falko') + cy.get('.card-body').within(function(){ + cy.get('.card-text').eq(7).contains('Agile Projects Manager') + }) + }) + + it('should add a project', function() { + cy.route({ + method: 'POST', + url: '/users\/1/projects', + status: 200, + response: [ + { + "id": 1, + "name": "Owla", + "description": "Improving classes", + "user_id": 1, + }, + { + "id": 2, + "name": "Falko", + "description": "Agile Projects Manager", + "user_id": 1, + }, + { + "id": 3, + "name": "NewProject", + "description": "New Project Description", + "user_id": 1, + } + ] + }).as('addProject') + + login() + + cy.get('.falko-button').eq(0).click() + + cy.get('.modal-body').within(function(){ + cy.get('input:first').type('NewProject') + cy.get('input:last').type('New Project Description') + + }) + + cy.route({ + method: 'GET', + url: '/users\/1/projects', + status: 200, + response: [ + { + "id": 1, + "name": "Owla", + "description": "Improving classes", + "user_id": 2, + }, + { + "id": 2, + "name": "Falko", + "description": "Agile Projects Manager", + "user_id": 2, + }, + { + "id": 3, + "name": "NewProject", + "description": "New Project Description", + "user_id": 2, + } + ] + }).as('newGetProjects') + + cy.get('.modal-footer').within(function(){ + cy.get('.falko-button').eq(0).click() + }) + + cy.wait('@newGetProjects') + + cy.get('.card-header').eq(0).contains('Owla') + cy.get('.card-header').eq(1).contains('Falko') + cy.get('.card-header').eq(2).contains('NewProject') + cy.get('.card-body').within(function () { + cy.get('.card-text').eq(11).contains('New Project Description') + }) + }) +}) + diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 00000000..fd170fba --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,17 @@ +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/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/docker-compose.dev.yml b/docker-compose.dev.yml index 70e19ea1..9fea385e 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -11,7 +11,7 @@ services: external_links: - falko20172backend_default ports: - - 80:8080 + - 8080:8080 networks: - default - falko20172backend_default diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 00000000..54bee020 --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,16 @@ +version: '2' +services: + + falko-front: + container_name: falko-front + image: alaxalves/front:1.5 + command: /bin/bash -lc "npm install && npm run dev" + volumes: + - .:/Falko-2017.2-FrontEnd + external_links: + - falko20172backend_default + ports: + - 8080:8080 + networks: + - default + ipc: host diff --git a/docker-compose.yml b/docker-compose.yml index 8f6a90e1..f4c5abc9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: falko-front: container_name: falko-front - image: alaxalves/front:1.4 + image: alaxalves/front:1.5 command: /bin/bash -lc "npm install && npm run dev" volumes: - .:/Falko-2017.2-FrontEnd @@ -14,6 +14,7 @@ services: networks: - default - falko20172backend_default + ipc: host networks: falko20172backend_default: diff --git a/express-prod-server.js b/express-prod-server.js index ee263eb2..63a4c75d 100644 --- a/express-prod-server.js +++ b/express-prod-server.js @@ -1,7 +1,7 @@ const express = require('express'); const app = express(); -const port = process.env.PORT || 5000; +const port = process.env.PORT || 80; const router = express.Router(); app.use(express.static(`${__dirname}/dist`)); diff --git a/package-lock.json b/package-lock.json index ccfecaf3..3894d7be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,86 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@cypress/listr-verbose-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", + "integrity": "sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "date-fns": "1.29.0", + "figures": "1.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "@cypress/xvfb": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.0.4.tgz", + "integrity": "sha512-Ey34vw8L2L1tFBLAsjCYlv24TVjzuU+sCUwONj8xMdlM5iNEHgj+AoMPR6C1LCijm/9Ue/FNAMffDe/lmKYWTA==", + "dev": true + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -1355,6 +1435,12 @@ "isarray": "1.0.0" } }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, "buffer-xor": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", @@ -1538,6 +1624,12 @@ "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", "dev": true }, + "check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true + }, "chokidar": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", @@ -1567,6 +1659,12 @@ "rimraf": "2.6.2" } }, + "ci-info": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz", + "integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==", + "dev": true + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -1643,6 +1741,24 @@ "integrity": "sha1-JnUyHBAPGVsCh3rEmemRH6NLl4M=", "dev": true }, + "cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "requires": { + "slice-ansi": "0.0.4", + "string-width": "1.0.2" + }, + "dependencies": { + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + } + } + }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -1768,8 +1884,16 @@ "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", - "dev": true + "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=" + }, + "common-tags": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.4.0.tgz", + "integrity": "sha1-EYe+Tz1M8MBCfUP3Tu8fc1AWFMA=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } }, "commondir": { "version": "1.0.1", @@ -2370,6 +2494,510 @@ "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", "dev": true }, + "cypress": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-1.1.1.tgz", + "integrity": "sha1-54VKrXSkq8EuY/f0hveZBiT/k0Y=", + "dev": true, + "requires": { + "@cypress/listr-verbose-renderer": "0.4.1", + "@cypress/xvfb": "1.0.4", + "bluebird": "3.5.0", + "chalk": "2.1.0", + "check-more-types": "2.24.0", + "commander": "2.11.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", + "lazy-ass": "1.6.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" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "dev": true + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs-extra": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.1.tgz", + "integrity": "sha1-f8DGyJV/mD9X8waiTlud3Y0N2IA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "3.0.1", + "universalify": "0.1.1" + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "requires": { + "aws-sign2": "0.6.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.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "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": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + } + }, + "request-progress": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-0.3.1.tgz", + "integrity": "sha1-ByHBBdipasayzossia4tXs/Pazo=", + "dev": true, + "requires": { + "throttleit": "0.0.2" + } + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + }, + "yauzl": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.8.0.tgz", + "integrity": "sha1-eUUK/yKyqcWkHvVOAtuQfM+/nuI=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13", + "fd-slicer": "1.0.1" + } + } + } + }, + "d3": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-4.11.0.tgz", + "integrity": "sha512-o048nfmydnbt0ciIvCUDTq9p62rZYOXzl8cKps0XVzk+5nHgeXmAS7jU4nh+3v82pUyH7t/GFm1bJRX4oIAlPw==", + "requires": { + "d3-array": "1.2.1", + "d3-axis": "1.0.8", + "d3-brush": "1.0.4", + "d3-chord": "1.0.4", + "d3-collection": "1.0.4", + "d3-color": "1.0.3", + "d3-dispatch": "1.0.3", + "d3-drag": "1.2.1", + "d3-dsv": "1.0.7", + "d3-ease": "1.0.3", + "d3-force": "1.1.0", + "d3-format": "1.2.0", + "d3-geo": "1.8.1", + "d3-hierarchy": "1.1.5", + "d3-interpolate": "1.1.5", + "d3-path": "1.0.5", + "d3-polygon": "1.0.3", + "d3-quadtree": "1.0.3", + "d3-queue": "3.0.7", + "d3-random": "1.1.0", + "d3-request": "1.0.6", + "d3-scale": "1.0.6", + "d3-selection": "1.1.0", + "d3-shape": "1.2.0", + "d3-time": "1.0.7", + "d3-time-format": "2.0.5", + "d3-timer": "1.0.7", + "d3-transition": "1.1.0", + "d3-voronoi": "1.1.2", + "d3-zoom": "1.6.0" + } + }, + "d3-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz", + "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==" + }, + "d3-axis": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.8.tgz", + "integrity": "sha1-MacFoLU15ldZ3hQXOjGTMTfxjvo=" + }, + "d3-brush": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.0.4.tgz", + "integrity": "sha1-AMLyOAGfJPbAoZSibUGhUw/+e8Q=", + "requires": { + "d3-dispatch": "1.0.3", + "d3-drag": "1.2.1", + "d3-interpolate": "1.1.5", + "d3-selection": "1.1.0", + "d3-transition": "1.1.0" + } + }, + "d3-chord": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.4.tgz", + "integrity": "sha1-fexPC6iG9xP+ERxF92NBT290yiw=", + "requires": { + "d3-array": "1.2.1", + "d3-path": "1.0.5" + } + }, + "d3-collection": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.4.tgz", + "integrity": "sha1-NC39EoN8kJdPM/HMCnha6lcNzcI=" + }, + "d3-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.0.3.tgz", + "integrity": "sha1-vHZD/KjlOoNH4vva/6I2eWtYUJs=" + }, + "d3-dispatch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.3.tgz", + "integrity": "sha1-RuFJHqqbWMNY/OW+TovtYm54cfg=" + }, + "d3-drag": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.1.tgz", + "integrity": "sha512-Cg8/K2rTtzxzrb0fmnYOUeZHvwa4PHzwXOLZZPwtEs2SKLLKLXeYwZKBB+DlOxUvFmarOnmt//cU4+3US2lyyQ==", + "requires": { + "d3-dispatch": "1.0.3", + "d3-selection": "1.1.0" + } + }, + "d3-dsv": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.7.tgz", + "integrity": "sha512-12szKhDhM/tM5U/Ch3hyJ7sMdcwPqMRmrUWitLLdPBMKO9Wuox95ezKZvemy/fxFbefLF/HIPKUmJMBLLuFDaQ==", + "requires": { + "commander": "2.11.0", + "iconv-lite": "0.4.19", + "rw": "1.3.3" + } + }, + "d3-ease": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", + "integrity": "sha1-aL+8NJM4o4DETYrMT7wzBKotjA4=" + }, + "d3-force": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", + "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==", + "requires": { + "d3-collection": "1.0.4", + "d3-dispatch": "1.0.3", + "d3-quadtree": "1.0.3", + "d3-timer": "1.0.7" + } + }, + "d3-format": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.2.0.tgz", + "integrity": "sha1-a0gLqohohdRlHcJIqPSsnaFtsHo=" + }, + "d3-geo": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.8.1.tgz", + "integrity": "sha512-PuvmWl1A1UXuaxcH55EGNhvMNAUBS0RQN2PAnxrZbDvDX56Xwkd+Yp1t1+ECkaJO3my+dnhxAyqAKMyyK+IFPQ==", + "requires": { + "d3-array": "1.2.1" + } + }, + "d3-hierarchy": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz", + "integrity": "sha1-ochFxC+Eoga88cAcAQmOpN2qeiY=" + }, + "d3-interpolate": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.1.5.tgz", + "integrity": "sha1-aeCZ/zkhRxblY8muw+qdHqS4p58=", + "requires": { + "d3-color": "1.0.3" + } + }, + "d3-path": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.5.tgz", + "integrity": "sha1-JB6xhJvZ6egCHA0KeZ+KDo5EF2Q=" + }, + "d3-polygon": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.3.tgz", + "integrity": "sha1-FoiOkCZGCTPysXllKtN4Ik04LGI=" + }, + "d3-quadtree": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.3.tgz", + "integrity": "sha1-rHmH4+I/6AWpkPKOG1DTj8uCJDg=" + }, + "d3-queue": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", + "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" + }, + "d3-random": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.0.tgz", + "integrity": "sha1-ZkLlBsb6OmSFldKyRpeIqNElKdM=" + }, + "d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-FJj8ySY6GYuAJHZMaCQ83xEYE4KbkPkmxZ3Hu6zA1xxG2GD+z6P+Lyp+zjdsHf0xEbp2xcluDI50rCS855EQ6w==", + "requires": { + "d3-collection": "1.0.4", + "d3-dispatch": "1.0.3", + "d3-dsv": "1.0.7", + "xmlhttprequest": "1.8.0" + } + }, + "d3-scale": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.6.tgz", + "integrity": "sha1-vOGdqA06DPQiyVQ64zIghiILNO0=", + "requires": { + "d3-array": "1.2.1", + "d3-collection": "1.0.4", + "d3-color": "1.0.3", + "d3-format": "1.2.0", + "d3-interpolate": "1.1.5", + "d3-time": "1.0.7", + "d3-time-format": "2.0.5" + } + }, + "d3-selection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.1.0.tgz", + "integrity": "sha1-GZhoSJZIj4OcoDchI9o08dMYgJw=" + }, + "d3-shape": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.2.0.tgz", + "integrity": "sha1-RdAVOPBkuv0F6j1tLLdI/YxB93c=", + "requires": { + "d3-path": "1.0.5" + } + }, + "d3-time": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.7.tgz", + "integrity": "sha1-lMr27bt4ebuAnQ0fdXK8SEgvcnA=" + }, + "d3-time-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.0.5.tgz", + "integrity": "sha1-nXeAIE98kRnJFwsaVttN6aivly4=", + "requires": { + "d3-time": "1.0.7" + } + }, + "d3-timer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz", + "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==" + }, + "d3-transition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.0.tgz", + "integrity": "sha1-z8hcdOUjkyQpBUZiNXKZBWDDlm8=", + "requires": { + "d3-color": "1.0.3", + "d3-dispatch": "1.0.3", + "d3-ease": "1.0.3", + "d3-interpolate": "1.1.5", + "d3-selection": "1.1.0", + "d3-timer": "1.0.7" + } + }, + "d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha1-Fodmfo8TotFYyAwUgMWinLDYlzw=" + }, + "d3-zoom": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.6.0.tgz", + "integrity": "sha512-viq+6rXA9JQY1wD+gpDEdlOtCeJ6IfcsNT2aVr31VTjIAIhYlO0YurQ80yDRZeMJw5P/e6nVPhGJF9D9Ade5Og==", + "requires": { + "d3-dispatch": "1.0.3", + "d3-drag": "1.2.1", + "d3-interpolate": "1.1.5", + "d3-selection": "1.1.0", + "d3-transition": "1.1.0" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2385,6 +3013,12 @@ "integrity": "sha1-dxY+qcINhkG0cH6PGKvfmnjzSDU=", "dev": true }, + "date-fns": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", + "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", + "dev": true + }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -2521,6 +3155,12 @@ "repeating": "2.0.1" } }, + "dev-null": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz", + "integrity": "sha1-WiBc48Ky73e2I41roXnrdMag6Bg=", + "dev": true + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -2665,6 +3305,12 @@ "integrity": "sha1-QyLVLBUUBuPq73StAmdog+hBZBg=", "dev": true }, + "elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "dev": true + }, "elliptic": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", @@ -3284,6 +3930,12 @@ "safe-buffer": "5.1.1" } }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, "expand-braces": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", @@ -3820,6 +4472,26 @@ "readable-stream": "2.3.3" } }, + "getos": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/getos/-/getos-2.8.4.tgz", + "integrity": "sha1-e4YD02GcKOOMsP56T2PDrLgNUWM=", + "dev": true, + "requires": { + "async": "2.1.4" + }, + "dependencies": { + "async": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.1.4.tgz", + "integrity": "sha1-LSFgx3iAMuTdbL4lAvH5osj2zeQ=", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + } + } + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -3891,6 +4563,15 @@ "is-glob": "2.0.1" } }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "1.3.5" + } + }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", @@ -4342,8 +5023,7 @@ "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" }, "icss-replace-symbols": { "version": "1.1.0", @@ -4415,6 +5095,12 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, "inject-loader": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/inject-loader/-/inject-loader-3.0.1.tgz", @@ -4546,6 +5232,15 @@ "builtin-modules": "1.1.1" } }, + "is-ci": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", + "integrity": "sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4=", + "dev": true, + "requires": { + "ci-info": "1.1.2" + } + }, "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", @@ -4606,6 +5301,16 @@ "is-extglob": "1.0.0" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "0.1.1", + "is-path-inside": "1.0.0" + } + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -5160,6 +5865,12 @@ "graceful-fs": "4.1.11" } }, + "lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -5185,6 +5896,249 @@ "type-check": "0.3.2" } }, + "listr": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.12.0.tgz", + "integrity": "sha1-a84sD1YD+klYDqF81qAMwOX6RRo=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "cli-truncate": "0.2.1", + "figures": "1.7.0", + "indent-string": "2.1.0", + "is-promise": "2.1.0", + "is-stream": "1.1.0", + "listr-silent-renderer": "1.1.1", + "listr-update-renderer": "0.2.0", + "listr-verbose-renderer": "0.4.1", + "log-symbols": "1.0.2", + "log-update": "1.0.2", + "ora": "0.2.3", + "p-map": "1.2.0", + "rxjs": "5.5.2", + "stream-to-observable": "0.1.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "cli-spinners": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", + "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "ora": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", + "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "cli-spinners": "0.1.2", + "object-assign": "4.1.1" + } + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true + }, + "listr-update-renderer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz", + "integrity": "sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "cli-truncate": "0.2.1", + "elegant-spinner": "1.0.1", + "figures": "1.7.0", + "indent-string": "3.2.0", + "log-symbols": "1.0.2", + "log-update": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "listr-verbose-renderer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", + "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "cli-cursor": "1.0.2", + "date-fns": "1.29.0", + "figures": "1.7.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5", + "object-assign": "4.1.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -5470,6 +6424,49 @@ } } }, + "log-update": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", + "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", + "dev": true, + "requires": { + "ansi-escapes": "1.4.0", + "cli-cursor": "1.0.2" + }, + "dependencies": { + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "1.0.1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "1.1.1", + "onetime": "1.1.0" + } + } + } + }, "log4js": { "version": "0.6.38", "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", @@ -6334,6 +7331,12 @@ "p-limit": "1.1.0" } }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, "pac-proxy-agent": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", @@ -8698,6 +9701,12 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "ramda": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.24.1.tgz", + "integrity": "sha1-w7d1UZfzW43DUCIoJixMkd22uFc=", + "dev": true + }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", @@ -9135,6 +10144,11 @@ "is-promise": "2.1.0" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, "rx-lite": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", @@ -9150,6 +10164,15 @@ "rx-lite": "4.0.8" } }, + "rxjs": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", + "integrity": "sha512-oRYoIKWBU3Ic37fLA5VJu31VqQO4bWubRntcHSJ+cwaDQBwdnZ9x4zmhJfm/nFQ2E82/I4loSioHnACamrKGgA==", + "dev": true, + "requires": { + "symbol-observable": "1.0.4" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -9657,21 +10680,18 @@ "xtend": "4.0.1" } }, + "stream-to-observable": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", + "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=", + "dev": true + }, "strict-uri-encode": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -9692,6 +10712,15 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -9754,6 +10783,12 @@ "whet.extend": "0.9.9" } }, + "symbol-observable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=", + "dev": true + }, "table": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", @@ -10033,6 +11068,12 @@ "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", "dev": true }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -10654,6 +11695,11 @@ "integrity": "sha1-ZGV4SKIP/F31g6Qq2KJ3tFErvE0=", "dev": true }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=" + }, "xmlhttprequest-ssl": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", diff --git a/package.json b/package.json index 3e921fe6..8fcd974a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "axios": "^0.16.2", "babel-preset-es2015": "^6.24.1", + "d3": "^4.11.0", "ejs": "^2.5.7", "es6-promise": "^4.1.1", "github-api": "^3.0.0", @@ -46,6 +47,7 @@ "cross-spawn": "^5.0.1", "css-loader": "^0.28.0", "cssnano": "^3.10.0", + "cypress": "^1.1.1", "eslint": "^4.8.0", "eslint-config-airbnb-base": "^12.0.1", "eslint-plugin-html": "^3.2.2", diff --git a/src/components/Authentication/LoginRegister.vue b/src/components/Authentication/LoginRegister.vue index 79450da5..53c6a076 100644 --- a/src/components/Authentication/LoginRegister.vue +++ b/src/components/Authentication/LoginRegister.vue @@ -2,17 +2,17 @@ <div class="card" id="loginRegisterComponent"> <ul class="nav justify-content-around" id="pills-tab" role="tablist"> <li class="nav-item"> - <a class="nav-link active" id="pills-home-tab" data-toggle="pill" href="#pills-home" role="tab" aria-controls="pills-home" aria-expanded="true">Log In</a> + <a class="nav-link active" id="pills-login-tab" data-toggle="pill" href="#pills-login" role="tab" aria-controls="pills-login" aria-expanded="true">Log In</a> </li> <li class="nav-item"> - <a class="nav-link" id="pills-profile-tab" data-toggle="pill" href="#pills-profile" role="tab" aria-controls="pills-profile" aria-expanded="true">Register</a> + <a class="nav-link" id="pills-register-tab" data-toggle="pill" href="#pills-register" role="tab" aria-controls="pills-register" aria-expanded="true">Register</a> </li> </ul> <div class="tab-content" id="pills-tabContent"> - <div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab"> + <div class="tab-pane fade show active" id="pills-login" role="tabpanel" aria-labelledby="pills-login-tab"> <login></login> </div> - <div class="tab-pane fade" id="pills-profile" role="tabpanel" aria-labelledby="pills-profile-tab"> + <div class="tab-pane fade" id="pills-register" role="tabpanel" aria-labelledby="pills-register-tab"> <register></register> </div> </div> diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index a8ff822c..eebe536f 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -33,7 +33,7 @@ <span v-if="this.$route.path == '/releases/'+this.$route.params.id || this.$route.path == '/releases/'+this.$route.params.id+'/sprints' || this.$route.path == '/sprints/'+this.$route.params.id" - class="sidebar-icon"><i class=" fa fa-repeat"></i></span> + class="sidebar-icon"><i class="fa fa-angle-double-down"></i></span> <span class="sidebar-title"></span> </a> </router-link> diff --git a/src/components/Sprints/Burndown.vue b/src/components/Sprints/Burndown.vue new file mode 100644 index 00000000..a349b065 --- /dev/null +++ b/src/components/Sprints/Burndown.vue @@ -0,0 +1,143 @@ +<template> + <div> + <div class="row text-center"> + <div class="col align-self-center"> + <h4>Burndown da sprint</h4> + </div> + </div> + <div class="row"> + <div class="col-md-6 mx-auto"> + <svg width="700" height="270"> + <g class="x axis" id="xAxis" transform="translate(50, 230)"></g> + <g class="y axis" id="yAxis" transform="translate(50, 20)"></g> + <g transform="translate(50, 20)"> + <path v-bind:d="realLine" /> + <path class="cor" v-bind:d="idealLine" /> + </g> + </svg> + </div> + </div> + </div> +</template> + +<script> +import { mapState } from 'vuex'; +import * as d3 from 'd3'; +import { HTTP } from '../../http-common'; + +export default { + name: 'vue-line-chart', + data() { + return { + realLineData: [], + realLine: '', + idealLineData: [], + idealLine: '' + }; + }, + computed: { + ...mapState({ + token: state => state.auth.token, + }), + }, + mounted() { + this.getBurndown(); + }, + methods: { + getBurndown() { + const headers = { Authorization: this.token }; + var parseTime = d3.timeParse("%Y-%m-%d") + HTTP.get(`sprints/${this.$route.params.id}/burndown`, { headers }) + .then((response) => { + this.realLineData = response.data; + this.realLineData.forEach((d) => d.x = parseTime(d.x)) + this.getIdealLineData(); + this.drawAxis(); + this.drawLine(); + }) + .catch((e) => { + this.errors.push(e); + }); + }, + getIdealLineData () { + var xMin = d3.min(this.realLineData, (d) => d.x); + var yMax = d3.max(this.realLineData, (d) => d.y); + var xMax = d3.max(this.realLineData, (d) => d.x); + var yMin = d3.min(this.realLineData, (d) => d.y); + var minPoint = {x: xMin, y: yMax}; + var maxPoint = {x: xMax, y: yMin}; + var idealLineData = [minPoint, maxPoint]; + this.idealLineData = idealLineData; + }, + getRealXRange() { + var xRange = d3.scaleTime().range([0, 500]) + .domain([d3.min(this.realLineData, (d) => d.x), + d3.max(this.realLineData, (d) => d.x)]); + return xRange; + }, + getRealYRange() { + var yRange = d3.scaleLinear().range([210, 0]) + .domain([d3.min(this.realLineData, (d) => d.y), + d3.max(this.realLineData, (d) => d.y)]) + return yRange; + }, + getIdealXRange() { + var xRange = d3.scaleTime().range([0, 500]) + .domain([d3.min(this.idealLineData, (d) => d.x), + d3.max(this.idealLineData, (d) => d.x)]); + return xRange; + }, + getIdealYRange() { + var yRange = d3.scaleLinear().range([210, 0]) + .domain([d3.min(this.idealLineData, (d) => d.y), + d3.max(this.idealLineData, (d) => d.y)]) + return yRange; + }, + getXAxis() { + var xAxis = d3.axisBottom(this.getRealXRange()).tickValues(this.realLineData.map((d) => d.x)).tickFormat(d3.timeFormat("%d-%m")) + return xAxis; + }, + + getYAxis() { + var yAxis = d3.axisLeft(this.getRealYRange()) + return yAxis; + }, + drawAxis() { + d3.select("#xAxis").call(this.getXAxis()) + d3.select("#yAxis").call(this.getYAxis()) + }, + drawLine() { + var xRange = this.getRealXRange(); + var yRange = this.getRealYRange(); + var path = d3.line() + .x((d) => xRange(d.x)) + .y((d) => yRange(d.y)); + this.realLine = path(this.realLineData) + + var idealXRange = this.getIdealXRange(); + var idealYRange = this.getIdealYRange(); + var idealPath = d3.line() + .x((d) => idealXRange(d.x)) + .y((d) => idealYRange(d.y)); + this.idealLine = idealPath(this.idealLineData) + } + }, +}; +</script> +<style scoped> + svg { + margin: 25px; + } + + path { + fill: none; + stroke: #76BF8A; + stroke-width: 3px; + } + .cor { + fill: none; + stroke: red; + stroke-width: 3px; + } + +</style> diff --git a/src/components/Sprints/Sprint.vue b/src/components/Sprints/Sprint.vue index 30cc7060..42c6e329 100644 --- a/src/components/Sprints/Sprint.vue +++ b/src/components/Sprints/Sprint.vue @@ -19,6 +19,13 @@ </ul> </div> <div class="col-md-6" align="end"> + <li class="list-inline-item"> + <router-link v-bind:to="'/sprints/'+$route.params.id+'/burndown'"> + <button type="button" class="btn btn-info btn-md falko-button"> + Burndown + </button> + </router-link> + </li> <li class="list-inline-item"> <add-retrospective v-on:retrospectiveCreated="setRetrospectiveAsCreated()" v-if="!isRetrospectiveCreated()"></add-retrospective> diff --git a/src/router/index.js b/src/router/index.js index 877df62a..1ec8cacb 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -26,6 +26,7 @@ import Retrospective from '@/components/Retrospective/Retrospective'; import Revision from '@/components/Revision/Revision'; import Gpa from '@/components/Gpa'; import Issues from '@/components/Issues/Issues'; +import Burndown from '@/components/Sprints/Burndown'; Vue.use(Router); @@ -163,6 +164,11 @@ const router = new Router({ name: 'Retrospective', component: Retrospective, }, + { + path: '/sprints/:id/burndown', + name: 'Burndown', + component: Burndown, + }, { path: '/revisions/:id', name: 'Revision',