Merge pull request #4338 from Ocelot-Social-Community/cypress

🍰 Get Cypress Tests Running Again
This commit is contained in:
Ulf Gebhardt 2021-04-15 15:26:07 +02:00 committed by GitHub
commit 6773633282
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
169 changed files with 2182 additions and 2250 deletions

View File

@ -269,4 +269,74 @@ jobs:
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 52
token: ${{ github.token }}
token: ${{ github.token }}
##############################################################################
# JOB: FULLSTACK TESTS #######################################################
##############################################################################
fullstack_tests:
name: Fullstack tests
runs-on: ubuntu-latest
needs: [build_test_webapp, build_test_backend, build_test_neo4j]
env:
jobs: 8
strategy:
matrix:
# run copies of the current job in parallel
job: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
##########################################################################
- name: Checkout code
uses: actions/checkout@v2
##########################################################################
# DOWNLOAD DOCKER IMAGES #################################################
##########################################################################
- name: Download Docker Image (Neo4J)
uses: actions/download-artifact@v2
with:
name: docker-neo4j-image
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/neo4j.tar
- name: Download Docker Image (Backend)
uses: actions/download-artifact@v2
with:
name: docker-backend-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/backend.tar
- name: Download Docker Image (Webapp)
uses: actions/download-artifact@v2
with:
name: docker-webapp-test
path: /tmp
- name: Load Docker Image
run: docker load < /tmp/webapp.tar
##########################################################################
# FULLSTACK TESTS CYPRESS ################################################
##########################################################################
- name: webapp | copy env files webapp
run: cp webapp/.env.template webapp/.env
- name: backend | copy env files backend
run: cp backend/.env.template backend/.env
- name: backend | docker-compose
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps webapp neo4j backend
- name: cypress | Fullstack tests
run: |
yarn install
yarn run cypress:run --spec $(cypress/parallel-features.sh ${{ matrix.job }} ${{ env.jobs }} )
##########################################################################
# UPLOAD SCREENSHOTS & VIDEO #############################################
##########################################################################
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: cypress-screenshots
path: cypress/screenshots/
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: cypress-videos
path: cypress/videos/

View File

