mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge branch 'master' of https://github.com/Ocelot-Social-Community/Ocelot-Social into 4265-move-sort-newsfeed-menu-into-filter-menu
This commit is contained in:
commit
59dfb4662e
31
.github/workflows/publish.yml
vendored
31
.github/workflows/publish.yml
vendored
@ -55,9 +55,9 @@ jobs:
|
||||
# NEO4J ##################################################################
|
||||
##########################################################################
|
||||
- name: Neo4J | Build `community` image
|
||||
run: |
|
||||
docker build --target community -t "ocelotsocialnetwork/neo4j:latest" -t "ocelotsocialnetwork/neo4j:community" -t "ocelotsocialnetwork/neo4j:${VERSION}" -t "ocelotsocialnetwork/neo4j:${BUILD_VERSION}" neo4j/
|
||||
docker save "ocelotsocialnetwork/neo4j" > /tmp/neo4j.tar
|
||||
run: docker build --target community -t "ocelotsocialnetwork/neo4j:latest" -t "ocelotsocialnetwork/neo4j:community" -t "ocelotsocialnetwork/neo4j:${VERSION}" -t "ocelotsocialnetwork/neo4j:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT neo4j/
|
||||
- name: Neo4J | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/neo4j" > /tmp/neo4j.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -91,10 +91,10 @@ jobs:
|
||||
##########################################################################
|
||||
# BUILD BACKEND DOCKER IMAGE (production) ################################
|
||||
##########################################################################
|
||||
- name: backend | Build `production` image
|
||||
run: |
|
||||
docker build --target production -t "ocelotsocialnetwork/backend:latest" -t "ocelotsocialnetwork/backend:${VERSION}" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}" backend/
|
||||
docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
|
||||
- name: Backend | Build `production` image
|
||||
run: docker build --target production -t "ocelotsocialnetwork/backend:latest" -t "ocelotsocialnetwork/backend:${VERSION}" -t "ocelotsocialnetwork/backend:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT backend/
|
||||
- name: Backend | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/backend" > /tmp/backend.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -128,10 +128,10 @@ jobs:
|
||||
##########################################################################
|
||||
# BUILD WEBAPP DOCKER IMAGE (build) ######################################
|
||||
##########################################################################
|
||||
- name: webapp | Build `production` image
|
||||
run: |
|
||||
docker build --target production -t "ocelotsocialnetwork/webapp:latest" -t "ocelotsocialnetwork/webapp:${VERSION}" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}" webapp/
|
||||
docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
|
||||
- name: Webapp | Build `production` image
|
||||
run: docker build --target production -t "ocelotsocialnetwork/webapp:latest" -t "ocelotsocialnetwork/webapp:${VERSION}" -t "ocelotsocialnetwork/webapp:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/
|
||||
- name: Webapp | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/webapp" > /tmp/webapp.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -165,11 +165,10 @@ jobs:
|
||||
##########################################################################
|
||||
# BUILD MAINTENANCE DOCKER IMAGE (build) #################################
|
||||
##########################################################################
|
||||
- name: maintenance | Build `production` image
|
||||
# TODO: --target production
|
||||
run: |
|
||||
docker build -t "ocelotsocialnetwork/maintenance:latest" -t "ocelotsocialnetwork/maintenance:${VERSION}" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" webapp/ -f webapp/Dockerfile.maintenance
|
||||
docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
|
||||
- name: Maintenance | Build `production` image
|
||||
run: docker build --target production -t "ocelotsocialnetwork/maintenance:latest" -t "ocelotsocialnetwork/maintenance:${VERSION}" -t "ocelotsocialnetwork/maintenance:${BUILD_VERSION}" --build-arg BBUILD_DATE=$BUILD_DATE --build-arg BBUILD_VERSION=$BUILD_VERSION --build-arg BBUILD_COMMIT=$BUILD_COMMIT webapp/ -f webapp/Dockerfile.maintenance
|
||||
- name: Maintenance | Save docker image
|
||||
run: docker save "ocelotsocialnetwork/maintenance" > /tmp/maintenance.tar
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
||||
75
.github/workflows/test.yml
vendored
75
.github/workflows/test.yml
vendored
@ -253,7 +253,6 @@ jobs:
|
||||
##########################################################################
|
||||
# COVERAGE REPORT FRONTEND ################################################
|
||||
##########################################################################
|
||||
# TODO: Maybe remove this later on to avoid spam?
|
||||
#- name: frontend | Coverage report
|
||||
# uses: romeovs/lcov-reporter-action@v0.2.21
|
||||
# with:
|
||||
@ -268,5 +267,75 @@ jobs:
|
||||
report_name: Coverage Webapp
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 52
|
||||
token: ${{ github.token }}
|
||||
min_coverage: 65
|
||||
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/
|
||||
|
||||
@ -3,15 +3,18 @@
|
||||
##################################################################################
|
||||
FROM node:12.19.0-alpine3.10 as base
|
||||
|
||||
# ENVs (available in production aswell, can be overwritten by commandline or env file)
|
||||
# ENVs
|
||||
## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame
|
||||
ENV DOCKER_WORKDIR="/app"
|
||||
## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0
|
||||
ENV BUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||
ARG BBUILD_DATE="1970-01-01T00:00:00.00Z"
|
||||
ENV BUILD_DATE=$BBUILD_DATE
|
||||
## We cannot do $(yarn run version)-${BUILD_NUMBER} here so we default to 0.0.0-0
|
||||
ENV BUILD_VERSION="0.0.0-0"
|
||||
ARG BBUILD_VERSION="0.0.0-0"
|
||||
ENV BUILD_VERSION=$BBUILD_VERSION
|
||||
## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000
|
||||
ENV BUILD_COMMIT="0000000"
|
||||
ARG BBUILD_COMMIT="0000000"
|
||||
ENV BUILD_COMMIT=$BBUILD_COMMIT
|
||||
## SET NODE_ENV
|
||||
ENV NODE_ENV="production"
|
||||
## App relevant Envs
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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]',
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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
|
||||
|
||||

