diff --git a/.travis.yml b/.travis.yml
index 12b309bae..d235af859 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,13 +20,15 @@ before_install:
install:
- docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT -t humanconnection/nitro-web .
- docker-compose -f docker-compose.yml up -d
- - git clone --depth=50 https://github.com/Human-Connection/Nitro-Backend.git ../Nitro-Backend
- - git --work-tree=../Nitro-Backend checkout $TRAVIS_BRANCH || echo "Branch \`$TRAVIS_BRANCH\` does not exist, falling back to \`master\`"
+ - git clone https://github.com/Human-Connection/Nitro-Backend.git ../Nitro-Backend
+ - git -C "../Nitro-Backend" checkout $TRAVIS_BRANCH || echo "Branch \`$TRAVIS_BRANCH\` does not exist, falling back to \`master\`"
- docker-compose -f ../Nitro-Backend/docker-compose.yml up -d
- yarn global add cypress wait-on
+ - yarn add cypress-cucumber-preprocessor
script:
- docker-compose exec webapp yarn run lint
+ - docker-compose exec webapp yarn run test
- docker-compose -f ../Nitro-Backend/docker-compose.yml exec backend yarn run db:seed > /dev/null
- wait-on http://localhost:3000
- cypress run --record --key $CYPRESS_TOKEN
diff --git a/assets/styles/main.scss b/assets/styles/main.scss
index 7b8c5c2e4..e9254c4b9 100644
--- a/assets/styles/main.scss
+++ b/assets/styles/main.scss
@@ -118,3 +118,11 @@ blockquote {
margin-top: 0;
}
}
+
+hr {
+ border: 0;
+ width: 100%;
+ color: $color-neutral-80;
+ background-color: $color-neutral-80;
+ height: 1px !important;
+}
diff --git a/components/PostCard.vue b/components/PostCard.vue
index 235763a60..450e69959 100644
--- a/components/PostCard.vue
+++ b/components/PostCard.vue
@@ -31,13 +31,23 @@
-
- {{ post.shoutedCount }}
-
-
-
- {{ post.commentsCount }}
-
+
+
+
+
+
+ {{ post.shoutedCount }}
+
+
+
+ {{ post.commentsCount }}
+
+
diff --git a/components/ShoutButton.vue b/components/ShoutButton.vue
index 9f00a6566..075006b2e 100644
--- a/components/ShoutButton.vue
+++ b/components/ShoutButton.vue
@@ -7,7 +7,7 @@
:disabled="disabled || loading"
danger
size="x-large"
- icon="heart"
+ icon="bullhorn"
@click="shout"
/>
diff --git a/cypress.json b/cypress.json
index 1f453389b..af4800ea3 100644
--- a/cypress.json
+++ b/cypress.json
@@ -1,3 +1,4 @@
{
- "projectId": "qa7fe2"
+ "projectId": "qa7fe2",
+ "ignoreTestFiles": "*.js"
}
diff --git a/cypress/integration/Login.feature b/cypress/integration/Login.feature
new file mode 100644
index 000000000..a7a47ddd5
--- /dev/null
+++ b/cypress/integration/Login.feature
@@ -0,0 +1,25 @@
+Feature: Authentication
+ As a database administrator
+ I want users to sign in
+ In order to attribute posts and other contributions to their authors
+
+ Background:
+ Given my account has the following details:
+ | name | email | password |
+ | Peter Lustig | admin@example.org | 1234 |
+
+ Scenario: Log in
+ When I visit the "/login" page
+ And I fill in my email and password combination and click submit
+ Then I can click on my profile picture in the top right corner
+ And I can see my name "Peter Lustig" in the dropdown menu
+
+ Scenario: Refresh and stay logged in
+ Given I am logged in
+ When I refresh the page
+ Then I am still logged in
+
+ Scenario: Log out
+ Given I am logged in
+ When I log out through the menu in the top right corner
+ Then I see the login screen again
diff --git a/cypress/integration/TagsAndCategories.feature b/cypress/integration/TagsAndCategories.feature
new file mode 100644
index 000000000..f9509e858
--- /dev/null
+++ b/cypress/integration/TagsAndCategories.feature
@@ -0,0 +1,41 @@
+Feature: Tags and Categories
+ As a database administrator
+ I would like to see a summary of all tags and categories and their usage
+ In order to be able to decide which tags and categories are popular or not
+
+ The currently deployed application, codename "Alpha", distinguishes between
+ categories and tags. Each post can have a number of categories and/or tags.
+ A few categories are required for each post, tags are completely optional.
+ Both help to find relevant posts in the database, e.g. users can filter for
+ categories.
+
+ If administrators summary of all tags and categories and how often they are
+ used, they learn what new category might be convenient for users, e.g. by
+ looking at the popularity of a tag.
+
+ Background:
+ Given we have a selection of tags and categories as well as posts
+ And my user account has the role "administrator"
+ Given I am logged in
+
+ Scenario: See an overview of categories
+ When I navigate to the administration dashboard
+ And I click on "Categories"
+ Then I can see a list of categories ordered by post count:
+ | Icon | Name | Post Count |
+ | | Just For Fun | 5 |
+ | | Happyness & Values | 2 |
+ | | Health & Wellbeing | 1 |
+
+ Scenario: See an overview of tags
+ When I navigate to the administration dashboard
+ And I click on "Tags"
+ Then I can see a list of tags ordered by user and post count:
+ | # | Name | Nutzer | Beiträge |
+ | 1 | Naturschutz | 2 | 2 |
+ | 2 | Freiheit | 2 | 2 |
+ | 3 | Umwelt | 1 | 1 |
+ | 4 | Demokratie | 1 | 1 |
+
+
+
diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js
new file mode 100644
index 000000000..f6bb9f2b0
--- /dev/null
+++ b/cypress/integration/common/steps.js
@@ -0,0 +1,112 @@
+import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'
+
+const baseUrl = 'http://localhost:3000'
+const username = 'Peter Lustig'
+
+const login = (email, password) => {
+ cy.visit(`${baseUrl}/login`)
+ cy.get('input[name=email]')
+ .trigger('focus')
+ .type(email)
+ cy.get('input[name=password]')
+ .trigger('focus')
+ .type(password)
+ cy.get('button[name=submit]')
+ .as('submitButton')
+ .click()
+ cy.location('pathname').should('eq', '/') // we're in!
+}
+
+const logout = () => {
+ cy.visit(`${baseUrl}/logout`)
+ cy.location('pathname').should('contain', '/login') // we're out
+}
+
+Given('I am logged in', () => {
+ login('admin@example.org', 1234)
+})
+
+Given('we have a selection of tags and categories as well as posts', () => {
+ // TODO: use db factories instead of seed data
+})
+
+Given('my account has the following details:', (table) => {
+ // TODO: use db factories instead of seed data
+})
+
+Given('my user account has the role {string}', (role) => {
+ // TODO: use db factories instead of seed data
+})
+
+When('I log out', logout)
+
+When(`I visit the {string} page`, route => {
+ cy.visit(`${baseUrl}/${route}`)
+})
+
+When('I fill in my email and password combination and click submit', () => {
+ login('admin@example.org', 1234)
+})
+
+When('I refresh the page', () => {
+ cy.reload()
+})
+
+When('I log out through the menu in the top right corner', () => {
+ cy.get('.avatar-menu').click()
+ cy.get('.avatar-menu-popover')
+ .find('a')
+ .contains('Logout')
+ .click()
+})
+
+Then('I can click on my profile picture in the top right corner', () => {
+ cy.get('.avatar-menu').click()
+})
+
+Then('I can see my name {string} in the dropdown menu', () => {
+ cy.get('.avatar-menu-popover').should('contain', username)
+})
+
+Then('I see the login screen again', () => {
+ cy.location('pathname').should('contain', '/login')
+ cy.contains(
+ 'Wenn Du ein Konto bei Human Connection hast, melde Dich bitte hier an.'
+ )
+})
+
+Then('I am still logged in', () => {
+ cy.get('.avatar-menu').click()
+ cy.get('.avatar-menu-popover').contains(username)
+})
+
+When('I navigate to the administration dashboard', () => {
+ cy.get('.avatar-menu').click()
+ cy.get('a').contains('Systemverwaltung').click()
+})
+
+When(`I click on {string}`, (linkOrButton) => {
+ cy.contains(linkOrButton).click()
+})
+
+Then('I can see a list of categories ordered by post count:', (table) => {
+ // TODO: match the table in the feature with the html table
+ cy.get('thead').find('tr th').should('have.length', 3)
+ const last_column = cy.get('tbody').find('tr td:last-child').then((last_column) => {
+ cy.wrap(last_column)
+ const values = last_column.map((i, td) => parseInt(td.textContent)).toArray()
+ const ordered_descending = values.slice(0).sort((a,b) => b - a)
+ return cy.wrap(values).should('deep.eq', ordered_descending)
+ })
+})
+
+Then('I can see a list of tags ordered by user and post count:', (table) => {
+ // TODO: match the table in the feature with the html table
+ cy.get('thead').find('tr th').should('have.length', 4)
+ const last_column = cy.get('tbody').find('tr td:last-child').then((last_column) => {
+ cy.wrap(last_column)
+ const values = last_column.map((i, td) => parseInt(td.textContent)).toArray()
+ const ordered_descending = values.slice(0).sort((a,b) => b - a)
+ return cy.wrap(values).should('deep.eq', ordered_descending)
+ })
+})
diff --git a/cypress/integration/login/login.spec.js b/cypress/integration/login/login.spec.js
deleted file mode 100644
index 33054bb7d..000000000
--- a/cypress/integration/login/login.spec.js
+++ /dev/null
@@ -1,73 +0,0 @@
-///
-
-const loginTestUser = function () {
- // Visiting our app before each test removes any state build up from
- cy.visit('http://localhost:3000/')
- .get('.layout-blank')
- .should('be.visible')
-
- cy.location('pathname')
- .should('contain', '/login')
-
- cy.get('input[name=email]')
- .as('inputEmail')
- .should('be.empty')
- .and('have.attr', 'placeholder', 'Deine E-Mail')
- .trigger('focus')
- .type('user@example.org')
-
- cy.get('input[name=password]')
- .as('inputPassword')
- .should('be.empty')
- // .and('have.attr', 'placeholder', 'Dein Passwort')
- .trigger('focus')
- .type('1234')
-
- cy.get('button[name=submit]')
- .as('submitButton')
- .should('be.visible')
- .and('not.be.disabled')
- .click()
-
- cy.get('@submitButton')
- .should('be.disabled')
- // .next('.snackbar')
-
- cy.get('.layout-default')
-
- cy.location('pathname')
- .should('eq', '/')
-}
-
-const logout = function () {
- cy.visit('http://localhost:3000/logout')
-
- cy.location('pathname')
- .should('contain', '/login')
-
- cy.get('.layout-blank')
- .should('be.visible')
-}
-
-context('Authentication', () => {
- it('Login Testuser', loginTestUser)
-
- it('Login & Logout', function () {
- // login
- loginTestUser()
-
- // logout
- logout()
- })
-
- it('Still logged in after page-reload', function () {
- // login
- loginTestUser()
-
- cy.reload()
- .get('.layout-default')
-
- // logout
- // logout()
- })
-})
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
index f7ef6e6ac..4ec4addb3 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -11,7 +11,10 @@
// 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) => {
+const cucumber = require('cypress-cucumber-preprocessor').default
+module.exports = on => {
+ // (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
+ on('file:preprocessor', cucumber())
}
diff --git a/graphql/UserProfileQuery.js b/graphql/UserProfileQuery.js
index e49233f5d..6374fd228 100644
--- a/graphql/UserProfileQuery.js
+++ b/graphql/UserProfileQuery.js
@@ -7,13 +7,6 @@ export default gql(`
name
avatar
createdAt
- friendsCount
- friends {
- id
- name
- slug
- avatar
- }
badges {
id
key
@@ -63,6 +56,11 @@ export default gql(`
deleted
image
createdAt
+ categories {
+ id
+ name
+ icon
+ }
author {
id
avatar
diff --git a/layouts/default.vue b/layouts/default.vue
index 8a34f66ba..0bea14ac2 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -19,6 +19,7 @@
style="float: right"
>