@ -105,12 +105,12 @@ Factory.define('user')
})
Factory.define('post')
.option('categoryIds', [])
/* .option('categoryIds', [])
.option('categories', ['categoryIds'], (categoryIds) => {
if (categoryIds.length) return Promise.all(categoryIds.map((id) => neode.find('Category', id)))
// there must be at least one category
return Promise.all([Factory.build('category')])
})
}) */
.option('tagIds', [])
.option('tags', ['tagIds'], (tagIds) => {
return Promise.all(tagIds.map((id) => neode.find('Tag', id)))
@ -147,16 +147,16 @@ Factory.define('post')
return language || 'en'
})
.after(async (buildObject, options) => {
const [post, author, image, categories, tags] = await Promise.all([
const [post, author, image, /* categories, */ tags] = await Promise.all([
neode.create('Post', buildObject),
options.author,
options.image,
options.categories,
// options.categories,
options.tags,
])
await Promise.all([
post.relateTo(author, 'author'),
Promise.all(categories.map((c) => c.relateTo(post, 'post'))),
// Promise.all(categories.map((c) => c.relateTo(post, 'post'))),
Promise.all(tags.map((t) => t.relateTo(post, 'post'))),
])
if (image) await post.relateTo(image, 'image')

View File

@ -348,7 +348,7 @@ export default {
undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'],
hasMany: {
tags: '-[:TAGGED]->(related:Tag)',
categories: '-[:CATEGORIZED]->(related:Category)',
// categories: '-[:CATEGORIZED]->(related:Category)',
comments: '<-[:COMMENTS]-(related:Comment)',
shoutedBy: '<-[:SHOUTED]-(related:User)',
emotions: '<-[related:EMOTED]',

View File

@ -147,7 +147,7 @@ describe('Post', () => {
})
})
it('by categories', async () => {
/* it('by categories', async () => {
const postQueryFilteredByCategories = gql`
query Post($filter: _PostFilter) {
Post(filter: $filter) {
@ -172,7 +172,7 @@ describe('Post', () => {
await expect(
query({ query: postQueryFilteredByCategories, variables }),
).resolves.toMatchObject(expected)
})
}) */
describe('by emotions', () => {
const postQueryFilteredByEmotions = gql`
@ -323,14 +323,8 @@ describe('CreatePost', () => {
describe('UpdatePost', () => {
let author, newlyCreatedPost
const updatePostMutation = gql`
mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID], $image: ImageInput) {
UpdatePost(
id: $id
title: $title
content: $content
categoryIds: $categoryIds
image: $image
) {
mutation($id: ID!, $title: String!, $content: String!, $image: ImageInput) {
UpdatePost(id: $id, title: $title, content: $content, image: $image) {
id
title
content
@ -338,9 +332,6 @@ describe('UpdatePost', () => {
name
slug
}
categories {
id
}
createdAt
updatedAt
}
@ -428,7 +419,7 @@ describe('UpdatePost', () => {
expect(newlyCreatedPost.updatedAt).not.toEqual(UpdatePost.updatedAt)
})
describe('no new category ids provided for update', () => {
/* describe('no new category ids provided for update', () => {
it('resolves and keeps current categories', async () => {
const expected = {
data: {
@ -443,9 +434,9 @@ describe('UpdatePost', () => {
expected,
)
})
})
}) */
describe('given category ids', () => {
/* describe('given category ids', () => {
beforeEach(() => {
variables = { ...variables, categoryIds: ['cat27'] }
})
@ -464,7 +455,7 @@ describe('UpdatePost', () => {
expected,
)
})
})
}) */
describe('params.image', () => {
describe('is object', () => {

View File

@ -13,16 +13,13 @@ $ docker-compose up
## Setup without docker
First, you have to tell cypress how to connect to your local neo4j database
among other things. You can copy our template configuration and change the new
file according to your needs.
To start the services that are required for cypress testing manually. You basically need the whole setup to run:
To start the services that are required for cypress testing, run:
- backend
- webapp
- neo4j
```bash
# in the top level folder Ocelot-Social/
$ yarn cypress:setup
```
Navigate to the corresponding folders and start the services.
## Install cypress
@ -35,21 +32,11 @@ without docker, you would have to install cypress and its dependencies first:
$ yarn install
```
## Run cypress
After verifying that there are no errors with the servers starting, open another tab in your terminal and run the following command:
```bash
$ yarn cypress:run
```
![Console output after running cypress test](../.gitbook/assets/grafik%20%281%29.png)
### Open Interactive Test Console
If you are like me, you might want to see some visual output. The interactive cypress environment also helps at debugging your tests, you can even time travel between individual steps and see the exact state of the app.
The interactive cypress test console allows to run tests and have visual feedback on that. The interactive cypress environment also helps at debugging the tests, you can even time travel between individual steps and see the exact state of the app.
To use this feature, instead of `yarn cypress:run` you would run the following command:
To use this feature run:
```bash
$ yarn cypress:open
@ -57,7 +44,19 @@ $ yarn cypress:open
![Interactive Cypress Environment](../.gitbook/assets/grafik-1%20%281%29.png)
## Run cypress
To run cypress without the user interface:
```bash
$ yarn cypress:run
```
This is used to run cypress in CI or in console
![Console output after running cypress test](../.gitbook/assets/grafik%20%281%29.png)
## Write some Tests
Check out the Cypress documentation for further information on how to write tests:
[https://docs.cypress.io/guides/getting-started/writing-your-first-test.html\#Write-a-simple-test](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html#Write-a-simple-test)
[Write-a-simple-test](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html#Write-a-simple-test)

View File

@ -1,2 +0,0 @@
// please change also version in file "webapp/constants/terms-and-conditions-version.js"
export const VERSION = '0.0.4'

View File

@ -1,8 +1,10 @@
{
"projectId": "qa7fe2",
"ignoreTestFiles": "*.js",
"chromeWebSecurity": false,
"baseUrl": "http://localhost:3000",
"env": {
"RETRIES": 2
"retries": {
"runMode": 2,
"openMode": 0
}
}

View File

@ -0,0 +1,43 @@
Feature: Admin pins a post
As an admin
I want to pin a post so that it always appears at the top
In order to make sure all network users read it
e.g. notify people about security incidents, maintenance downtimes
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
| user | user@example.org | abcd | user | User-Chad | user | 0.0.4 |
| admin | admin@example.org | 1234 | admin | Admin-Man | admin | 0.0.4 |
Given the following "posts" are in the database:
| id | title | pinned | createdAt |
| p1 | Some other post | | 2020-01-21 |
| p2 | Houston we have a problem | x | 2020-01-20 |
| p3 | Yet another post | | 2020-01-19 |
Scenario: Pinned post always appears on the top of the newsfeed
When I am logged in as "user"
And I navigate to page "/"
Then the first post on the newsfeed has the title:
"""
Houston we have a problem
"""
And the post with title "Houston we have a problem" has a ribbon for pinned posts
Scenario: Ordinary users cannot pin a post
When I am logged in as "user"
And I navigate to page "/"
And I open the content menu of post "Yet another post"
Then there is no button to pin a post
Scenario: Admins are allowed to pin a post
When I am logged in as "admin"
And I navigate to page "/"
And I open the content menu of post "Yet another post"
And I click on "pin post"
Then I see a toaster with "Post pinned successfully"
And the first post on the newsfeed has the title:
"""
Yet another post
"""
And the post with title "Yet another post" has a ribbon for pinned posts

View File

@ -0,0 +1,7 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I open the content menu of post {string}", (title)=> {
cy.contains('.post-teaser', title)
.find('.content-menu .base-button')
.click()
})

View File

@ -0,0 +1,9 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the post with title {string} has a ribbon for pinned posts", (title) => {
cy.get(".post-teaser").contains(title)
.parent()
.parent()
.find(".ribbon.--pinned")
.should("contain", "Announcement")
})

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("there is no button to pin a post", () => {
cy.get("a.ds-menu-item-link")
.should('contain', "Report Post") // sanity check
.should('not.contain', "Pin post")
})

View File

@ -0,0 +1,31 @@
Feature: Admin tag overview
As a database administrator
I would like to see a overview of all tags and their usage
In order to be able to decide which tags are popular or not
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
| admin | admin@example.org | 1234 | admin | Admin-Man | admin | 0.0.4 |
| u1 | u1@example.org | 1234 | u1 | User1 | user | 0.0.4 |
| u2 | u2@example.org | 1234 | u2 | User2 | user | 0.0.4 |
| u3 | u3@example.org | 1234 | u3 | User3 | user | 0.0.4 |
And the following "tags" are in the database:
| id |
| Ecology |
| Nature |
| Democracy |
And the following "posts" are in the database:
| id | title | authorId | tagIds |
| p1 | P1 from U1 | u1 | Nature, Democracy |
| p2 | P2 from U2 | u2 | Ecology, Democracy |
| p3 | P3 from U3 | u3 | Nature, Democracy |
And I am logged in as "admin"
Scenario: See an overview of tags
When I navigate to page "/admin/hashtags"
Then I can see the following table:
| No. | Hashtags | Users | Posts |
| 1 | #Democracy | 3 | 3 |
| 2 | #Nature | 2 | 2 |
| 3 | #Ecology | 1 | 1 |

View File

@ -4,7 +4,7 @@ Feature: Internationalization
In order to be able to understand the interface
Background:
Given I am on the "login" page
Given I navigate to page "/login"
Scenario Outline: I select "<language>" in the language menu and see "<buttonLabel>"
When I select "<language>" in the language menu
@ -18,6 +18,6 @@ Feature: Internationalization
| English | Login |
Scenario: Keep preferred language after refresh
Given I previously switched the language to "Français"
When I select "Français" in the language menu
And I refresh the page
Then the whole user interface appears in "Français"

View File

@ -0,0 +1,5 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I see a button with the label {string}", label => {
cy.contains("button", label);
});

View File

@ -0,0 +1,8 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I select {string} in the language menu", language => {
cy.get(".locale-menu")
.click();
cy.contains(".locale-menu-popover a", language)
.click();
});

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
import locales from '../../../webapp/locales'
Then("the whole user interface appears in {string}", language => {
const { code } = locales.find((entry) => entry.name === language);
cy.get(`html[lang=${code}]`);
cy.getCookie("locale").should("have.property", "value", code);
});

View File

@ -0,0 +1,40 @@
Feature: Hide Posts
As a moderator
I would like to be able to hide posts from the public
to enforce our network's code of conduct and/or legal regulations
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
| user | user@example.org | abcd | user | User-Chad | user | 0.0.4 |
| moderator | moderator@example.org | 1234 | moderator | Mod-Man | moderator | 0.0.4 |
Given the following "posts" are in the database:
| id | title | deleted | disabled |
| p1 | This post should be visible | | |
| p2 | This post is disabled | | x |
| p3 | This post is deleted | x | |
Scenario: Disabled posts don't show up on the newsfeed as user
When I am logged in as "user"
And I navigate to page "/"
Then I should see only 1 posts on the newsfeed
And the first post on the newsfeed has the title:
"""
This post should be visible
"""
Scenario: Disabled posts show up on the newsfeed as moderator
When I am logged in as "moderator"
And I navigate to page "/"
Then I should see only 2 posts on the newsfeed
And the first post on the newsfeed has the title:
"""
This post is disabled
"""
Scenario: Visiting a disabled post's page should return 404
Given I am logged in as "user"
Then the page "/post/this-post-is-disabled" returns a 404 error with a message:
"""
This post could not be found
"""

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see only {int} posts on the newsfeed", posts => {
cy.get(".post-teaser")
.should("have.length", posts);
});

View File

@ -0,0 +1,14 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the page {string} returns a 404 error with a message:", (route, message) => {
cy.request({
url: route,
failOnStatusCode: false
})
.its("status")
.should("eq", 404);
cy.visit(route, {
failOnStatusCode: false
});
cy.get(".error-message").should("contain", message);
});

View File

@ -8,51 +8,47 @@ Feature: Report and Moderate
So I can look into it and decide what to do
Background:
Given we have the following user accounts:
| id | name |
| u67 | David Irving |
| annoying-user | I'm gonna mute Moderators and Admins HA HA HA |
Given we have the following posts in our database:
Given the following "users" are in the database:
| slug | email | password | id | name | role | termsAndConditionsAgreedVersion |
| user | user@example.org | abcd | user | User-Chad | user | 0.0.4 |
| moderator | moderator@example.org | 1234 | moderator | Mod-Man | moderator | 0.0.4 |
| annoying | annoying@example.org | 1234 | annoying-user | I'm gonna mute Moderators and Admins HA HA HA | user | 0.0.4 |
And the following "posts" are in the database:
| authorId | id | title | content |
| u67 | p1 | The Truth about the Holocaust | It never existed! |
| annoying-user | p1 | The Truth about the Holocaust | It never existed! |
| annoying-user | p2 | Fake news | This content is demonstratably infactual in some way |
Scenario Outline: Report a post from various pages
Given I am logged in with a "user" role
When I see David Irving's post on the <Page>
When I am logged in as "user"
And I navigate to page "<Page>"
And I click on "Report Post" from the content menu of the post
And I confirm the reporting dialog because it is a criminal act under German law:
"""
Do you really want to report the contribution "The Truth about the Holocaust"?
"""
Then I see a success message:
"""
Thanks for reporting!
"""
Then I see a toaster with "Thanks for reporting!"
Examples:
| Page |
| landing page |
| post page |
| Page |
| / |
| /post/p1 |
Scenario: Report user
Given I am logged in with a "user" role
And I see David Irving's post on the post page
Given I am logged in as "user"
And I navigate to page "/post/the-truth-about-the-holocaust"
When I click on the author
And I click on "Report User" from the content menu in the user info box
And I confirm the reporting dialog because he is a holocaust denier:
"""
Do you really want to report the user "David Irving"?
"""
Then I see a success message:
"""
Thanks for reporting!
Do you really want to report the user "I'm gonna mute Moderators and "?
"""
Then I see a toaster with "Thanks for reporting!"
Scenario: Review reported content
Given somebody reported the following posts:
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| p1.submitter@example.org | p1 | discrimination_etc | Offensive content |
And I am logged in with a "moderator" role
And I am logged in as "moderator"
And I navigate to page "/"
When I click on the avatar menu in the top right corner
And I click on "Moderation"
Then I see all the reported posts including the one from above
@ -62,7 +58,8 @@ Feature: Report and Moderate
Given somebody reported the following posts:
| submitterEmail | resourceId | reasonCategory | reasonDescription |
| p2.submitter@example.org | p2 | other | Offensive content |
And I am logged in with a "moderator" role
And I am logged in as "moderator"
And I navigate to page "/"
And there is an annoying user who has muted me
When I click on the avatar menu in the top right corner
And I click on "Moderation"
@ -70,6 +67,7 @@ Feature: Report and Moderate
And I can visit the post page
Scenario: Normal user can't see the moderation page
Given I am logged in with a "user" role
Given I am logged in as "user"
And I navigate to page "/"
When I click on the avatar menu in the top right corner
Then I can't see the moderation menu item

View File

@ -0,0 +1,11 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then(`I can't see the moderation menu item`, () => {
cy.get('.avatar-menu-popover')
.find('a[href="/settings"]', 'Settings')
.should('exist') // OK, the dropdown is actually open
cy.get('.avatar-menu-popover')
.find('a[href="/moderation"]', 'Moderation')
.should('not.exist')
})

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('I can visit the post page', () => {
cy.contains('Fake news').click()
cy.location('pathname').should('contain', '/post')
.get('.base-card .title').should('contain', 'Fake news')
})

View File

@ -0,0 +1,11 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When('I click on "Report Post" from the content menu of the post', () => {
cy.contains('.base-card', 'The Truth about the Holocaust')
.find('.content-menu .base-button')
.click({force: true})
cy.get('.popover .ds-menu-item-link')
.contains('Report Post')
.click()
})

View File

@ -0,0 +1,7 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When('I click on the author', () => {
cy.get('.user-teaser')
.click()
.url().should('include', '/profile/')
})

View File

@ -0,0 +1,5 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I click on the avatar menu in the top right corner", () => {
cy.get(".avatar-menu").click();
});

View File

@ -0,0 +1,16 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When(/^I confirm the reporting dialog .*:$/, message => {
cy.contains(message) // wait for element to become visible
cy.get('.ds-modal')
.within(() => {
cy.get('.ds-radio-option-label')
.first()
.click({
force: true
})
cy.get('button')
.contains('Report')
.click()
})
})

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('I see all the reported posts including from the user who muted me', () => {
cy.get('table tbody').within(() => {
cy.contains('tr', 'Fake news')
})
})

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('I see all the reported posts including the one from above', () => {
cy.get('table tbody').within(() => {
cy.contains('tr', 'The Truth about the Holocaust')
})
})

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('each list item links to the post page', () => {
cy.contains('The Truth about the Holocaust').click();
cy.location('pathname').should('contain', '/post')
})

View File

@ -0,0 +1,23 @@
import { Given } from "cypress-cucumber-preprocessor/steps";
import { gql } from '../../../backend/src/helpers/jest'
Given('somebody reported the following posts:', table => {
table.hashes().forEach(({ submitterEmail, resourceId, reasonCategory, reasonDescription }) => {
const submitter = {
email: submitterEmail,
password: '1234'
}
cy.factory()
.build('user', {}, submitter)
.authenticateAs(submitter)
.mutate(gql`mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) {
fileReport(resourceId: $resourceId, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription) {
reportId
}
}`, {
resourceId,
reasonCategory,
reasonDescription
})
})
})

View File

@ -0,0 +1,13 @@
Given("there is an annoying user who has muted me", () => {
cy.neode()
.first("User", {
role: 'moderator'
})
.then(mutedUser => {
cy.neode()
.first("User", {
id: 'user'
})
.relateTo(mutedUser, "muted");
});
});

View File

@ -0,0 +1,29 @@
Feature: Notification for a mention
As a user
I want to be notified if somebody mentions me in a post or comment
In order join conversations about or related to me
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| wolle-aus-hamburg | wolle@example.org | 1234 | wolle | Wolle aus Hamburg | 0.0.4 |
| matt-rider | matt@example.org | 4321 | matt | Matt Rider | 0.0.4 |
Scenario: Mention another user, re-login as this user and see notifications
Given I am logged in as "wolle-aus-hamburg"
And I navigate to page "/"
And I navigate to page "/post/create"
And I start to write a new post with the title "Hey Matt" beginning with:
"""
Big shout to our fellow contributor
"""
And mention "@matt-rider" in the text
And I click on "save button"
And I am logged in as "matt-rider"
And I navigate to page "/"
And see 1 unread notifications in the top menu
And open the notification menu and click on the first item
And I wait for 750 milliseconds
Then I am on page "/post/.*/hey-matt"
And the unread counter is removed
And the notification menu button links to the all notifications page

View File

@ -0,0 +1,8 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I start to write a new post with the title {string} beginning with:", (title, intro) => {
cy.get('input[name="title"]')
.type(title);
cy.get(".ProseMirror")
.type(intro);
});

View File

@ -0,0 +1,9 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("mention {string} in the text", mention => {
cy.get(".ProseMirror")
.type(" @");
cy.get(".suggestion-list__item")
.contains(mention)
.click();
});

View File

@ -0,0 +1,10 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("open the notification menu and click on the first item", () => {
cy.get(".notifications-menu")
.invoke('show')
.click(); // "invoke('show')" because of the delay for show the menu
cy.get(".notification .link")
.first()
.click({force: true});
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("see {int} unread notifications in the top menu", count => {
cy.get(".notifications-menu")
.should("contain", count);
});

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the notification menu button links to the all notifications page", () => {
cy.get(".notifications-menu")
.click();
cy.location("pathname")
.should("contain", "/notifications");
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the unread counter is removed", () => {
cy.get('.notifications-menu .counter-icon')
.should('not.exist');
});

View File

@ -0,0 +1,31 @@
Feature: Persistent Links
As a user
I want all links to carry permanent information that identifies the linked resource
In order to have persistent links even if a part of the URL might change
| | Modifiable | Referenceable | Unique | Purpose |
| -- | -- | -- | -- | -- |
| ID | no | yes | yes | Identity, Traceability, Links |
| Slug | yes | yes | yes | @-Mentions, SEO-friendly URL |
| Name | yes | no | no | Search, self-description |
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| thehawk | hawk@example.org | abcd | MHNqce98y1 | Stephen Hawking | 0.0.4 |
| narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
And the following "posts" are in the database:
| id | title | slug |
| bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays |
And I am logged in as "narrator"
Scenario Outline: Link with healable information is valid and gets auto-completed
When I navigate to page "<url>"
Then I am on page "<redirectUrl>"
Examples:
| url | redirectUrl | reason |
| /profile/thehawk | /profile/MHNqce98y1/thehawk | Identifiable user slug |
| /post/101-essays | /post/bWBjpkTKZp/101-essays | Identifiable post slug |
| /profile/MHNqce98y1 | /profile/MHNqce98y1/thehawk | Identifiable user ID |
| /post/bWBjpkTKZp | /post/bWBjpkTKZp/101-essays | Identifiable post ID |
| /profile/MHNqce98y1/stephen-hawking | /profile/MHNqce98y1/thehawk | Identifiable user ID takes precedence over slug |
| /post/bWBjpkTKZp/the-way-you-think | /post/bWBjpkTKZp/101-essays | Identifiable post ID takes precedence over slug |

View File

@ -0,0 +1,49 @@
Feature: Comments on post
As a user
I want to comment and see comments on contributions of others
To be able to express my thoughts and emotions about these, discuss, and add give further information.
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| peter-pan| peter@pan.com | abcd | id-of-peter-pan| Peter Pan | 0.0.4 |
| narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
And the following "posts" are in the database:
| id | title | slug | authorId |
| bWBjpkTKZp | 101 Essays that will change the way you think | 101-essays | id-of-peter-pan |
And the following "comments" are in the database:
| postId | content | authorId |
| bWBjpkTKZp | @peter-pan reply to me | id-of-peter-pan |
And I am logged in as "narrator"
Scenario: Comment creation
Given I navigate to page "/post/bWBjpkTKZp/101-essays"
And I comment the following:
"""
Ocelot.social rocks
"""
And I click on "comment button"
Then my comment should be successfully created
And I should see my comment
And the editor should be cleared
Scenario: View medium length comments
Given I navigate to page "/post/bWBjpkTKZp/101-essays"
And I type in a comment with 305 characters
And I click on "comment button"
Then my comment should be successfully created
And I should see the entirety of my comment
And the editor should be cleared
Scenario: View long comments
Given I navigate to page "/post/bWBjpkTKZp/101-essays"
And I type in a comment with 1205 characters
And I click on "comment button"
Then my comment should be successfully created
And I should see an abbreviated version of my comment
And the editor should be cleared
Scenario: Direct reply to Comment
Given I navigate to page "/post/bWBjpkTKZp/101-essays"
And I click on "reply button"
Then it should create a mention in the CommentForm

View File

@ -0,0 +1,7 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I comment the following:", async text => {
const comment = text.replace("\n", " ")
cy.task('pushValue', { name: 'lastComment', value: comment })
cy.get(".editor .ProseMirror").type(comment);
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see an abbreviated version of my comment", () => {
cy.get("article.comment-card")
.should("contain", "show more")
});

View File

@ -0,0 +1,13 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see my comment", () => {
cy.get("article.comment-card p")
.should("contain", "Ocelot.social rocks")
.get(".user-teaser span.slug")
.should("contain", "@peter-pan") // specific enough
.get(".user-avatar img")
.should("have.attr", "src")
.and("contain", 'https://') // some url
.get(".user-teaser > .info > .text")
.should("contain", "today at");
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see the entirety of my comment", () => {
cy.get("article.comment-card")
.should("not.contain", "show more")
});

View File

@ -0,0 +1,9 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I type in a comment with {int} characters", size => {
var c="";
for (var i = 0; i < size; i++) {
c += "c"
}
cy.get(".editor .ProseMirror").type(c);
});

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("it should create a mention in the CommentForm", () => {
cy.get(".ProseMirror a")
.should('have.class', 'mention')
.should('contain', '@peter-pan')
})