|
||||
|
||||
## Run cypress
|
||||
|
||||
To run cypress without the user interface:
|
||||
|
||||
```bash
|
||||
$ yarn cypress:run
|
||||
```
|
||||
|
||||
This is used to run cypress in CI or in console
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
// please change also version in file "webapp/constants/terms-and-conditions-version.js"
|
||||
export const VERSION = '0.0.4'
|
||||
@ -1,8 +1,10 @@
|
||||
{
|
||||
"projectId": "qa7fe2",
|
||||
"ignoreTestFiles": "*.js",
|
||||
"chromeWebSecurity": false,
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"env": {
|
||||
"RETRIES": 2
|
||||
"retries": {
|
||||
"runMode": 2,
|
||||
"openMode": 0
|
||||
}
|
||||
}
|
||||
43
cypress/integration/Admin.PinPost.feature
Normal file
43
cypress/integration/Admin.PinPost.feature
Normal 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
|
||||
@ -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()
|
||||
})
|
||||
@ -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")
|
||||
})
|
||||
@ -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")
|
||||
})
|
||||
31
cypress/integration/Admin.TagOverview.feature
Normal file
31
cypress/integration/Admin.TagOverview.feature
Normal 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 |
|
||||
@ -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"
|
||||
@ -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);
|
||||
});
|
||||
@ -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();
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
40
cypress/integration/Moderation.HidePost.feature
Normal file
40
cypress/integration/Moderation.HidePost.feature
Normal 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
|
||||
"""
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
@ -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
|
||||
@ -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')
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
@ -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()
|
||||
})
|
||||
@ -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/')
|
||||
})
|
||||
@ -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();
|
||||
});
|
||||
@ -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()
|
||||
})
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
@ -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
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
29
cypress/integration/Notification.Mention.feature
Normal file
29
cypress/integration/Notification.Mention.feature
Normal 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
|
||||
@ -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);
|
||||
});
|
||||
@ -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();
|
||||
});
|
||||
@ -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});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
@ -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");
|
||||
});
|
||||
@ -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');
|
||||
});
|
||||
31
cypress/integration/PersistentLinks.feature
Normal file
31
cypress/integration/PersistentLinks.feature
Normal 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 |
|
||||
49
cypress/integration/Post.Comment.feature
Normal file
49
cypress/integration/Post.Comment.feature
Normal 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
|
||||
@ -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);
|
||||
});
|
||||
@ -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")
|
||||
});
|
||||
13
cypress/integration/Post.Comment/I_should_see_my_comment.js
Normal file
13
cypress/integration/Post.Comment/I_should_see_my_comment.js
Normal 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");
|
||||
});
|
||||
@ -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")
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
@ -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')
|
||||
})
|
||||
@ -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!");
|
||||
});
|
||||
@ -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");
|
||||
});
|
||||
24
cypress/integration/Post.Create.feature
Normal file
24
cypress/integration/Post.Create.feature
Normal 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
|
||||
@ -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);
|
||||
});
|
||||
@ -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);
|
||||
})
|
||||
});
|
||||
66
cypress/integration/Post.Images.feature
Normal file
66
cypress/integration/Post.Images.feature
Normal 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
|
||||
@ -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')
|
||||
})
|
||||
@ -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;
|
||||
}
|
||||
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
@ -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')
|
||||
})
|
||||
23
cypress/integration/Post.feature
Normal file
23
cypress/integration/Post.feature
Normal 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/.*"
|
||||
@ -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');
|
||||
|
||||
});
|
||||
@ -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"
|
||||
7
cypress/integration/Search/I_select_a_post_entry.js
Normal file
7
cypress/integration/Search/I_select_a_post_entry.js
Normal 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");
|
||||
});
|
||||
7
cypress/integration/Search/I_select_a_user_entry.js
Normal file
7
cypress/integration/Search/I_select_a_user_entry.js
Normal 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");
|
||||
})
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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");
|
||||
});
|
||||
@ -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");
|
||||
});
|
||||
@ -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)
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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 });
|
||||
});
|
||||
@ -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}");
|
||||
});
|
||||
@ -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", "");
|
||||
});
|
||||
@ -0,0 +1,6 @@
|
||||
import { Then } from "cypress-cucumber-preprocessor/steps";
|
||||
|
||||
Then("the search parameter equals {string}", search => {
|
||||
cy.location("search")
|
||||
.should("eq", search);
|
||||
});
|
||||
26
cypress/integration/User.Authentication.feature
Normal file
26
cypress/integration/User.Authentication.feature
Normal 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"
|
||||
@ -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
|
||||
});
|
||||
@ -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
|
||||
11
cypress/integration/User.Block/I_block_the_user_{string}.js
Normal file
11
cypress/integration/User.Block/I_block_the_user_{string}.js
Normal 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");
|
||||
});
|
||||
});
|
||||
@ -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)
|
||||
})
|
||||
@ -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.")
|
||||
})
|
||||
@ -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)
|
||||
})
|
||||
@ -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)
|
||||
})
|
||||
15
cypress/integration/User.Block/a_user_has_blocked_me.js
Normal file
15
cypress/integration/User.Block/a_user_has_blocked_me.js
Normal 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");
|
||||
});
|
||||
});
|
||||
@ -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')
|
||||
})
|
||||
@ -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.")
|
||||
})
|
||||
60
cypress/integration/User.Mute.feature.broken
Normal file
60
cypress/integration/User.Mute.feature.broken
Normal 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 |
|
||||
13
cypress/integration/User.Mute/I_mute_the_user_{string}.js
Normal file
13
cypress/integration/User.Mute/I_mute_the_user_{string}.js
Normal 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");
|
||||
});
|
||||
});
|
||||
@ -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");
|
||||
});
|
||||
@ -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}");
|
||||
})
|
||||
@ -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')
|
||||
});
|
||||
20
cypress/integration/UserProfile.Avatar.feature
Normal file
20
cypress/integration/UserProfile.Avatar.feature
Normal 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
|
||||
@ -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");
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
55
cypress/integration/UserProfile.ChangePassword.feature
Normal file
55
cypress/integration/UserProfile.ChangePassword.feature
Normal 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
|
||||
@ -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!");
|
||||
});
|
||||
@ -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.");
|
||||
});
|
||||
@ -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');
|
||||
});
|
||||
@ -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"]);
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import { Then } from "cypress-cucumber-preprocessor/steps";
|
||||
|
||||
Then("I see a {string} message:", (type, message) => {
|
||||
cy.contains(message);
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
import { When } from "cypress-cucumber-preprocessor/steps";
|
||||
|
||||
When("I submit the form", () => {
|
||||
cy.get("form").submit();
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user