diff --git a/.gitignore b/.gitignore index edfc11914..f9e1f85f9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ node_modules # testing coverage +# Cypress +cypress/screenshots +cypress/videos + # production build diff --git a/.travis.yml b/.travis.yml index 31d394a58..ada9f32d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,14 @@ language: node_js node_js: - - "10" + - 10 env: matrix: - REACT=16.3 -sudo: false +cache: + directories: + - ~/.npm + - ~/.cache +install: + - npm ci script: - - npm test -after_success: - - npm run coverage + - npm test \ No newline at end of file diff --git a/cypress.json b/cypress.json new file mode 100644 index 000000000..cf7e5fe3f --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "baseUrl": "http://localhost:4100" +} \ No newline at end of file diff --git a/cypress/fixtures/articles.json b/cypress/fixtures/articles.json new file mode 100644 index 000000000..81735e9ae --- /dev/null +++ b/cypress/fixtures/articles.json @@ -0,0 +1,175 @@ +{ + "articles": [ + { + "slug": "how-to-train-your-dragon", + "title": "How to train your dragon", + "description": "Ever wonder how?", + "body": "It takes a Jacobian", + "tagList": ["dragons", "training"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + }, + { + "slug": "eternal-return-law-ubermensch", + "title": "Eternal-return law ubermensch", + "description": "Eternal-return law ubermensch decrepit holiest philosophy right merciful victorious philosophy ascetic mountains", + "body": "Eternal-return law ubermensch decrepit holiest philosophy right merciful victorious philosophy ascetic mountains. Gains holiest truth zarathustra victorious evil mountains. Value abstract holiest battle ultimate christianity sexuality justice faith faith christian insofar war. Ocean play suicide holiest burying truth love derive will philosophy love salvation contradict. Hope ascetic decrepit eternal-return ultimate pinnacle ultimate deceptions.", + "tagList": ["philosophy", "ubermensch"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": true, + "favoritesCount": 0, + "author": { + "username": "nietzsche", + "bio": "Contradict strong faith victorious merciful", + "image": "http://nietzsche-ipsum.com/nietzsche.jpg", + "following": false + } + }, + { + "slug": "how-to-train-your-dragon-2", + "title": "How to train your dragon 2", + "description": "So toothless", + "body": "It a dragon", + "tagList": ["dragons", "training"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + }, + { + "slug": "transvaluation-reason-revaluation-zarathustra", + "title": "Transvaluation reason revaluation zarathustra", + "description": "Transvaluation reason revaluation zarathustra contradict endless passion evil chaos ocean superiority decieve sea", + "body": "Transvaluation reason revaluation zarathustra contradict endless passion evil chaos ocean superiority decieve sea. Oneself of law god hatred oneself transvaluation marvelous self sexuality. Snare law enlightenment joy madness sea horror war ultimate war noble zarathustra victorious pinnacle. Salvation grandeur morality self sexuality ultimate overcome depths ocean insofar. Horror dead derive pious christian inexpedient reason decrepit ultimate will. Love hatred play oneself burying dead faith war superiority deceptions contradict. Ultimate value ubermensch reason insofar.", + "tagList": ["morality", "zarathustra", "philosophy"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "nietzsche", + "bio": "Contradict strong faith victorious merciful", + "image": "http://nietzsche-ipsum.com/nietzsche.jpg", + "following": false + } + }, + { + "slug": "webtwo-ipsum-unigo-udemy", + "title": "Webtwo ipsum unigo udemy", + "description": "Webtwo ipsum unigo udemy meevee udemy yammer convore voki, balihoo ebay zlio jumo", + "body": "Webtwo ipsum unigo udemy meevee udemy yammer convore voki, balihoo ebay zlio jumo. Dopplr kno jaiku, boxbe. Akismet zillow yuntaa zoodles meebo shopify, napster udemy eskobo ifttt. Wakoopa zoosk prezi blippy, ebay wesabe. Edmodo yammer orkut vimeo zoho, blippy yoono sclipo. Ning nuvvo loopt airbnb, knewton wikia akismet movity, jaiku mog. Spotify jajah jaiku zinch tumblr ngmoco meebo, mzinga oovoo jajah tumblr. Dropio heroku zimbra trulia wufoo joukuu trulia etsy doostang, zillow octopart imvu revver divvyshot babblely flickr.", + "tagList": [], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "jesus.mcdonalid47", + "bio": "Web 2.0 startup names, just for the heck of it", + "image": "https://randomuser.me/api/portraits/men/92.jpg", + "following": false + } + }, + { + "slug": "hipmunk-zillow-kazaa-gsnap-twitter-squidoo-cuil", + "title": "Hipmunk zillow kazaa gsnap twitter squidoo cuil", + "description": "Hipmunk zillow kazaa gsnap twitter squidoo cuil, scribd greplin babblely stypi hulu", + "body": "Hipmunk zillow kazaa gsnap twitter squidoo cuil, scribd greplin babblely stypi hulu. Prezi oovoo lanyrd yoono qeyno zillow, meevee foodzie doostang. Handango skype wakoopa yammer rovio waze, bitly spock wesabe kiko. Zooomr zoho empressr jibjab sclipo meebo nuvvo, ngmoco elgg weebly squidoo knewton. Chumby jabber bubbli twitter yuntaa doostang dopplr, oooj qeyno disqus udemy blyve. mzinga trulia insala. Blekko wufoo omgpop meebo jiglu, tumblr babblely voki. prezi bebo. Kaboodle imvu lala omgpop skype, shopify chumby. Edmodo oooj sifteo joost zoosk edmodo, flickr bebo voxy. Joyent oooj flickr chartly edmodo ngmoco, loopt wesabe nuvvo kazaa. Cuil jaiku tivo wakoopa heekya napster ifttt, twitter tivo kiko ideeli napster. Stypi sococo mozy, convore.", + "tagList": [], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "jesus.mcdonalid47", + "bio": "Web 2.0 startup names, just for the heck of it", + "image": "https://randomuser.me/api/portraits/men/92.jpg", + "following": false + } + }, + { + "slug": "dead-selfish-moral-reason", + "title": "Dead selfish moral reason", + "description": "Dead selfish moral reason christianity chaos endless strong snare", + "body": "Dead selfish moral reason christianity chaos endless strong snare. Joy grandeur selfish eternal-return christian dead. Virtues free hope passion hatred justice war good battle pinnacle deceptions god spirit pinnacle. Eternal-return sexuality faith selfish law ideal ideal convictions sea. Faith law derive revaluation prejudice fearful good ultimate faithful sexuality law. Suicide love contradict against good zarathustra mountains society salvation prejudice hope ideal reason. Intentions endless holiest law moral contradict self holiest morality pious merciful gains.", + "tagList": ["moral", "selfish", "philosophy"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "nietzsche", + "bio": "Contradict strong faith victorious merciful", + "image": "http://nietzsche-ipsum.com/nietzsche.jpg", + "following": false + } + }, + { + "slug": "i-just-told-you-you-ve-killed-me", + "title": "I just told you! You've killed me!", + "description": "I never loved you. Is that a cooking show?", + "body": "I love this planet! I've got wealth, fame, and access to the depths of sleaze that those things bring. I usually try to keep my sadness pent up inside where it can fester quietly as a mental illness. Oh, how I wish I could believe or understand that! There's only one reasonable course of action now: kill Flexo!\n\nSpare me your space age technobabble, Attila the Hun! My fellow Earthicans, as I have explained in my book 'Earth in the Balance'', and the much more popular ''Harry Potter and the Balance of Earth', we need to defend our planet against pollution. Also dark wizards.", + "tagList": ["planet", "technobabble"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "bender", + "bio": "I'm sorry, guys. I never meant to hurt you. Just to destroy everything you ever believed in", + "image": "http://www.unboundworlds.com/wp-content/uploads/2012/03/bender2.jpg", + "following": false + } + }, + { + "slug": "what-s-with-you-kids", + "title": "What's with you kids?", + "description": "Every other day it's food, food, food. Alright, I'll get you some stupid food.", + "body": "When I was first asked to make a film about my nephew, Hubert Farnsworth, I thought \"Why should I?\" Then later, Leela made the film. But if I did make it, you can bet there would have been more topless women on motorcycles. Roll film!\n\nIt may comfort you to know that Fry's death took only fifteen seconds, yet the pain was so intense, that it felt to him like fifteen years. And it goes without saying, it caused him to empty his bowels. You're going back for the Countess, aren't you?", + "tagList": ["planet", "motorcycles"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "bender", + "bio": "I'm sorry, guys. I never meant to hurt you. Just to destroy everything you ever believed in", + "image": "http://www.unboundworlds.com/wp-content/uploads/2012/03/bender2.jpg", + "following": false + } + }, + { + "slug": "well-mercia-s-a-temperate-zone", + "title": "Well, Mercia's a temperate zone!", + "description": "What a strange person. Why do you think that she is a witch? We want a shrubbery!! Burn her anyway!", + "body": "Why do you think that she is a witch? We want a shrubbery!! Burn her! A newt? Why do you think that she is a witch?\n\nI'm not a witch. And the hat. She's a witch! Be quiet! Burn her!\n\nI have to push the pram a lot. The swallow may fly south with the sun, and the house martin or the plover may seek warmer climes in winter, yet these are not strangers to our land. We found them. And this isn't my nose. This is a false one.\n\nWell, what do you want? Bloody Peasant! Look, my liege! Found them? In Mercia?! The coconut's tropical! Burn her anyway! Ah, now we see the violence inherent in the system!\n\nWhat a strange person. I'm not a witch. Why do you think that she is a witch? Knights of Ni, we are but simple travelers who seek the enchanter who lives beyond these woods. We want a shrubbery!!\n\nListen. Strange women lying in ponds distributing swords is no basis for a system of government. Supreme executive power derives from a mandate from the masses, not from some farcical aquatic ceremony. Shut up!", + "tagList": ["mercia", "witch"], + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:48:35.824Z", + "favorited": false, + "favoritesCount": 0, + "author": { + "username": "monty.python", + "bio": "You can't expect to wield supreme power just 'cause some watery tart threw a sword at you!", + "image": "https://images-na.ssl-images-amazon.com/images/I/710PzQnaomL._SY445_.jpg", + "following": false + } + } + ], + "articlesCount": 20 +} \ No newline at end of file diff --git a/cypress/fixtures/authentication.json b/cypress/fixtures/authentication.json new file mode 100644 index 000000000..c90df0277 --- /dev/null +++ b/cypress/fixtures/authentication.json @@ -0,0 +1,40 @@ +{ + "jake": { + "email": "jake@jake.jake", + "token": "1200cf8ad3.28a60559cf.5e7c5f46ee", + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg" + }, + "nietzsche": { + "email": "nietzsche@nietzsche-ipsum.com", + "token": "40a8a36c43.da3a787b87.0ebcb2d769", + "username": "nietzsche", + "bio": "Contradict strong faith victorious merciful", + "image": "http://nietzsche-ipsum.com/nietzsche.jpg" + }, + "jesus.mcdonalid47": { + "email": "jesus.mcdonalid47@example.com", + "token": "d3f98b5a2f.7b1324c6e4.7475c89e59", + "username": "jesus.mcdonalid47", + "bio": "Web 2.0 startup names, just for the heck of it", + "image": "https://randomuser.me/api/portraits/men/92.jpg", + "following": false + }, + "bender": { + "email": "bender@fillerama.io", + "token": "71b475cbc8.23152cb82e.8aef5fc03e", + "username": "bender", + "bio": "I'm sorry, guys. I never meant to hurt you. Just to destroy everything you ever believed in", + "image": "http://www.unboundworlds.com/wp-content/uploads/2012/03/bender2.jpg", + "following": false + }, + "monty.python": { + "email": "monty.python@fillerama.io", + "token": "a347aca33d.1084fef2aa.341b81a3a6", + "username": "monty.python", + "bio": "You can't expect to wield supreme power just 'cause some watery tart threw a sword at you!", + "image": "https://images-na.ssl-images-amazon.com/images/I/710PzQnaomL._SY445_.jpg", + "following": false + } +} \ No newline at end of file diff --git a/cypress/fixtures/comments.json b/cypress/fixtures/comments.json new file mode 100644 index 000000000..adb7185a7 --- /dev/null +++ b/cypress/fixtures/comments.json @@ -0,0 +1,64 @@ +{ + "comments": [ + { + "id": 1, + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:22:56.637Z", + "body": "Wait a minute - you've been declared dead. You can't give orders around here.", + "author": { + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + }, + { + "id": 2, + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:22:56.637Z", + "body": "Wait a minute - you've been declared dead. You can't give orders around here.", + "author": { + "username": "johnjacob", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + }, + { + "id": 3, + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:22:56.637Z", + "body": "Besides, you look good in a dress.", + "author": { + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + }, + { + "id": 4, + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:22:56.637Z", + "body": "It's called 'taking advantage.' It's what gets you ahead in life. I care deeply for nature. Whoa, this guy's straight? What's Spanish for \"I know you speak English?\"", + "author": { + "username": "johnjacob", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + }, + { + "id": 5, + "createdAt": "2016-02-18T03:22:56.637Z", + "updatedAt": "2016-02-18T03:22:56.637Z", + "body": "There's only one man I've ever called a coward, and that's Brian Doyle Murray. No, what I'm calling you is a television actor.\n\nBad news. Andy Griffith turned us down. He didn't like his trailer. I'm half machine. I'm a monster.", + "author": { + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + } + } + ] +} \ No newline at end of file diff --git a/cypress/fixtures/profile.json b/cypress/fixtures/profile.json new file mode 100644 index 000000000..b6c355ca5 --- /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/profiles.json b/cypress/fixtures/profiles.json new file mode 100644 index 000000000..68b8ad5b8 --- /dev/null +++ b/cypress/fixtures/profiles.json @@ -0,0 +1,32 @@ +{ + "jake": { + "username": "jake", + "bio": "I work at statefarm", + "image": "https://i.stack.imgur.com/xHWG8.jpg", + "following": false + }, + "nietzsche": { + "username": "nietzsche", + "bio": "Contradict strong faith victorious merciful", + "image": "http://nietzsche-ipsum.com/nietzsche.jpg", + "following": false + }, + "jesus.mcdonalid47": { + "username": "jesus.mcdonalid47", + "bio": "Web 2.0 startup names, just for the heck of it", + "image": "https://randomuser.me/api/portraits/men/92.jpg", + "following": false + }, + "bender": { + "username": "bender", + "bio": "I'm sorry, guys. I never meant to hurt you. Just to destroy everything you ever believed in", + "image": "http://www.unboundworlds.com/wp-content/uploads/2012/03/bender2.jpg", + "following": false + }, + "monty.python": { + "username": "monty.python", + "bio": "You can't expect to wield supreme power just 'cause some watery tart threw a sword at you!", + "image": "https://images-na.ssl-images-amazon.com/images/I/710PzQnaomL._SY445_.jpg", + "following": false + } +} \ No newline at end of file diff --git a/cypress/fixtures/tags.json b/cypress/fixtures/tags.json new file mode 100644 index 000000000..180396a44 --- /dev/null +++ b/cypress/fixtures/tags.json @@ -0,0 +1,17 @@ +{ + "tags": [ + "dragons", + "mercia", + "moral", + "morality", + "motorcycles", + "philosophy", + "planet", + "selfish", + "technobabble", + "training", + "ubermensch", + "witch", + "zarathustra" + ] +} \ No newline at end of file diff --git a/cypress/fixtures/users.json b/cypress/fixtures/users.json new file mode 100644 index 000000000..79b699aa7 --- /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/homepage_spec.js b/cypress/integration/homepage_spec.js new file mode 100644 index 000000000..04b8a2575 --- /dev/null +++ b/cypress/integration/homepage_spec.js @@ -0,0 +1,192 @@ +describe('Homepage', () => { + beforeEach(() => { + cy.server(); + cy + .route( + /.*\/api\/articles(\?limit=(\d+)&offset=(\d+))?/, + 'fixture:articles.json' + ) + .as('loadArticles'); + cy.route(/.*\/api\/tags/, 'fixture:tags.json').as('loadTags'); + + cy.visit('/', { + // see https://github.com/cypress-io/cypress/issues/95#issuecomment-281273126 + onBeforeLoad(win) { + win.fetch = null; + } + }); + cy.wait(['@loadArticles', '@loadTags']); + }); + + it('Should the page title must be set', () => { + cy.title().should('include', 'Conduit'); + cy.screenshot(); + }); + + context('Query DOM', () => { + it('Should have a header navbar', () => { + cy.get('.navbar').as('navbar'); + + cy.get('@navbar').should('be.visible'); + cy.get('@navbar').within(() => { + cy.get('a').as('navbarButtons'); + + cy.get('@navbarButtons').its('length').should('be.gte', 4); + cy + .get('@navbarButtons') + .contains('conduit') + .should('have.attr', 'href', '/'); + cy + .get('@navbarButtons') + .contains('Home') + .should('have.attr', 'href', '/'); + cy + .get('@navbarButtons') + .contains('Sign in') + .should('have.attr', 'href', '/login'); + cy + .get('@navbarButtons') + .contains('Sign up') + .should('have.attr', 'href', '/register'); + }); + }); + + it('Should have a banner container', () => { + cy.get('.banner').as('banner'); + + cy.get('@banner').within(() => { + cy.get('h1').contains('conduit').should('be.visible'); + cy.get('p').should('be.visible'); + }); + }); + + it('Should have a main container with articles and tags', () => { + cy.get('.page').as('main'); + + cy.get('@main').within(() => { + cy.get('.feed-toggle').contains('Global Feed'); + cy + .get('.article-preview') + .as('articles') + .its('length') + .should('be.lte', 10); + + cy.get('@articles').each($el => { + cy.wrap($el).as('preview'); + cy.get('@preview').get('a > img'); + cy.get('@preview').get('.info > a, .info > span'); + cy + .get('@preview') + .get('button > i') + .should('have.class', 'ion-heart'); + cy.get('@preview').get('.preview-link').within(() => { + cy.get('h1, p, span, ul.tag-list'); + cy.get('span').contains('Read more...'); + }); + }); + + cy.get('.sidebar').within(() => { + cy.get('p').contains('Popular Tags'); + cy.get('.tag-list').its('length').should('be.gte', 0); + }); + + cy.get('.pagination').within(() => { + cy.get('.page-item').its('length').should('be.gt', 1); + }); + }); + }); + }); + + context('Navigation links', () => { + afterEach(() => { + cy.screenshot(); + cy.go('back'); + }); + + it('Should navigate to Login page', () => { + cy.get('a.nav-link').contains('Sign in').click(); + + cy.location('pathname').should('be.equal', '/login'); + cy.get('h1').contains('Sign In'); + }); + + it('Should navigate to Register page', () => { + cy.get('a.nav-link').contains('Sign up').click(); + + cy.location('pathname').should('be.equal', '/register'); + cy.get('h1').contains('Sign Up'); + }); + + it('Should navigate to first article', () => { + const slug = 'how-to-train-your-dragon'; + const title = 'How to train your dragon'; + + cy.getArticle(slug).as('articleLoaded'); + cy.route(/.*\/api\/articles\/[\w-]+/, '@articleLoaded').as('loadArticle'); + cy + .route(/.*\/api\/articles\/[\w-]+\/comments/, 'fixture:comments.json') + .as('loadComments'); + cy.get('.preview-link').eq(0).contains('Read more...').click(); + cy.wait(['@loadArticle', '@loadComments']); + + cy.location('pathname').should('match', /\/article\/[\w-]+/); + cy.get('h1').contains(title); + }); + + it('Should navigate to the next page', () => { + cy.get('.page-item').contains('2').as('nextPage'); + cy.get('@nextPage').click(); + cy.get('@nextPage').parent().should('have.class', 'active'); + cy + .get('@nextPage') + .parent() + .siblings(':not(.active)') + .its('length') + .should('be', 0); + }); + }); + + context('Filter articles by tag', () => { + const tag = 'dragons'; + + beforeEach(() => { + cy.getArticlesByTag(tag).as('articlesByTagLoaded'); + cy + .route( + /.*\/api\/articles\?tag=(\w+)(&limit=(\d+)&offset=(\d+))?/, + '@articlesByTagLoaded' + ) + .as('loadArticlesByTag'); + + cy.get('.tag-list a.tag-pill').contains(tag).click(); + }); + + it('Should not navigate away from homepage', () => { + cy.location('pathname').should('to.be', '/'); + }); + + it('Should show the tag as active', () => { + cy.get('.feed-toggle').contains(tag).should('have.class', 'active'); + }); + + it('Should filter articles list by tag', () => { + cy + .get('.article-preview') + .as('articles') + .its('length') + .should('be.lte', 10); + cy.get('@articles').each($el => { + cy.wrap($el).find('.tag-pill').should($pills => { + const tags = $pills.map((idx, el) => Cypress.$(el).text()); + + expect(tags.get()).to.include(tag); + }); + }); + }); + + afterEach(() => { + cy.screenshot(); + cy.reload(); + }); + }); +}); \ No newline at end of file diff --git a/cypress/integration/login_spec.js b/cypress/integration/login_spec.js new file mode 100644 index 000000000..9319d3725 --- /dev/null +++ b/cypress/integration/login_spec.js @@ -0,0 +1,70 @@ +describe('Sign in', () => { + beforeEach(() => { + cy.server(); + cy.visit('/login', { + // see https://github.com/cypress-io/cypress/issues/95#issuecomment-281273126 + onBeforeLoad(win) { + win.fetch = null; + } + }); + }); + + it('Should appear the page title', () => { + cy.get('.auth-page h1').contains('Sign In'); + }); + + it('Should fail with invalid credentials', () => { + cy.route({ + method: 'POST', + url: /.*\/api\/users/, + status: 422, + response: { + errors: { 'email or password': ['is invalid'] } + } + }).as('postUser'); + + cy.get('input[placeholder="Email"]').type('joe@doe.me'); + cy.get('input[placeholder="Password"]').type('123456{enter}'); + + cy.wait('@postUser'); + cy.get('.auth-page .error-messages > li'); + }); + + it('Should success with valid credentials', () => { + const registeredUser = { + username: 'joe_doe', + email: 'joe@doe.me', + password: 'P4$$w0rd' + }; + + cy.route('POST', /.*\/api\/users/, { + user: { + id: Date.now(), + email: registeredUser.email, + createdAt: new Date(), + updatedAt: new Date(), + username: registeredUser.username, + bio: null, + image: null, + token: 'jwt' + } + }).as('postUser'); + cy.route(/.*\/api\/tags/, 'fixture:tags.json').as('loadTags'); + cy.route(/.*\/api\/articles\/feed(\?limit=(\d+)&offset=(\d+))?/, { + articles: [], + articlesCount: 0 + }).as('loadFeed'); + + cy.get('input[placeholder="Email"]').type(registeredUser.email); + cy.get('input[placeholder="Password"]').type(`${registeredUser.password}{enter}`); + + cy.wait(['@postUser', '@loadTags', '@loadFeed']); + + cy.get(`a[href="/@${registeredUser.username}"]`).contains(registeredUser.username); + }); + + afterEach(() => { + cy.screenshot(); + cy.reload(); + }); +}); \ No newline at end of file diff --git a/cypress/integration/register_spec.js b/cypress/integration/register_spec.js new file mode 100644 index 000000000..b183d5649 --- /dev/null +++ b/cypress/integration/register_spec.js @@ -0,0 +1,80 @@ +describe('Registration', () => { + beforeEach(() => { + cy.server(); + cy.route(/.*\/api\/tags/, 'fixture:tags.json').as('loadTags'); + cy.route(/.*\/api\/articles\/feed(\?limit=(\d+)&offset=(\d+))?/, { + articles: [], + articlesCount: 0 + }).as('loadFeed'); + + cy.visit('/register', { + // see https://github.com/cypress-io/cypress/issues/95#issuecomment-281273126 + onBeforeLoad(win) { + win.fetch = null; + } + }); + }); + + it('Should appear the page title', () => { + cy.get('.auth-page h1').contains('Sign Up'); + }); + + it('Should register a new user', () => { + const newUser = { + username: 'joe_doe', + email: 'joe@doe.me', + password: 'P4$$w0rd' + }; + + cy.route('POST', /.*\/api\/users/, { + user: { + id: Date.now(), + email: newUser.email, + createdAt: new Date(), + updatedAt: new Date(), + username: newUser.username, + bio: null, + image: null, + token: 'jwt' + } + }).as('postUser'); + + cy.get('input[placeholder="Username"]').type(newUser.username); + cy.get('input[placeholder="Email"]').type(newUser.email); + cy.get('input[placeholder="Password"]').type(newUser.password); + cy.get('.container.page').screenshot(); + cy.get('.auth-page form').submit(); + + cy.wait(['@postUser', '@loadTags', '@loadFeed']); + + cy.get(`a[href="/@${newUser.username}"]`).contains(newUser.username); + }); + + it('Should show form errors', () => { + cy.route({ + method: 'POST', + url: /.*\/api\/users/, + status: 422, + response: { + errors: { + email: ["can't be blank"], + password: ["can't be blank"], + username: [ + "can't be blank", + 'is too short (minimum is 1 character)', + 'is too long (maximum is 20 characters)' + ] + } + } + }).as('postUser'); + + cy.get('.auth-page form').submit(); + cy.wait('@postUser'); + cy.get('.auth-page .error-messages'); + }); + + afterEach(() => { + cy.screenshot(); + cy.reload(); + }); +}); \ No newline at end of file diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 000000000..023156cd7 --- /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 +} \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 000000000..ff11bf1cf --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,53 @@ +// *********************************************** +// 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) => { ... }) +const articlesFx = require('../fixtures/articles.json'); + +Cypress.Commands.add('getArticles', (limit = 10, offset = 0) => { + if (articlesFx.articles.length > limit) { + offset = offset < 0 ? 0 : offset; + + return Object.assign(articlesFx, { + articles: articlesFx.articles.slice( + offset, + offset > 0 ? limit + offset : limit + ) + }); + } + + return articlesFx; +}); + +Cypress.Commands.add('getArticlesByTag', (tag, limit = 10, offset = 0) => + Object.assign(articlesFx, { + articles: articlesFx.articles.filter(article => + article.tagList.includes(tag) + ) + }) +); + +Cypress.Commands.add('getArticle', slug => ({ + article: articlesFx.articles.filter(article => article.slug === slug)[0] +})); \ No newline at end of file diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 000000000..708a72e29 --- /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') \ No newline at end of file diff --git a/package.json b/package.json index 9680bc6c6..e7622211a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "private": true, "devDependencies": { - "cross-env": "^5.1.4", + "cross-env": "^5.2.0", + "cypress": "^3.0.2", + "npm-run-all": "^4.1.2", "react-scripts": "1.1.1" }, "dependencies": { @@ -25,7 +27,9 @@ "scripts": { "start": "cross-env PORT=4100 react-scripts start", "build": "react-scripts build", - "test": "cross-env PORT=4100 react-scripts test --env=jsdom", + "test:e2e": "cypress run", + "test:unit": "cross-env PORT=4100 react-scripts test --env=jsdom", + "test": "npm-run-all -s test:unit -p -r start test:e2e", "eject": "react-scripts eject" } -} +} \ No newline at end of file