View File

@ -0,0 +1,5 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("my comment should be successfully created", () => {
cy.get(".iziToast-message").contains("Comment submitted!");
});

View File

@ -0,0 +1,5 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the editor should be cleared", () => {
cy.get(".ProseMirror p").should("have.class", "is-empty");
});

View File

@ -0,0 +1,24 @@
Feature: Create a post
As an logged in user
I would like to create a post
To say something to everyone in the community
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
And I am logged in as "narrator"
And I navigate to page "/"
Scenario: Create a post
When I click on "create post button"
Then I am on page "post/create"
When I choose "My first post" as the title
And I choose the following text as content:
"""
Ocelot.social is a free and open-source social network
for active citizenship.
"""
And I click on "save button"
Then I am on page "/post/.*/my-first-post"
And the post was saved successfully

View File

@ -0,0 +1,8 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I choose {string} as the title", async title => {
const lastPost = {}
lastPost.title = title.replace("\n", " ");
cy.task('pushValue', { name: 'lastPost', value: lastPost })
cy.get('input[name="title"]').type(lastPost.title);
});

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the post was saved successfully", async () => {
cy.task('getValue', 'lastPost').then(lastPost => {
cy.get(".base-card > .title").should("contain", lastPost.title);
cy.get(".content").should("contain", lastPost.content);
})
});

