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 inside the button,
+ // but the 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 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 element and set its src to the dataUrl
+ var img = Cypress.$(' ', {src: dataUrl})
+ // need to explicitly return cy here since we are initially returning
+ // the Cypress.Blob.imgSrcToDataURL promise to our test
+ // append the image
+ $div.append(img)
+
+ cy.get('.utility-blob img').click()
+ .should('have.attr', 'src', dataUrl)
+ })
+ })
+ })
+
+ it('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 @@
-
+
-
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 @@
+ class="sidebar-icon">
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 @@
+
+
+
+
+
Burndown da sprint
+
+
+
+
+
+
+
+
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 @@
+
+
+
+ Burndown
+
+
+
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',