test(cypress): Cover "Pinned post" feature

* Implement cypress tests

- Start implementation of cypress tests for pinned posts

* Test that Admin can pin a post

- Tests the process of an admin pinning a post

* Resolve failing tests

- Fix ordering of posts immediately after pinning posts by reloading page
- Check that tests are pinned posts are displayed first for role user

* Refactor to seperate the initialization

- Of the post data created in the database during setup

* Fix toaster test

* test(cypress): Add missing parts for pin feature

* docs(cucumber): Link to admin 🥒 folder

* Follow @mattwr18's suggestions

* test(backend): Order pinned posts like frontend

@mattwr18 I think this was a false negative.

Co-authored-by: Robert Schäfer <git@roschaefer.de>
This commit is contained in:
Mike Aono 2020-01-23 22:30:12 +03:00 committed by Robert Schäfer
parent 42c08d4296
commit d49afc25cf
12 changed files with 145 additions and 56 deletions

View File

@ -41,4 +41,6 @@ export default {
language: { type: 'string', allow: [null] },
imageBlurred: { type: 'boolean', default: false },
imageAspectRatio: { type: 'float', default: 1.0 },
pinned: { type: 'boolean', default: null, valid: [null, true] },
pinnedAt: { type: 'string', isoDate: true },
}

View File

@ -682,58 +682,62 @@ describe('UpdatePost', () => {
})
describe('PostOrdering', () => {
let pinnedPost, admin
beforeEach(async () => {
;[pinnedPost] = await Promise.all([
neode.create('Post', {
id: 'im-a-pinned-post',
pinned: true,
}),
neode.create('Post', {
id: 'i-was-created-after-pinned-post',
createdAt: '2019-10-22T17:26:29.070Z', // this should always be 3rd
}),
])
admin = await user.update({
role: 'admin',
name: 'Admin',
updatedAt: new Date().toISOString(),
await factory.create('Post', {
id: 'im-a-pinned-post',
createdAt: '2019-11-22T17:26:29.070Z',
pinned: true,
})
await factory.create('Post', {
id: 'i-was-created-before-pinned-post',
// fairly old, so this should be 3rd
createdAt: '2019-10-22T17:26:29.070Z',
})
await admin.relateTo(pinnedPost, 'pinned')
})
it('pinned post appear first even when created before other posts', async () => {
const postOrderingQuery = gql`
query($orderBy: [_PostOrdering]) {
Post(orderBy: $orderBy) {
id
pinnedAt
describe('order by `pinned_asc` and `createdAt_desc`', () => {
beforeEach(() => {
// this is the ordering in the frontend
variables = { orderBy: ['pinned_asc', 'createdAt_desc'] }
})
it('pinned post appear first even when created before other posts', async () => {
const postOrderingQuery = gql`
query($orderBy: [_PostOrdering]) {
Post(orderBy: $orderBy) {
id
pinned
createdAt
pinnedAt
}
}
}
`
const expected = {
data: {
Post: [
{
id: 'im-a-pinned-post',
pinnedAt: expect.any(String),
},
{
id: 'p9876',
pinnedAt: null,
},
{
id: 'i-was-created-after-pinned-post',
pinnedAt: null,
},
],
},
errors: undefined,
}
variables = { orderBy: ['pinned_desc', 'createdAt_desc'] }
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject(
expected,
)
`
await expect(query({ query: postOrderingQuery, variables })).resolves.toMatchObject({
data: {
Post: [
{
id: 'im-a-pinned-post',
pinned: true,
createdAt: '2019-11-22T17:26:29.070Z',
pinnedAt: expect.any(String),
},
{
id: 'p9876',
pinned: null,
createdAt: expect.any(String),
pinnedAt: null,
},
{
id: 'i-was-created-before-pinned-post',
pinned: null,
createdAt: '2019-10-22T17:26:29.070Z',
pinnedAt: null,
},
],
},
errors: undefined,
})
})
})
})
})

View File