View File

@ -0,0 +1,66 @@
Feature: Upload/Delete images on posts
As a user
I would like to be able to add/delete an image to/from my Post
So that I can personalize my posts
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
And the following "posts" are in the database:
| authorId | id | title | content |
| narrator | p1 | Post to be updated | successfully updated |
And I am logged in as "narrator"
And I navigate to page "/"
Scenario: Create a Post with a Teaser Image
When I click on "create post button"
Then I wait for 750 milliseconds
Then I should be able to "add" a teaser image
And I add all required fields
And I click on "save button"
And I wait for 750 milliseconds
Then I am on page "/post/.*/new-post"
And I wait for 750 milliseconds
And the post was saved successfully with the "new" teaser image
Scenario: Update a Post to add an image
Given I navigate to page "/post/edit/p1"
Then I wait for 750 milliseconds
And I should be able to "change" a teaser image
And I click on "save button"
Then I see a toaster with "Saved!"
And I wait for 750 milliseconds
And I am on page "/post/.*/post-to-be-updated"
And I wait for 750 milliseconds
Then the post was saved successfully with the "updated" teaser image
Scenario: Add image, then add a different image
When I click on "create post button"
Then I wait for 750 milliseconds
Then I should be able to "add" a teaser image
And I should be able to "change" a teaser image
And the first image should not be displayed anymore
Scenario: Add image, then delete it
When I click on "create post button"
Then I wait for 750 milliseconds
Then I should be able to "add" a teaser image
Then I should be able to "remove" a teaser image
And I add all required fields
And I click on "save button"
And I wait for 750 milliseconds
Then I am on page "/post/.*/new-post"
And I wait for 750 milliseconds
And the "new" post was saved successfully without a teaser image
Scenario: Delete existing image
Given I navigate to page "/post/edit/p1"
Then I wait for 750 milliseconds
And my post has a teaser image
Then I should be able to "remove" a teaser image
And I click on "save button"
And I wait for 750 milliseconds
Then I am on page "/post/.*/post-to-be-updated"
And I wait for 750 milliseconds
And the "updated" post was saved successfully without a teaser image

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I add all required fields", () => {
cy.get('input[name="title"]')
.type('new post')
.get(".editor .ProseMirror")
.type('new post content')
})

