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 @@ 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" > @@ -30,6 +31,7 @@
/src/$1", "^@@/(.*)$": "/styleguide/src/system/$1", "^~/(.*)$": "/$1" - } + }, + "modulePathIgnorePatterns": [ + "/styleguide" + ] }, "dependencies": { "@nuxtjs/apollo": "^4.0.0-rc3", @@ -50,10 +56,12 @@ }, "devDependencies": { "@vue/eslint-config-prettier": "^4.0.1", + "@vue/server-test-utils": "^1.0.0-beta.27", "@vue/test-utils": "^1.0.0-beta.27", "babel-eslint": "^10.0.1", "babel-jest": "^23.6.0", "babel-preset-env": "^1.7.0", + "cypress-cucumber-preprocessor": "^1.9.1", "eslint": "^5.10.0", "eslint-config-prettier": "^3.1.0", "eslint-loader": "^2.0.0", diff --git a/pages/admin.vue b/pages/admin.vue index 6ce9ca5a1..8d2286ae6 100644 --- a/pages/admin.vue +++ b/pages/admin.vue @@ -48,6 +48,10 @@ export default { name: 'Categories', path: `/admin/categories` }, + { + name: 'Tags', + path: `/admin/tags` + }, { name: 'Settings', path: `/admin/settings` diff --git a/pages/admin/categories.vue b/pages/admin/categories.vue index 0c7f18075..e09db79c0 100644 --- a/pages/admin/categories.vue +++ b/pages/admin/categories.vue @@ -1,7 +1,46 @@ + + diff --git a/pages/admin/tags.vue b/pages/admin/tags.vue new file mode 100644 index 000000000..a0866fb8e --- /dev/null +++ b/pages/admin/tags.vue @@ -0,0 +1,51 @@ + + + diff --git a/pages/index.vue b/pages/index.vue index 77e69c780..ca4ebc03c 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -107,6 +107,7 @@ export default { categories { id name + icon } shoutedCount } diff --git a/pages/post/_slug/index.vue b/pages/post/_slug/index.vue index b230f1ae4..949263e95 100644 --- a/pages/post/_slug/index.vue +++ b/pages/post/_slug/index.vue @@ -23,14 +23,22 @@ :post-id="post.id" /> -
+   + +