mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 10:42:45 +03:00
🐛 Fixed listing pages in Admin (#20633)
closes https://github.com/TryGhost/Ghost/issues/20632 - Revert "✨ Improved performance loading the posts list in admin (#20618)"
This commit is contained in:
parent
d0db527b8d
commit
8ea1dfb957
@ -1,5 +1,5 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import SelectionList from './posts-list/selection-list';
|
import SelectionList from '../utils/selection-list';
|
||||||
import {action} from '@ember/object';
|
import {action} from '@ember/object';
|
||||||
import {inject as service} from '@ember/service';
|
import {inject as service} from '@ember/service';
|
||||||
import {task} from 'ember-concurrency';
|
import {task} from 'ember-concurrency';
|
||||||
|
@ -216,14 +216,11 @@ export default class PostsContextMenu extends Component {
|
|||||||
yield this.performBulkDestroy();
|
yield this.performBulkDestroy();
|
||||||
this.notifications.showNotification(this.#getToastMessage('deleted'), {type: 'success'});
|
this.notifications.showNotification(this.#getToastMessage('deleted'), {type: 'success'});
|
||||||
|
|
||||||
for (const key in this.selectionList.infinityModel) {
|
const remainingModels = this.selectionList.infinityModel.content.filter((model) => {
|
||||||
const remainingModels = this.selectionList.infinityModel[key].content.filter((model) => {
|
|
||||||
return !deletedModels.includes(model);
|
return !deletedModels.includes(model);
|
||||||
});
|
});
|
||||||
// Deleteobjects method from infintiymodel is broken for all models except the first page, so we cannot use this
|
// Deleteobjects method from infintiymodel is broken for all models except the first page, so we cannot use this
|
||||||
this.infinity.replace(this.selectionList.infinityModel[key], remainingModels);
|
this.infinity.replace(this.selectionList.infinityModel, remainingModels);
|
||||||
}
|
|
||||||
|
|
||||||
this.selectionList.clearSelection({force: true});
|
this.selectionList.clearSelection({force: true});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -250,7 +247,9 @@ export default class PostsContextMenu extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove posts that no longer match the filter
|
||||||
this.updateFilteredPosts();
|
this.updateFilteredPosts();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,17 +282,14 @@ export default class PostsContextMenu extends Component {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: something is wrong in here
|
const remainingModels = this.selectionList.infinityModel.content.filter((model) => {
|
||||||
for (const key in this.selectionList.infinityModel) {
|
|
||||||
const remainingModels = this.selectionList.infinityModel[key].content.filter((model) => {
|
|
||||||
if (!updatedModels.find(u => u.id === model.id)) {
|
if (!updatedModels.find(u => u.id === model.id)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return filterNql.queryJSON(model.serialize({includeId: true}));
|
return filterNql.queryJSON(model.serialize({includeId: true}));
|
||||||
});
|
});
|
||||||
// Deleteobjects method from infintiymodel is broken for all models except the first page, so we cannot use this
|
// Deleteobjects method from infintiymodel is broken for all models except the first page, so we cannot use this
|
||||||
this.infinity.replace(this.selectionList.infinityModel[key], remainingModels);
|
this.infinity.replace(this.selectionList.infinityModel, remainingModels);
|
||||||
}
|
|
||||||
|
|
||||||
this.selectionList.clearUnavailableItems();
|
this.selectionList.clearUnavailableItems();
|
||||||
}
|
}
|
||||||
@ -390,10 +386,8 @@ export default class PostsContextMenu extends Component {
|
|||||||
const data = result[this.type === 'post' ? 'posts' : 'pages'][0];
|
const data = result[this.type === 'post' ? 'posts' : 'pages'][0];
|
||||||
const model = this.store.peekRecord(this.type, data.id);
|
const model = this.store.peekRecord(this.type, data.id);
|
||||||
|
|
||||||
// Update infinity draft posts content - copied posts are always drafts
|
// Update infinity list
|
||||||
if (this.selectionList.infinityModel.draftPosts) {
|
this.selectionList.infinityModel.content.unshiftObject(model);
|
||||||
this.selectionList.infinityModel.draftPosts.content.unshiftObject(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show notification
|
// Show notification
|
||||||
this.notifications.showNotification(this.#getToastMessage('duplicated'), {type: 'success'});
|
this.notifications.showNotification(this.#getToastMessage('duplicated'), {type: 'success'});
|
||||||
|
@ -1,39 +1,14 @@
|
|||||||
<MultiList::List @model={{@list}} class="posts-list gh-list {{unless @model "no-posts"}} feature-memberAttribution" as |list| >
|
<MultiList::List @model={{@list}} class="posts-list gh-list {{unless @model "no-posts"}} feature-memberAttribution" as |list| >
|
||||||
{{!-- always order as scheduled, draft, remainder --}}
|
{{#each @model as |post|}}
|
||||||
{{#if (or @model.scheduledPosts (or @model.draftPosts @model.publishedAndSentPosts))}}
|
|
||||||
{{#if @model.scheduledPosts}}
|
|
||||||
{{#each @model.scheduledPosts as |post|}}
|
|
||||||
<list.item @id={{post.id}} class="gh-posts-list-item-group">
|
<list.item @id={{post.id}} class="gh-posts-list-item-group">
|
||||||
<PostsList::ListItem
|
<PostsList::ListItem
|
||||||
@post={{post}}
|
@post={{post}}
|
||||||
data-test-post-id={{post.id}}
|
data-test-post-id={{post.id}}
|
||||||
/>
|
/>
|
||||||
</list.item>
|
</list.item>
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if (and @model.draftPosts (or (not @model.scheduledPosts) (and @model.scheduledPosts @model.scheduledPosts.reachedInfinity)))}}
|
|
||||||
{{#each @model.draftPosts as |post|}}
|
|
||||||
<list.item @id={{post.id}} class="gh-posts-list-item-group">
|
|
||||||
<PostsList::ListItem
|
|
||||||
@post={{post}}
|
|
||||||
data-test-post-id={{post.id}}
|
|
||||||
/>
|
|
||||||
</list.item>
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if (and @model.publishedAndSentPosts (and (or (not @model.scheduledPosts) @model.scheduledPosts.reachedInfinity) (or (not @model.draftPosts) @model.draftPosts.reachedInfinity)))}}
|
|
||||||
{{#each @model.publishedAndSentPosts as |post|}}
|
|
||||||
<list.item @id={{post.id}} class="gh-posts-list-item-group">
|
|
||||||
<PostsList::ListItem
|
|
||||||
@post={{post}}
|
|
||||||
data-test-post-id={{post.id}}
|
|
||||||
/>
|
|
||||||
</list.item>
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
{{/if}}
|
{{/each}}
|
||||||
</MultiList::List>
|
</MultiList::List>
|
||||||
|
|
||||||
{{!-- The currently selected item or items are passed to the context menu --}}
|
{{!-- The currently selected item or items are passed to the context menu --}}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import SelectionList from 'ghost-admin/components/posts-list/selection-list';
|
import SelectionList from 'ghost-admin/utils/selection-list';
|
||||||
import {DEFAULT_QUERY_PARAMS} from 'ghost-admin/helpers/reset-query-params';
|
import {DEFAULT_QUERY_PARAMS} from 'ghost-admin/helpers/reset-query-params';
|
||||||
import {action} from '@ember/object';
|
import {action} from '@ember/object';
|
||||||
import {inject} from 'ghost-admin/decorators/inject';
|
import {inject} from 'ghost-admin/decorators/inject';
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
|
||||||
import RSVP from 'rsvp';
|
|
||||||
import {action} from '@ember/object';
|
import {action} from '@ember/object';
|
||||||
import {assign} from '@ember/polyfills';
|
import {assign} from '@ember/polyfills';
|
||||||
import {isBlank} from '@ember/utils';
|
import {isBlank} from '@ember/utils';
|
||||||
@ -40,53 +39,43 @@ export default class PostsRoute extends AuthenticatedRoute {
|
|||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
const user = this.session.user;
|
const user = this.session.user;
|
||||||
|
let queryParams = {};
|
||||||
let filterParams = {tag: params.tag, visibility: params.visibility};
|
let filterParams = {tag: params.tag, visibility: params.visibility};
|
||||||
let paginationParams = {
|
let paginationParams = {
|
||||||
perPageParam: 'limit',
|
perPageParam: 'limit',
|
||||||
totalPagesParam: 'meta.pagination.pages'
|
totalPagesParam: 'meta.pagination.pages'
|
||||||
};
|
};
|
||||||
|
|
||||||
// type filters are actually mapping statuses
|
|
||||||
assign(filterParams, this._getTypeFilters(params.type));
|
assign(filterParams, this._getTypeFilters(params.type));
|
||||||
|
|
||||||
if (params.type === 'featured') {
|
if (params.type === 'featured') {
|
||||||
filterParams.featured = true;
|
filterParams.featured = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// authors and contributors can only view their own posts
|
|
||||||
if (user.isAuthor) {
|
if (user.isAuthor) {
|
||||||
|
// authors can only view their own posts
|
||||||
filterParams.authors = user.slug;
|
filterParams.authors = user.slug;
|
||||||
} else if (user.isContributor) {
|
} else if (user.isContributor) {
|
||||||
|
// Contributors can only view their own draft posts
|
||||||
filterParams.authors = user.slug;
|
filterParams.authors = user.slug;
|
||||||
// otherwise we need to filter by author if present
|
// filterParams.status = 'draft';
|
||||||
} else if (params.author) {
|
} else if (params.author) {
|
||||||
filterParams.authors = params.author;
|
filterParams.authors = params.author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filter = this._filterString(filterParams);
|
||||||
|
if (!isBlank(filter)) {
|
||||||
|
queryParams.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isBlank(params.order)) {
|
||||||
|
queryParams.order = params.order;
|
||||||
|
}
|
||||||
|
|
||||||
let perPage = this.perPage;
|
let perPage = this.perPage;
|
||||||
|
let paginationSettings = assign({perPage, startingPage: 1}, paginationParams, queryParams);
|
||||||
|
|
||||||
const filterStatuses = filterParams.status;
|
return this.infinity.model(this.modelName, paginationSettings);
|
||||||
let queryParams = {allFilter: this._filterString({...filterParams})}; // pass along the parent filter so it's easier to apply the params filter to each infinity model
|
|
||||||
let models = {};
|
|
||||||
if (filterStatuses.includes('scheduled')) {
|
|
||||||
let scheduledPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: 'scheduled'})};
|
|
||||||
models.scheduledPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, scheduledPostsParams));
|
|
||||||
}
|
|
||||||
if (filterStatuses.includes('draft')) {
|
|
||||||
let draftPostsParams = {...queryParams, order: params.order || 'updated_at desc', filter: this._filterString({...filterParams, status: 'draft'})};
|
|
||||||
models.draftPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, draftPostsParams));
|
|
||||||
}
|
|
||||||
if (filterStatuses.includes('published') || filterStatuses.includes('sent')) {
|
|
||||||
let publishedAndSentPostsParams;
|
|
||||||
if (filterStatuses.includes('published') && filterStatuses.includes('sent')) {
|
|
||||||
publishedAndSentPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: '[published,sent]'})};
|
|
||||||
} else {
|
|
||||||
publishedAndSentPostsParams = {...queryParams, order: params.order || 'published_at desc', filter: this._filterString({...filterParams, status: filterStatuses.includes('published') ? 'published' : 'sent'})};
|
|
||||||
}
|
|
||||||
models.publishedAndSentPosts = this.infinity.model('post', assign({perPage, startingPage: 1}, paginationParams, publishedAndSentPostsParams));
|
|
||||||
}
|
|
||||||
|
|
||||||
return RSVP.hash(models);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger a background load of all tags and authors for use in filter dropdowns
|
// trigger a background load of all tags and authors for use in filter dropdowns
|
||||||
@ -131,12 +120,6 @@ export default class PostsRoute extends AuthenticatedRoute {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object containing the status filter based on the given type.
|
|
||||||
*
|
|
||||||
* @param {string} type - The type of filter to generate (draft, published, scheduled, sent).
|
|
||||||
* @returns {Object} - An object containing the status filter.
|
|
||||||
*/
|
|
||||||
_getTypeFilters(type) {
|
_getTypeFilters(type) {
|
||||||
let status = '[draft,scheduled,published,sent]';
|
let status = '[draft,scheduled,published,sent]';
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<section class="view-container content-list">
|
<section class="view-container content-list">
|
||||||
<PostsList::List
|
<PostsList::List
|
||||||
@model={{@model}}
|
@model={{this.postsInfinityModel}}
|
||||||
@list={{this.selectionList}}
|
@list={{this.selectionList}}
|
||||||
>
|
>
|
||||||
<li class="no-posts-box" data-test-no-posts-box>
|
<li class="no-posts-box" data-test-no-posts-box>
|
||||||
@ -43,7 +43,7 @@
|
|||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{else}}
|
{{else}}
|
||||||
<h4>No posts match the current filter</h4>
|
<h4>No posts match the current filter</h4>
|
||||||
<LinkTo @route="posts" @query={{hash type=null author=null tag=null visibility=null}} class="gh-btn" data-test-link="show-all">
|
<LinkTo @route="posts" @query={{hash type=null author=null tag=null}} class="gh-btn" data-test-link="show-all">
|
||||||
<span>Show all posts</span>
|
<span>Show all posts</span>
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@ -51,26 +51,11 @@
|
|||||||
</li>
|
</li>
|
||||||
</PostsList::List>
|
</PostsList::List>
|
||||||
|
|
||||||
{{!-- only show one infinity loader wheel at a time - always order as scheduled, draft, remainder --}}
|
|
||||||
{{#if @model.scheduledPosts}}
|
|
||||||
<GhInfinityLoader
|
<GhInfinityLoader
|
||||||
@infinityModel={{@model.scheduledPosts}}
|
@infinityModel={{this.postsInfinityModel}}
|
||||||
@scrollable=".gh-main"
|
@scrollable=".gh-main"
|
||||||
@triggerOffset={{1000}} />
|
@triggerOffset={{1000}} />
|
||||||
{{/if}}
|
|
||||||
{{#if (and @model.draftPosts (or (not @model.scheduledPosts) (and @model.scheduledPosts @model.scheduledPosts.reachedInfinity)))}}
|
|
||||||
<GhInfinityLoader
|
|
||||||
@infinityModel={{@model.draftPosts}}
|
|
||||||
@scrollable=".gh-main"
|
|
||||||
@triggerOffset={{1000}} />
|
|
||||||
{{/if}}
|
|
||||||
{{#if (and @model.publishedAndSentPosts (and (or (not @model.scheduledPosts) @model.scheduledPosts.reachedInfinity) (or (not @model.draftPosts) @model.draftPosts.reachedInfinity)))}}
|
|
||||||
<GhInfinityLoader
|
|
||||||
@infinityModel={{@model.publishedAndSentPosts}}
|
|
||||||
@scrollable=".gh-main"
|
|
||||||
@triggerOffset={{1000}} />
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</section>
|
</section>
|
||||||
|
@ -18,11 +18,7 @@ export default class SelectionList {
|
|||||||
#clearOnNextUnfreeze = false;
|
#clearOnNextUnfreeze = false;
|
||||||
|
|
||||||
constructor(infinityModel) {
|
constructor(infinityModel) {
|
||||||
this.infinityModel = infinityModel ?? {
|
this.infinityModel = infinityModel ?? {content: []};
|
||||||
draftPosts: {
|
|
||||||
content: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
freeze() {
|
freeze() {
|
||||||
@ -45,12 +41,7 @@ export default class SelectionList {
|
|||||||
* Returns an NQL filter for all items, not the selection
|
* Returns an NQL filter for all items, not the selection
|
||||||
*/
|
*/
|
||||||
get allFilter() {
|
get allFilter() {
|
||||||
const models = this.infinityModel;
|
return this.infinityModel.extraParams?.filter ?? '';
|
||||||
// grab filter from the first key in the infinityModel object (they should all be identical)
|
|
||||||
for (const key in models) {
|
|
||||||
return models[key].extraParams?.allFilter ?? '';
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,15 +81,12 @@ export default class SelectionList {
|
|||||||
* Keep in mind that when using CMD + A, we don't have all items in memory!
|
* Keep in mind that when using CMD + A, we don't have all items in memory!
|
||||||
*/
|
*/
|
||||||
get availableModels() {
|
get availableModels() {
|
||||||
const models = this.infinityModel;
|
|
||||||
const arr = [];
|
const arr = [];
|
||||||
for (const key in models) {
|
for (const item of this.infinityModel.content) {
|
||||||
for (const item of models[key].content) {
|
|
||||||
if (this.isSelected(item.id)) {
|
if (this.isSelected(item.id)) {
|
||||||
arr.push(item);
|
arr.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,13 +102,7 @@ export default class SelectionList {
|
|||||||
if (!this.inverted) {
|
if (!this.inverted) {
|
||||||
return this.selectedIds.size;
|
return this.selectedIds.size;
|
||||||
}
|
}
|
||||||
|
return Math.max((this.infinityModel.meta?.pagination?.total ?? 0) - this.selectedIds.size, 1);
|
||||||
const models = this.infinityModel;
|
|
||||||
let total;
|
|
||||||
for (const key in models) {
|
|
||||||
total += models[key].meta?.pagination?.total;
|
|
||||||
}
|
|
||||||
return Math.max((total ?? 0) - this.selectedIds.size, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected(id) {
|
isSelected(id) {
|
||||||
@ -165,14 +147,11 @@ export default class SelectionList {
|
|||||||
|
|
||||||
clearUnavailableItems() {
|
clearUnavailableItems() {
|
||||||
const newSelection = new Set();
|
const newSelection = new Set();
|
||||||
const models = this.infinityModel;
|
for (const item of this.infinityModel.content) {
|
||||||
for (const key in models) {
|
|
||||||
for (const item of models[key].content) {
|
|
||||||
if (this.selectedIds.has(item.id)) {
|
if (this.selectedIds.has(item.id)) {
|
||||||
newSelection.add(item.id);
|
newSelection.add(item.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.selectedIds = newSelection;
|
this.selectedIds = newSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,9 +181,7 @@ export default class SelectionList {
|
|||||||
// todo
|
// todo
|
||||||
let running = false;
|
let running = false;
|
||||||
|
|
||||||
const models = this.infinityModel;
|
for (const item of this.infinityModel.content) {
|
||||||
for (const key in models) {
|
|
||||||
for (const item of this.models[key].content) {
|
|
||||||
// Exlusing the last selected item
|
// Exlusing the last selected item
|
||||||
if (item.id === this.lastSelectedId || item.id === id) {
|
if (item.id === this.lastSelectedId || item.id === id) {
|
||||||
if (!running) {
|
if (!running) {
|
||||||
@ -238,7 +215,6 @@ export default class SelectionList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Force update
|
// Force update
|
||||||
// eslint-disable-next-line no-self-assign
|
// eslint-disable-next-line no-self-assign
|
@ -23,6 +23,7 @@ function extractTags(postAttrs, tags) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: handle authors filter
|
||||||
export function getPosts({posts}, {queryParams}) {
|
export function getPosts({posts}, {queryParams}) {
|
||||||
let {filter, page, limit} = queryParams;
|
let {filter, page, limit} = queryParams;
|
||||||
|
|
||||||
@ -30,27 +31,15 @@ export function getPosts({posts}, {queryParams}) {
|
|||||||
limit = +limit || 15;
|
limit = +limit || 15;
|
||||||
|
|
||||||
let statusFilter = extractFilterParam('status', filter);
|
let statusFilter = extractFilterParam('status', filter);
|
||||||
let authorsFilter = extractFilterParam('authors', filter);
|
|
||||||
let visibilityFilter = extractFilterParam('visibility', filter);
|
|
||||||
|
|
||||||
let collection = posts.all().filter((post) => {
|
let collection = posts.all().filter((post) => {
|
||||||
let matchesStatus = true;
|
let matchesStatus = true;
|
||||||
let matchesAuthors = true;
|
|
||||||
let matchesVisibility = true;
|
|
||||||
|
|
||||||
if (!isEmpty(statusFilter)) {
|
if (!isEmpty(statusFilter)) {
|
||||||
matchesStatus = statusFilter.includes(post.status);
|
matchesStatus = statusFilter.includes(post.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(authorsFilter)) {
|
return matchesStatus;
|
||||||
matchesAuthors = authorsFilter.includes(post.authors.models[0].slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEmpty(visibilityFilter)) {
|
|
||||||
matchesVisibility = visibilityFilter.includes(post.visibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchesStatus && matchesAuthors && matchesVisibility;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return paginateModelCollection('posts', collection, page, limit);
|
return paginateModelCollection('posts', collection, page, limit);
|
||||||
@ -70,6 +59,7 @@ export default function mockPosts(server) {
|
|||||||
return posts.create(attrs);
|
return posts.create(attrs);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: handle authors filter
|
||||||
server.get('/posts/', getPosts);
|
server.get('/posts/', getPosts);
|
||||||
|
|
||||||
server.get('/posts/:id/', function ({posts}, {params}) {
|
server.get('/posts/:id/', function ({posts}, {params}) {
|
||||||
@ -110,13 +100,6 @@ export default function mockPosts(server) {
|
|||||||
posts.find(ids).destroy();
|
posts.find(ids).destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
server.post('/posts/:id/copy/', function ({posts}, {params}) {
|
|
||||||
let post = posts.find(params.id);
|
|
||||||
let attrs = post.attrs;
|
|
||||||
|
|
||||||
return posts.create(attrs);
|
|
||||||
});
|
|
||||||
|
|
||||||
server.put('/posts/bulk/', function ({tags}, {requestBody}) {
|
server.put('/posts/bulk/', function ({tags}, {requestBody}) {
|
||||||
const bulk = JSON.parse(requestBody).bulk;
|
const bulk = JSON.parse(requestBody).bulk;
|
||||||
const action = bulk.action;
|
const action = bulk.action;
|
||||||
@ -132,7 +115,7 @@ export default function mockPosts(server) {
|
|||||||
tags.create(tag);
|
tags.create(tag);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// TODO: update the actual posts in the mock db if wanting to write tests where we navigate around (refresh model)
|
// TODO: update the actual posts in the mock db
|
||||||
// const postsToUpdate = posts.find(ids);
|
// const postsToUpdate = posts.find(ids);
|
||||||
// getting the posts is fine, but within this we CANNOT manipulate them (???) not even iterate with .forEach
|
// getting the posts is fine, but within this we CANNOT manipulate them (???) not even iterate with .forEach
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,11 @@ const findButton = (text, buttons) => {
|
|||||||
return Array.from(buttons).find(button => button.innerText.trim() === text);
|
return Array.from(buttons).find(button => button.innerText.trim() === text);
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: With accommodations for faster loading of posts in the UI, the requests to fetch the posts have been split into separate requests based
|
|
||||||
// on the status of the post. This means that the tests for filtering by status will have multiple requests to check against.
|
|
||||||
describe('Acceptance: Content', function () {
|
describe('Acceptance: Content', function () {
|
||||||
let hooks = setupApplicationTest();
|
let hooks = setupApplicationTest();
|
||||||
setupMirage(hooks);
|
setupMirage(hooks);
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
// console.log(`this.server`, this.server);
|
|
||||||
// console.log(`this.server.db`, this.server.db);
|
|
||||||
this.server.loadFixtures('configs');
|
this.server.loadFixtures('configs');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -36,70 +32,6 @@ describe('Acceptance: Content', function () {
|
|||||||
expect(currentURL()).to.equal('/signin');
|
expect(currentURL()).to.equal('/signin');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('as contributor', function () {
|
|
||||||
beforeEach(async function () {
|
|
||||||
let contributorRole = this.server.create('role', {name: 'Contributor'});
|
|
||||||
this.server.create('user', {roles: [contributorRole]});
|
|
||||||
|
|
||||||
return await authenticateSession();
|
|
||||||
});
|
|
||||||
|
|
||||||
// NOTE: This test seems to fail if run AFTER the 'can change access' test in the 'as admin' section; router seems to fail, did not look into it further
|
|
||||||
it('shows posts list and allows post creation', async function () {
|
|
||||||
await visit('/posts');
|
|
||||||
|
|
||||||
// has an empty state
|
|
||||||
expect(findAll('[data-test-post-id]')).to.have.length(0);
|
|
||||||
expect(find('[data-test-no-posts-box]')).to.exist;
|
|
||||||
expect(find('[data-test-link="write-a-new-post"]')).to.exist;
|
|
||||||
|
|
||||||
await click('[data-test-link="write-a-new-post"]');
|
|
||||||
|
|
||||||
expect(currentURL()).to.equal('/editor/post');
|
|
||||||
|
|
||||||
await fillIn('[data-test-editor-title-input]', 'First contributor post');
|
|
||||||
await blur('[data-test-editor-title-input]');
|
|
||||||
|
|
||||||
expect(currentURL()).to.equal('/editor/post/1');
|
|
||||||
|
|
||||||
await click('[data-test-link="posts"]');
|
|
||||||
|
|
||||||
expect(findAll('[data-test-post-id]')).to.have.length(1);
|
|
||||||
expect(find('[data-test-no-posts-box]')).to.not.exist;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('as author', function () {
|
|
||||||
let author, authorPost;
|
|
||||||
|
|
||||||
beforeEach(async function () {
|
|
||||||
let authorRole = this.server.create('role', {name: 'Author'});
|
|
||||||
author = this.server.create('user', {roles: [authorRole]});
|
|
||||||
let adminRole = this.server.create('role', {name: 'Administrator'});
|
|
||||||
let admin = this.server.create('user', {roles: [adminRole]});
|
|
||||||
|
|
||||||
// create posts
|
|
||||||
authorPost = this.server.create('post', {authors: [author], status: 'published', title: 'Author Post'});
|
|
||||||
this.server.create('post', {authors: [admin], status: 'scheduled', title: 'Admin Post'});
|
|
||||||
|
|
||||||
return await authenticateSession();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('only fetches the author\'s posts', async function () {
|
|
||||||
await visit('/posts');
|
|
||||||
// trigger a filter request so we can grab the posts API request easily
|
|
||||||
await selectChoose('[data-test-type-select]', 'Published posts');
|
|
||||||
|
|
||||||
// API request includes author filter
|
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
||||||
expect(lastRequest.queryParams.filter).to.have.string(`authors:${author.slug}`);
|
|
||||||
|
|
||||||
// only author's post is shown
|
|
||||||
expect(findAll('[data-test-post-id]').length, 'post count').to.equal(1);
|
|
||||||
expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author post').to.exist;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('as admin', function () {
|
describe('as admin', function () {
|
||||||
let admin, editor, publishedPost, scheduledPost, draftPost, authorPost;
|
let admin, editor, publishedPost, scheduledPost, draftPost, authorPost;
|
||||||
|
|
||||||
@ -109,10 +41,11 @@ describe('Acceptance: Content', function () {
|
|||||||
let editorRole = this.server.create('role', {name: 'Editor'});
|
let editorRole = this.server.create('role', {name: 'Editor'});
|
||||||
editor = this.server.create('user', {roles: [editorRole]});
|
editor = this.server.create('user', {roles: [editorRole]});
|
||||||
|
|
||||||
publishedPost = this.server.create('post', {authors: [admin], status: 'published', title: 'Published Post', visibility: 'paid'});
|
publishedPost = this.server.create('post', {authors: [admin], status: 'published', title: 'Published Post'});
|
||||||
scheduledPost = this.server.create('post', {authors: [admin], status: 'scheduled', title: 'Scheduled Post'});
|
scheduledPost = this.server.create('post', {authors: [admin], status: 'scheduled', title: 'Scheduled Post'});
|
||||||
|
// draftPost = this.server.create('post', {authors: [admin], status: 'draft', title: 'Draft Post', visibility: 'paid'});
|
||||||
draftPost = this.server.create('post', {authors: [admin], status: 'draft', title: 'Draft Post'});
|
draftPost = this.server.create('post', {authors: [admin], status: 'draft', title: 'Draft Post'});
|
||||||
authorPost = this.server.create('post', {authors: [editor], status: 'published', title: 'Editor Published Post'});
|
authorPost = this.server.create('post', {authors: [editor], status: 'published', title: 'Editor Published Post', visibiity: 'paid'});
|
||||||
|
|
||||||
// pages shouldn't appear in the list
|
// pages shouldn't appear in the list
|
||||||
this.server.create('page', {authors: [admin], status: 'published', title: 'Published Page'});
|
this.server.create('page', {authors: [admin], status: 'published', title: 'Published Page'});
|
||||||
@ -128,17 +61,7 @@ describe('Acceptance: Content', function () {
|
|||||||
// displays all posts by default (all statuses) [no pages]
|
// displays all posts by default (all statuses) [no pages]
|
||||||
expect(posts.length, 'all posts count').to.equal(4);
|
expect(posts.length, 'all posts count').to.equal(4);
|
||||||
|
|
||||||
// make sure display is scheduled > draft > published/sent
|
// note: atm the mirage backend doesn't support ordering of the results set
|
||||||
expect(posts[0].querySelector('.gh-content-entry-title').textContent, 'post 1 title').to.contain('Scheduled Post');
|
|
||||||
expect(posts[1].querySelector('.gh-content-entry-title').textContent, 'post 2 title').to.contain('Draft Post');
|
|
||||||
expect(posts[2].querySelector('.gh-content-entry-title').textContent, 'post 3 title').to.contain('Published Post');
|
|
||||||
expect(posts[3].querySelector('.gh-content-entry-title').textContent, 'post 4 title').to.contain('Editor Published Post');
|
|
||||||
|
|
||||||
// check API requests
|
|
||||||
let lastRequests = this.server.pretender.handledRequests.filter(request => request.url.includes('/posts/'));
|
|
||||||
expect(lastRequests[0].queryParams.filter, 'scheduled request filter').to.have.string('status:scheduled');
|
|
||||||
expect(lastRequests[1].queryParams.filter, 'drafts request filter').to.have.string('status:draft');
|
|
||||||
expect(lastRequests[2].queryParams.filter, 'published request filter').to.have.string('status:[published,sent]');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can filter by status', async function () {
|
it('can filter by status', async function () {
|
||||||
@ -174,6 +97,13 @@ describe('Acceptance: Content', function () {
|
|||||||
// Displays scheduled post
|
// Displays scheduled post
|
||||||
expect(findAll('[data-test-post-id]').length, 'scheduled count').to.equal(1);
|
expect(findAll('[data-test-post-id]').length, 'scheduled count').to.equal(1);
|
||||||
expect(find(`[data-test-post-id="${scheduledPost.id}"]`), 'scheduled post').to.exist;
|
expect(find(`[data-test-post-id="${scheduledPost.id}"]`), 'scheduled post').to.exist;
|
||||||
|
|
||||||
|
// show all posts
|
||||||
|
await selectChoose('[data-test-type-select]', 'All posts');
|
||||||
|
|
||||||
|
// API request is correct
|
||||||
|
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
|
expect(lastRequest.queryParams.filter, '"all" request status filter').to.have.string('status:[draft,scheduled,published,sent]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can filter by author', async function () {
|
it('can filter by author', async function () {
|
||||||
@ -184,31 +114,20 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct
|
// API request is correct
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.allFilter, '"editor" request status filter')
|
expect(lastRequest.queryParams.filter, '"editor" request status filter')
|
||||||
.to.have.string('status:[draft,scheduled,published,sent]');
|
.to.have.string('status:[draft,scheduled,published,sent]');
|
||||||
expect(lastRequest.queryParams.allFilter, '"editor" request filter param')
|
expect(lastRequest.queryParams.filter, '"editor" request filter param')
|
||||||
.to.have.string(`authors:${editor.slug}`);
|
.to.have.string(`authors:${editor.slug}`);
|
||||||
|
|
||||||
// Displays editor post
|
|
||||||
expect(findAll('[data-test-post-id]').length, 'editor count').to.equal(1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can filter by visibility', async function () {
|
it('can filter by visibility', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
await selectChoose('[data-test-visibility-select]', 'Paid members-only');
|
await selectChoose('[data-test-visibility-select]', 'Paid members-only');
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
||||||
expect(lastRequest.queryParams.allFilter, '"visibility" request filter param')
|
|
||||||
.to.have.string('visibility:[paid,tiers]');
|
|
||||||
let posts = findAll('[data-test-post-id]');
|
|
||||||
expect(posts.length, 'all posts count').to.equal(1);
|
|
||||||
|
|
||||||
await selectChoose('[data-test-visibility-select]', 'Public');
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
expect(lastRequest.queryParams.filter, '"visibility" request filter param')
|
||||||
expect(lastRequest.queryParams.allFilter, '"visibility" request filter param')
|
.to.have.string('visibility:[paid,tiers]+status:[draft,scheduled,published,sent]');
|
||||||
.to.have.string('visibility:public');
|
|
||||||
posts = findAll('[data-test-post-id]');
|
|
||||||
expect(posts.length, 'all posts count').to.equal(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can filter by tag', async function () {
|
it('can filter by tag', async function () {
|
||||||
@ -231,13 +150,14 @@ describe('Acceptance: Content', function () {
|
|||||||
await selectChoose('[data-test-tag-select]', 'B - Second');
|
await selectChoose('[data-test-tag-select]', 'B - Second');
|
||||||
// affirm request
|
// affirm request
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.allFilter, '"tag" request filter param').to.have.string('tag:second');
|
expect(lastRequest.queryParams.filter, 'request filter').to.have.string('tag:second');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('context menu actions', function () {
|
describe('context menu actions', function () {
|
||||||
describe('single post', function () {
|
describe('single post', function () {
|
||||||
it('can duplicate a post', async function () {
|
// has a duplicate option
|
||||||
|
it.skip('can duplicate a post', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
// get the post
|
// get the post
|
||||||
@ -245,11 +165,13 @@ describe('Acceptance: Content', function () {
|
|||||||
expect(post, 'post').to.exist;
|
expect(post, 'post').to.exist;
|
||||||
|
|
||||||
await triggerEvent(post, 'contextmenu');
|
await triggerEvent(post, 'contextmenu');
|
||||||
|
// await this.pauseTest();
|
||||||
|
|
||||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||||
|
|
||||||
let buttons = contextMenu.querySelectorAll('button');
|
let buttons = contextMenu.querySelectorAll('button');
|
||||||
|
|
||||||
|
// should have three options for a published post
|
||||||
expect(contextMenu, 'context menu').to.exist;
|
expect(contextMenu, 'context menu').to.exist;
|
||||||
expect(buttons.length, 'context menu buttons').to.equal(5);
|
expect(buttons.length, 'context menu buttons').to.equal(5);
|
||||||
expect(buttons[0].innerText.trim(), 'context menu button 1').to.contain('Unpublish');
|
expect(buttons[0].innerText.trim(), 'context menu button 1').to.contain('Unpublish');
|
||||||
@ -261,15 +183,19 @@ describe('Acceptance: Content', function () {
|
|||||||
// duplicate the post
|
// duplicate the post
|
||||||
await click(buttons[3]);
|
await click(buttons[3]);
|
||||||
|
|
||||||
const posts = findAll('[data-test-post-id]');
|
// API request is correct
|
||||||
expect(posts.length, 'all posts count').to.equal(5);
|
// POST /ghost/api/admin/posts/{id}/copy/?formats=mobiledoc,lexical
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
||||||
expect(lastRequest.url, 'request url').to.match(new RegExp(`/posts/${publishedPost.id}/copy/`));
|
// TODO: probably missing endpoint in mirage...
|
||||||
|
|
||||||
|
// let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
|
// console.log(`lastRequest`, lastRequest);
|
||||||
|
// expect(lastRequest.url, 'request url').to.match(new RegExp(`/posts/${publishedPost.id}/copy/`));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('multiple posts', function () {
|
describe('multiple posts', function () {
|
||||||
it('can feature and unfeature', async function () {
|
it('can feature and unfeature posts', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
// get all posts
|
// get all posts
|
||||||
@ -300,7 +226,7 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct - note, we don't mock the actual model updates
|
// API request is correct - note, we don't mock the actual model updates
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.filter, 'feature request id').to.equal(`id:['${publishedPost.id}','${authorPost.id}']`);
|
expect(lastRequest.queryParams.filter, 'feature request id').to.equal(`id:['3','4']`);
|
||||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'feature request action').to.equal('feature');
|
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'feature request action').to.equal('feature');
|
||||||
|
|
||||||
// ensure ui shows these are now featured
|
// ensure ui shows these are now featured
|
||||||
@ -321,7 +247,7 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct - note, we don't mock the actual model updates
|
// API request is correct - note, we don't mock the actual model updates
|
||||||
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
[lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.filter, 'unfeature request id').to.equal(`id:['${publishedPost.id}','${authorPost.id}']`);
|
expect(lastRequest.queryParams.filter, 'unfeature request id').to.equal(`id:['3','4']`);
|
||||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'unfeature request action').to.equal('unfeature');
|
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'unfeature request action').to.equal('unfeature');
|
||||||
|
|
||||||
// ensure ui shows these are now unfeatured
|
// ensure ui shows these are now unfeatured
|
||||||
@ -329,7 +255,7 @@ describe('Acceptance: Content', function () {
|
|||||||
expect(postFourContainer.querySelector('.gh-featured-post'), 'postFour featured').to.not.exist;
|
expect(postFourContainer.querySelector('.gh-featured-post'), 'postFour featured').to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add a tag', async function () {
|
it('can add a tag to multiple posts', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
// get all posts
|
// get all posts
|
||||||
@ -369,12 +295,13 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct - note, we don't mock the actual model updates
|
// API request is correct - note, we don't mock the actual model updates
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-2);
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-2);
|
||||||
expect(lastRequest.queryParams.filter, 'add tag request id').to.equal(`id:['${publishedPost.id}','${authorPost.id}']`);
|
expect(lastRequest.queryParams.filter, 'add tag request id').to.equal(`id:['3','4']`);
|
||||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'add tag request action').to.equal('addTag');
|
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'add tag request action').to.equal('addTag');
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Skip for now. This causes the member creation test to fail ('New member' text doesn't show... ???).
|
// NOTE: we do not seem to be loading the settings properly into the membersutil service, such that the members
|
||||||
it.skip('can change access', async function () {
|
// service doesn't think members are enabled
|
||||||
|
it.skip('can change access to multiple posts', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
// get all posts
|
// get all posts
|
||||||
@ -390,38 +317,26 @@ describe('Acceptance: Content', function () {
|
|||||||
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
expect(postFourContainer.getAttribute('data-selected'), 'postFour selected').to.exist;
|
||||||
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
expect(postThreeContainer.getAttribute('data-selected'), 'postThree selected').to.exist;
|
||||||
|
|
||||||
|
// NOTE: right clicks don't seem to work in these tests
|
||||||
|
// contextmenu is the event triggered - https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event
|
||||||
await triggerEvent(postFourContainer, 'contextmenu');
|
await triggerEvent(postFourContainer, 'contextmenu');
|
||||||
|
|
||||||
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
let contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
||||||
expect(contextMenu, 'context menu').to.exist;
|
expect(contextMenu, 'context menu').to.exist;
|
||||||
|
|
||||||
|
// TODO: the change access button is not showing; need to debug the UI to see what field it expects
|
||||||
|
// change access to the posts
|
||||||
let buttons = contextMenu.querySelectorAll('button');
|
let buttons = contextMenu.querySelectorAll('button');
|
||||||
let changeAccessButton = findButton('Change access', buttons);
|
let changeAccessButton = findButton('Change access', buttons);
|
||||||
|
|
||||||
expect(changeAccessButton, 'change access button').not.to.exist;
|
|
||||||
|
|
||||||
const settingsService = this.owner.lookup('service:settings');
|
|
||||||
await settingsService.set('membersEnabled', true);
|
|
||||||
|
|
||||||
await triggerEvent(postFourContainer, 'contextmenu');
|
|
||||||
contextMenu = find('.gh-posts-context-menu'); // this is a <ul> element
|
|
||||||
expect(contextMenu, 'context menu').to.exist;
|
|
||||||
buttons = contextMenu.querySelectorAll('button');
|
|
||||||
changeAccessButton = findButton('Change access', buttons);
|
|
||||||
|
|
||||||
expect(changeAccessButton, 'change access button').to.exist;
|
expect(changeAccessButton, 'change access button').to.exist;
|
||||||
await click(changeAccessButton);
|
await click(changeAccessButton);
|
||||||
|
|
||||||
const changeAccessModal = find('[data-test-modal="edit-posts-access"]');
|
const changeAccessModal = find('[data-test-modal="edit-posts-access"]');
|
||||||
const selectElement = changeAccessModal.querySelector('select');
|
expect(changeAccessModal, 'change access modal').to.exist;
|
||||||
await fillIn(selectElement, 'members');
|
|
||||||
await click('[data-test-button="confirm"]');
|
|
||||||
|
|
||||||
// check API request
|
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
|
||||||
expect(lastRequest.queryParams.filter, 'change access request id').to.equal(`id:['${publishedPost.id}','${authorPost.id}']`);
|
|
||||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'change access request action').to.equal('access');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can unpublish', async function () {
|
it('can unpublish posts', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
// get all posts
|
// get all posts
|
||||||
@ -457,7 +372,7 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct - note, we don't mock the actual model updates
|
// API request is correct - note, we don't mock the actual model updates
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.filter, 'unpublish request id').to.equal(`id:['${publishedPost.id}','${authorPost.id}']`);
|
expect(lastRequest.queryParams.filter, 'unpublish request id').to.equal(`id:['3','4']`);
|
||||||
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'unpublish request action').to.equal('unpublish');
|
expect(JSON.parse(lastRequest.requestBody).bulk.action, 'unpublish request action').to.equal('unpublish');
|
||||||
|
|
||||||
// ensure ui shows these are now unpublished
|
// ensure ui shows these are now unpublished
|
||||||
@ -465,7 +380,7 @@ describe('Acceptance: Content', function () {
|
|||||||
expect(postFourContainer.querySelector('.gh-content-entry-status').textContent, 'postThree status').to.contain('Draft');
|
expect(postFourContainer.querySelector('.gh-content-entry-status').textContent, 'postThree status').to.contain('Draft');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete', async function () {
|
it('can delete posts', async function () {
|
||||||
await visit('/posts');
|
await visit('/posts');
|
||||||
|
|
||||||
// get all posts
|
// get all posts
|
||||||
@ -501,7 +416,7 @@ describe('Acceptance: Content', function () {
|
|||||||
|
|
||||||
// API request is correct - note, we don't mock the actual model updates
|
// API request is correct - note, we don't mock the actual model updates
|
||||||
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
expect(lastRequest.queryParams.filter, 'delete request id').to.equal(`id:['${publishedPost.id}','${authorPost.id}']`);
|
expect(lastRequest.queryParams.filter, 'delete request id').to.equal(`id:['3','4']`);
|
||||||
expect(lastRequest.method, 'delete request method').to.equal('DELETE');
|
expect(lastRequest.method, 'delete request method').to.equal('DELETE');
|
||||||
|
|
||||||
// ensure ui shows these are now deleted
|
// ensure ui shows these are now deleted
|
||||||
@ -593,4 +508,67 @@ describe('Acceptance: Content', function () {
|
|||||||
expect(find('[data-test-screen-title]').innerText).to.match(/Scheduled/);
|
expect(find('[data-test-screen-title]').innerText).to.match(/Scheduled/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('as author', function () {
|
||||||
|
let author, authorPost;
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
let authorRole = this.server.create('role', {name: 'Author'});
|
||||||
|
author = this.server.create('user', {roles: [authorRole]});
|
||||||
|
let adminRole = this.server.create('role', {name: 'Administrator'});
|
||||||
|
let admin = this.server.create('user', {roles: [adminRole]});
|
||||||
|
|
||||||
|
// create posts
|
||||||
|
authorPost = this.server.create('post', {authors: [author], status: 'published', title: 'Author Post'});
|
||||||
|
this.server.create('post', {authors: [admin], status: 'scheduled', title: 'Admin Post'});
|
||||||
|
|
||||||
|
return await authenticateSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only fetches the author\'s posts', async function () {
|
||||||
|
await visit('/posts');
|
||||||
|
// trigger a filter request so we can grab the posts API request easily
|
||||||
|
await selectChoose('[data-test-type-select]', 'Published posts');
|
||||||
|
|
||||||
|
// API request includes author filter
|
||||||
|
let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
|
||||||
|
expect(lastRequest.queryParams.filter).to.have.string(`authors:${author.slug}`);
|
||||||
|
|
||||||
|
// only author's post is shown
|
||||||
|
expect(findAll('[data-test-post-id]').length, 'post count').to.equal(1);
|
||||||
|
expect(find(`[data-test-post-id="${authorPost.id}"]`), 'author post').to.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('as contributor', function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
let contributorRole = this.server.create('role', {name: 'Contributor'});
|
||||||
|
this.server.create('user', {roles: [contributorRole]});
|
||||||
|
|
||||||
|
return await authenticateSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows posts list and allows post creation', async function () {
|
||||||
|
await visit('/posts');
|
||||||
|
|
||||||
|
// has an empty state
|
||||||
|
expect(findAll('[data-test-post-id]')).to.have.length(0);
|
||||||
|
expect(find('[data-test-no-posts-box]')).to.exist;
|
||||||
|
expect(find('[data-test-link="write-a-new-post"]')).to.exist;
|
||||||
|
|
||||||
|
await click('[data-test-link="write-a-new-post"]');
|
||||||
|
|
||||||
|
expect(currentURL()).to.equal('/editor/post');
|
||||||
|
|
||||||
|
await fillIn('[data-test-editor-title-input]', 'First contributor post');
|
||||||
|
await blur('[data-test-editor-title-input]');
|
||||||
|
|
||||||
|
expect(currentURL()).to.equal('/editor/post/1');
|
||||||
|
|
||||||
|
await click('[data-test-link="posts"]');
|
||||||
|
|
||||||
|
expect(findAll('[data-test-post-id]')).to.have.length(1);
|
||||||
|
expect(find('[data-test-no-posts-box]')).to.not.exist;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
Loading…
Reference in New Issue
Block a user