View File

@ -0,0 +1,30 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should be able to {string} a teaser image", condition => {
// cy.reload()
switch(condition){
case 'change':
cy.get('.delete-image-button')
.click()
cy.fixture('humanconnection.png').as('postTeaserImage').then(function() {
cy.get("#postdropzone").upload(
{ fileContent: this.postTeaserImage, fileName: 'humanconnection.png', mimeType: "image/png" },
{ subjectType: "drag-n-drop", force: true }
).wait(750);
})
break;
case 'add':
cy.fixture('onourjourney.png').as('postTeaserImage').then(function() {
cy.get("#postdropzone").upload(
{ fileContent: this.postTeaserImage, fileName: 'onourjourney.png', mimeType: "image/png" },
{ subjectType: "drag-n-drop", force: true }
).wait(750);
})
break;
case 'remove':
cy.get('.delete-image-button')
.click()
break;
}
})

View File

@ -0,0 +1,7 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When('my post has a teaser image', () => {
cy.get('.contribution-form .image')
.should('exist')
.and('have.attr', 'src')
})

View File

@ -0,0 +1,9 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the first image should not be displayed anymore", () => {
cy.get(".hero-image")
.children()
.get('.hero-image > .image')
.should('have.length', 1)
.and('have.attr', 'src')
})

View File

@ -0,0 +1,11 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the post was saved successfully with the {string} teaser image", condition => {
cy.get(".base-card > .title")
.should("contain", condition === 'updated' ? 'to be updated' : 'new post')
.get(".content")
.should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
.get('.post-page img')
.should("have.attr", "src")
.and("contains", condition === 'updated' ? 'humanconnection' : 'onourjourney')
})