@ -21,11 +21,15 @@ export default function create() {
categoryIds: [],
imageBlurred: false,
imageAspectRatio: 1.333,
pinned: null,
}
args = {
...defaults,
...args,
}
// Convert false to null
args.pinned = args.pinned || null
args.slug = args.slug || slugify(args.title, { lower: true })
args.contentExcerpt = args.contentExcerpt || args.content
@ -50,9 +54,21 @@ export default function create() {
if (author && authorId) throw new Error('You provided both author and authorId')
if (authorId) author = await neodeInstance.find('User', authorId)
author = author || (await factoryInstance.create('User'))
const post = await neodeInstance.create('Post', args)
await post.relateTo(author, 'author')
if (args.pinned) {
args.pinnedAt = args.pinnedAt || new Date().toISOString()
if (!args.pinnedBy) {
const admin = await factoryInstance.create('User', {
role: 'admin',
updatedAt: new Date().toISOString(),
})
await admin.relateTo(post, 'pinned')
args.pinnedBy = admin
}
}
await Promise.all(categories.map(c => c.relateTo(post, 'post')))
await Promise.all(tags.map(t => t.relateTo(post, 'post')))
return post

View File

@ -249,10 +249,12 @@ Shows automatically related actions for existing post.
### Administration
[Cucumber Features](https://github.com/Human-Connection/Human-Connection/tree/master/cypress/integration/administration)
* Provide Admin-Interface to send Users Invite Code
* Static Pages for Data Privacy Statement ...
* Create, edit and delete Announcements
* Show Announcements on top of User Interface
* Pin a post to inform users
### Invitation

View File

@ -0,0 +1,36 @@
Feature: Pin 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 we have the following posts in our 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
Given I am logged in with a "user" role
Then the first post on the landing page 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
Given I am logged in with a "user" role
When 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
Given I am logged in with a "admin" role
And I open the content menu of post "Yet another post"
When I click on 'Pin post'
Then I see a toaster with "Post pinned successfully"
And the first post on the landing page has the title:
"""
Yet another post
"""
And the post with title "Yet another post" has a ribbon for pinned posts

View File

@ -44,3 +44,31 @@ Then("I should see an abreviated version of my comment", () => {
Then("the editor should be cleared", () => {
cy.get(".ProseMirror p").should("have.class", "is-empty");
});
When("I open the content menu of post {string}", (title)=> {
cy.contains('.post-card', title)
.find('.content-menu .base-button')
.click()
})
When("I click on 'Pin post'", (string)=> {
cy.get("a.ds-menu-item-link").contains("Pin post")
.click()
})
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")
})
And("the post with title {string} has a ribbon for pinned posts", (title) => {
cy.get("article.post-card").contains(title)
.parent()
.find("div.ribbon.ribbon--pinned")
.should("contain", "Announcement")
})
Then("I see a toaster with {string}", (title) => {
cy.get(".iziToast-message").should("contain", title);
})

View File

@ -170,5 +170,4 @@ When("they have a post someone has reported", () => {
authorId: 'annnoying-user',
title,
});
})

View File

@ -212,6 +212,7 @@ Given("we have the following posts in our database:", table => {
...postAttributes,
deleted: Boolean(postAttributes.deleted),
disabled: Boolean(postAttributes.disabled),
pinned: Boolean(postAttributes.pinned),
categoryIds: ['cat-456']
}
cy.factory().create("Post", postAttributes);

View File

@ -9,10 +9,10 @@ Feature: Report and Moderate
Background:
Given we have the following user accounts:
| id | name |
| u67 | David Irving |
| 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:
| authorId | id | title | content |
| u67 | p1 | The Truth about the Holocaust | It never existed! |

View File

@ -132,7 +132,7 @@ export default {
)
},
isPinned() {
return this.post && this.post.pinnedBy
return this.post && this.post.pinned
},
},
methods: {

View File

@ -52,6 +52,7 @@ export const postFragment = gql`
}
pinnedAt
imageAspectRatio
pinned
}
`

View File

@ -413,8 +413,8 @@
"name": "Take action"
},
"menu": {
"edit": "Edit Post",
"delete": "Delete Post",
"edit": "Edit post",
"delete": "Delete post",
"pin": "Pin post",
"pinnedSuccessfully": "Post pinned successfully!",
"unpin": "Unpin post",