View File

@ -0,0 +1,12 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('the {string} post was saved successfully without a teaser image', condition => {
cy.get(".base-card > .title")
.should("contain", condition === 'updated' ? 'to be updated' : 'new post')
.get(".content")
.should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
.get('.post-page')
.should('exist')
.get('.hero-image > .image')
.should('not.exist')
})

View File

@ -0,0 +1,23 @@
Feature: See a post
As an logged in user
I would like to see a post
And to see the whole content of it
Background:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| peter-pan| peter@pan.com | abcd | id-of-peter-pan| Peter Pan | 0.0.4 |
| narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
And the following "posts" are in the database:
| id | title | slug | authorId | content |
| aBcDeFgHiJ | previously created post | previously-created-post | id-of-peter-pan | with some content |
And I am logged in as "narrator"
Scenario: See a post on the newsfeed
When I navigate to page "/"
Then the post shows up on the newsfeed at position 1
Scenario: Navigate to the Post Page
When I navigate to page "/"
And I click on "the first post"
Then I am on page "/post/.*"

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the post shows up on the newsfeed at position {int}", index => {
const selector = `.post-teaser:nth-child(${index}) > .base-card`;
cy.get(selector).should("contain", 'previously created post');
cy.get(selector).should("contain", 'with some content');
});

View File

@ -4,20 +4,21 @@ Feature: Search
In order to find related content
Background:
Given I have a user account
And we have the following posts in our database:
Given the following "users" are in the database:
| slug | email | password | id | name | termsAndConditionsAgreedVersion |
| narrator | narrator@example.org | 1234 | narrator | Nathan Narrator | 0.0.4 |
| search-for-me | u1@example.org | 1234 | user-for-search | Search for me | 0.0.4 |
| not-to-be-found | u2@example.org | 1234 | just-an-id | Not to be found | 0.0.4 |
And the following "posts" are in the database:
| id | title | content |
| p1 | 101 Essays that will change the way you think | 101 Essays, of course (PR)! |
| p2 | No content | will be found in this post, I guarantee |
And we have the following user accounts:
| slug | name | id |
| search-for-me | Search for me | user-for-search |
| not-to-be-found | Not to be found | just-an-id |
Given I am logged in
| p2 | No content | will be found in this post, I guarantee |
And I am logged in as "narrator"
And I navigate to page "/"
Scenario: Search for specific words
When I search for "Essays"
And I wait for 3000 milliseconds
Then I should have one item in the select dropdown
Then I should see the following posts in the select dropdown:
| title |
@ -25,8 +26,9 @@ Feature: Search
Scenario: Press enter opens search page
When I type "PR" and press Enter
Then I should see the search results page
Then I should see the following posts on the search results page
Then I am on page "/search/search-results"
And the search parameter equals "?search=PR"
Then I should see the following posts on the search results page:
| title |
| 101 Essays that will change the way you think |
@ -36,8 +38,9 @@ Feature: Search
Scenario: Select entry goes to post
When I search for "Essays"
And I wait for 3000 milliseconds
And I select a post entry
Then I should be on the post's page
Then I am on page "/post/p1/101-essays-that-will-change-the-way-you-think"
Scenario: Select dropdown content
When I search for "Essays"
@ -52,4 +55,4 @@ Feature: Search
| slug |
| search-for-me |
And I select a user entry
Then I should be on the user's profile
Then I am on page "/profile/user-for-search/search-for-me"

View File

@ -0,0 +1,7 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I select a post entry", () => {
cy.get(".searchable-input .search-post")
.first()
.trigger("click");
});

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I select a user entry", () => {
cy.get(".searchable-input .user-teaser")
.first()
.trigger("click");
})

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should have one item in the select dropdown", () => {
cy.get(".searchable-input .ds-select-dropdown").should($li => {
expect($li).to.have.length(1);
});
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should not see posts without the searched-for term in the select dropdown", () => {
cy.get(".ds-select-dropdown")
.should("not.contain","No searched for content");
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see posts with the searched-for term in the select dropdown", () => {
cy.get(".ds-select-dropdown")
.should("contain","101 Essays that will change the way you think");
});

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see the following posts on the search results page:", table => {
table.hashes().forEach(({ title }) => {
cy.get(".post-teaser")
.should("contain",title)
});
});

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see the following users in the select dropdown:", table => {
cy.get(".search-heading").should("contain", "Users");
table.hashes().forEach(({ slug }) => {
cy.get(".ds-select-dropdown").should("contain", slug);
});
});

View File

@ -0,0 +1,8 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I type {string} and press Enter", value => {
cy.get(".searchable-input .ds-select input")
.focus()
.type(value)
.type("{enter}", { force: true });
});

View File

@ -0,0 +1,8 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I type {string} and press escape", value => {
cy.get(".searchable-input .ds-select input")
.focus()
.type(value)
.type("{esc}");
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the search field should clear", () => {
cy.get(".searchable-input .ds-select input")
.should("have.text", "");
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the search parameter equals {string}", search => {
cy.location("search")
.should("eq", search);
});

View File

@ -0,0 +1,26 @@
Feature: User authentication
As an user
I want to sign in
In order to be able to posts and do other contributions as myself
Furthermore I want to be able to stay logged in and logout again
Background:
Given the following "users" are in the database:
| email | password | id | name | slug | termsAndConditionsAgreedVersion |
| peterpan@example.org | 1234 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
Scenario: Log in
When I navigate to page "/login"
And I fill in my credentials "peterpan@example.org" "1234"
And I click on "submit button"
Then I am logged in with username "Peter Pan"
Scenario: Refresh and stay logged in
Given I am logged in as "peter-pan"
When I refresh the page
Then I am logged in with username "Peter Pan"
Scenario: Log out
Given I am logged in as "peter-pan"
When I log out
Then I am on page "login"

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I am logged in with username {string}", name => {
cy.get(".avatar-menu").click();
cy.get(".avatar-menu-popover").contains(name);
cy.get(".avatar-menu").click(); // Close menu again
});

View File

@ -1,16 +1,21 @@
Feature: Block a User
Feature: User - block an user
As a user
I'd like to have a button to block another user
To prevent him from seeing and interacting with my contributions
Background:
Given I have a user account
And there is an annoying user called "Harassing User"
And I am logged in
Given the following "users" are in the database:
| email | password | id | name | slug | termsAndConditionsAgreedVersion |
| peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
| user@example.org | 123 | harassing-user | Harassing User | harassing-user | 0.0.4 |
And the following "posts" are in the database:
| id | title | slug | authorId |
| bWBjpkTKZp | previously created post | previously-created-post | id-of-peter-pan |
And I am logged in as "peter-pan"
Scenario: Block a user
Given I am on the profile page of the annoying user
When I click on "Block user" from the content menu in the user info box
When I navigate to page "profile/harassing-user"
And I click on "Block user" from the content menu in the user info box
And I "should" see "Unblock user" from the content menu in the user info box
And I navigate to my "Blocked users" settings page
Then I can see the following table:
@ -19,42 +24,46 @@ Feature: Block a User
Scenario: Blocked user cannot interact with my contributions
Given I block the user "Harassing User"
And I previously created a post
And a blocked user visits the post page of one of my authored posts
And I am logged in as "harassing-user"
And I navigate to page "/post/previously-created-post"
Then they should see a text explaining why commenting is not possible
And they should not see the comment form
Scenario: Block a previously followed user
Given I follow the user "Harassing User"
When I visit the profile page of the annoying user
When I navigate to page "/profile/harassing-user"
And I click on "Block user" from the content menu in the user info box
And I get removed from his follower collection
And I "should" see "Unblock user" from the content menu in the user info box
Scenario: Posts of blocked users are not filtered from search results
Given "Harassing User" wrote a post "You can still see my posts"
Given "harassing-user" wrote a post "You can still see my posts"
And I block the user "Harassing User"
When I search for "see"
And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| You can still see my posts |
Scenario: Blocked users can still see my posts
Given I previously created a post
And I block the user "Harassing User"
And the "blocked" user searches for "previously created"
When I block the user "Harassing User"
And I am logged in as "harassing-user"
And I navigate to page "/"
And I search for "previously created"
And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| previously created post |
Scenario: Blocked users cannot see they are blocked in their list
Given a user has blocked me
And I navigate to page "/"
And I navigate to my "Blocked users" settings page
Then I should see no users in my blocked users list
Scenario: Blocked users should not see link or button to unblock, only blocking users
Given a user has blocked me
When I visit the profile page of the annoying user
When I navigate to page "/profile/harassing-user"
And I should see the "Follow" button
And I should not see "Unblock user" button
And I "should not" see "Unblock user" from the content menu in the user info box

View File

@ -0,0 +1,11 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I block the user {string}", name => {
cy.neode()
.first("User", { name })
.then(blockedUser => {
cy.neode()
.first("User", {id: "id-of-peter-pan"})
.relateTo(blockedUser, "blocked");
});
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('I should not see {string} button', button => {
cy.get('.base-card .action-buttons')
.should('have.length', 1)
})

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should see no users in my blocked users list", () => {
cy.get('.ds-placeholder')
.should('contain', "So far, you have not blocked anybody.")
})

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('I should see the {string} button', button => {
cy.get('.base-card .action-buttons .base-button')
.should('contain', button)
})

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I {string} see {string} from the content menu in the user info box", (condition, link) => {
cy.get(".user-content-menu .base-button").click()
cy.get(".popover .ds-menu-item-link")
.should(condition === 'should' ? 'contain' : 'not.contain', link)
})

View File

@ -0,0 +1,15 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("a user has blocked me", () => {
cy.neode()
.first("User", {
name: "Peter Pan"
})
.then(blockedUser => {
cy.neode()
.first("User", {
name: 'Harassing User'
})
.relateTo(blockedUser, "blocked");
});
});

View File

@ -0,0 +1,5 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("they should not see the comment form", () => {
cy.get(".base-card").children().should('not.have.class', 'comment-form')
})

View File

@ -0,0 +1,5 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("they should see a text explaining why commenting is not possible", () => {
cy.get('.ds-placeholder').should('contain', "Commenting is not possible at this time on this post.")
})

View File

@ -0,0 +1,60 @@
Feature: Mute a User
As a user
I'd like to have a button to mute another user
To prevent him from seeing and interacting with my contributions
Background:
Given the following "users" are in the database:
| email | password | id | name | slug | termsAndConditionsAgreedVersion |
| peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
| user@example.org | 123 | annoying-user | Annoying User | annoying-user | 0.0.4 |
Given the following "posts" are in the database:
| id | title | content | authorId |
| im-not-muted | Post that should be seen | cause I'm not muted | id-of-peter-pan |
| bWBjpkTKZp | previously created post | previously-created-post | id-of-peter-pan |
And I am logged in as "peter-pan"
Scenario: Mute a user
Given I navigate to page "/profile/annoying-user"
When I click on "Mute user" from the content menu in the user info box
And I navigate to my "Muted users" settings page
Then I can see the following table:
| Avatar | Name |
| | Annoying User |
Scenario: Mute a previously followed user
Given I follow the user "Annoying User"
And "annoying-user" wrote a post "Spam Spam Spam"
When I navigate to page "/profile/annoying-user"
And I click on "Mute user" from the content menu in the user info box
Then the list of posts of this user is empty
And I get removed from his follower collection
Scenario: Posts of muted users are filtered from search results, users are not
Given "annoying-user" wrote a post "Spam Spam Spam"
When I search for "Spam"
And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| Spam Spam Spam |
When I mute the user "Annoying User"
And I refresh the page
And I search for "Anno"
And I wait for 3000 milliseconds
Then the search should not contain posts by the annoying user
But the search should contain the annoying user
But I search for "not muted"
And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| Post that should be seen |
Scenario: Muted users can still see my posts
And I mute the user "Annoying User"
And I am logged in as "annoying-user"
And I navigate to page "/"
And I search for "previously created"
And I wait for 3000 milliseconds
Then I should see the following posts in the select dropdown:
| title |
| previously created post |

View File

@ -0,0 +1,13 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I mute the user {string}", name => {
cy.neode()
.first("User", { name })
.then(mutedUser => {
cy.neode()
.first("User", {
name: "Peter Pan"
})
.relateTo(mutedUser, "muted");
});
});

View File

@ -0,0 +1,6 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the list of posts of this user is empty", () => {
cy.get(".base-card").not(".post-link");
cy.get(".main-container").find(".ds-space.hc-empty");
});

View File

@ -0,0 +1,13 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the search should contain the annoying user", () => {
cy.get(".searchable-input .ds-select-dropdown")
.should($li => {
expect($li).to.have.length(1);
})
cy.get(".ds-select-dropdown .user-teaser .slug")
.should("contain", '@annoying-user');
cy.get(".searchable-input .ds-select input")
.focus()
.type("{esc}");
})

View File

@ -0,0 +1,10 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("the search should not contain posts by the annoying user", () => {
cy.get(".searchable-input .ds-select-dropdown").should($li => {
expect($li).to.have.length(1);
})
cy.get(".ds-select-dropdown")
.should("not.have.class", '.search-post')
.should("not.contain", 'Spam')
});

View File

@ -0,0 +1,20 @@
Feature: User profile - Upload avatar image
As a user
I would like to be able to add an avatar image to my profile
So that I can personalize my profile
Background:
Given the following "users" are in the database:
| email | password | id | name | slug | termsAndConditionsAgreedVersion |
| peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
| user@example.org | 123 | user | User | user | 0.0.4 |
And I am logged in as "peter-pan"
Scenario: Change my UserProfile Image
And I navigate to page "/profile/peter-pan"
Then I should be able to change my profile picture
Scenario: Unable to change another user's avatar
Given I am logged in as "user"
And I navigate to page "/profile/peter-pan"
Then I cannot upload a picture

View File

@ -0,0 +1,8 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I cannot upload a picture", () => {
cy.get(".base-card")
.children()
.should("not.have.id", "customdropzone")
.should("have.class", "user-avatar");
});

View File

@ -0,0 +1,17 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I should be able to change my profile picture", () => {
const avatarUpload = "onourjourney.png";
cy.fixture(avatarUpload, "base64").then(fileContent => {
cy.get("#customdropzone").upload(
{ fileContent, fileName: avatarUpload, mimeType: "image/png" },
{ subjectType: "drag-n-drop", force: true }
);
});
cy.get(".profile-avatar img")
.should("have.attr", "src")
.and("contains", "onourjourney");
cy.contains(".iziToast-message", "Upload successful")
.should("have.length",1);
});

View File

@ -0,0 +1,55 @@
Feature: User profile - change password
As a user
I want to change my password in my settings
For security, e.g. if I exposed my password by accident
Login via email and password is a well-known authentication procedure and you
can assure to the server that you are who you claim to be. Either if you
exposed your password by acccident and you want to invalidate the exposed
password or just out of an good habit, you want to change your password.
Background:
Given the following "users" are in the database:
| email | password | id | name | slug | termsAndConditionsAgreedVersion |
| peterpan@example.org | exposed | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
And I am logged in as "peter-pan"
And I navigate to page "/settings"
And I click on "security menu"
Scenario: Incorrect Old Password
When I fill the password form with:
| Your old password | incorrect |
| Your new password | secure |
| Confirm new password | secure |
And I submit the form
And I see a "failure toaster" message:
"""
Old password is not correct
"""
Scenario: Incorrect Password Repeat
When I fill the password form with:
| Your old password | exposed |
| Your new password | secure |
| Confirm new password | eruces |
And I cannot submit the form
Scenario: Change my password
Given I navigate to page "/settings"
And I click on "security menu"
When I fill the password form with:
| Your old password | exposed |
| Your new password | secure |
| Confirm new password | secure |
And I submit the form
And I see a "success toaster" message:
"""
Password successfully changed!
"""
And I log out
Then I fill in my credentials "peterpan@example.org" "exposed"
And I click on "submit button"
And I cannot login anymore
But I fill in my credentials "peterpan@example.org" "secure"
And I click on "submit button"
And I can login successfully

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I can login successfully", () => {
// cy.reload();
cy.get(".iziToast-wrapper")
.should("contain", "You are logged in!");
});

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I cannot login anymore", password => {
//cy.reload();
cy.get(".iziToast-wrapper")
.should("contain", "Incorrect email address or password.");
});

View File

@ -0,0 +1,6 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I cannot submit the form", () => {
cy.get("button[type=submit]")
.should('be.disabled');
});

View File

@ -0,0 +1,11 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I fill the password form with:", table => {
table = table.rowsHash();
cy.get("input[id=oldPassword]")
.type(table["Your old password"])
.get("input[id=password]")
.type(table["Your new password"])
.get("input[id=passwordConfirmation]")
.type(table["Confirm new password"]);
});

View File

@ -0,0 +1,5 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then("I see a {string} message:", (type, message) => {
cy.contains(message);
});

View File

@ -0,0 +1,5 @@
import { When } from "cypress-cucumber-preprocessor/steps";
When("I submit the form", () => {
cy.get("form").submit();
});

View File

@ -0,0 +1,38 @@
Feature: User profile - name, description and location
As a user
I would like to change my name, add a description and a location
So others can see my name, get some info about me and my location
Background:
Given the following "users" are in the database:
| email | password | id | name | slug | termsAndConditionsAgreedVersion |
| peterpan@example.org | 123 | id-of-peter-pan | Peter Pan | peter-pan | 0.0.4 |
And I am logged in as "peter-pan"
And I navigate to page "settings"
Scenario: Change username
When I save "Hansi" as my new name
Then I can see my new name "Hansi" when I click on my profile picture in the top right
When I refresh the page
Then I can see my new name "Hansi" when I click on my profile picture in the top right
Scenario Outline: I set my location to "<location>"
When I save "<location>" as my location
And I navigate to page "/profile/peter-pan"
Then they can see "<location>" in the info box below my avatar
Examples: Location
| location | type |
| Paris | City |
| Saxony-Anhalt | Region |
| Germany | Country |
Scenario: Display a description on profile page
Given I have the following self-description:
"""
Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
"""
When I navigate to page "/profile/peter-pan"
Then they can see the following text in the info box below my avatar:
"""
Ich lebe fettlos, fleischlos, fischlos dahin, fühle mich aber ganz wohl dabei
"""

View File

@ -0,0 +1,7 @@
import { Then } from "cypress-cucumber-preprocessor/steps";
Then('I can see my new name {string} when I click on my profile picture in the top right', name => {
cy.get('.avatar-menu').click() // open
cy.get('.avatar-menu-popover').contains(name)
cy.get('.avatar-menu').click() // close again
})

Some files were not shown because too many files have changed in this diff Show More