Added acceptance test for members search

refs https://github.com/TryGhost/Team/issues/1336
refs bf9bbc3aa6

- adds basic search param handling to `GET /members` API mock
- adds acceptance tests for basic search behaviour
- contains regression check for search input not being visible when no members match
This commit is contained in:
Kevin Ansfield 2022-02-14 16:23:50 +00:00
parent 450182edf4
commit 995673ad3c
3 changed files with 148 additions and 9 deletions

View File

@ -16,6 +16,7 @@
{{on "focus" (fn (mut this.searchIsFocused) true)}}
{{on "blur" (fn (mut this.searchIsFocused) false)}}
{{will-destroy (fn (mut this.searchIsFocused) false)}}
data-test-input="members-search"
/>
</div>
</div>
@ -54,15 +55,15 @@
</li>
<li class="{{if this.members.length "" "disabled"}}">
{{#if this.members.length}}
<button class="mr2" type="button" {{on "click" this.exportData}}>
<button class="mr2" type="button" {{on "click" this.exportData}} data-test-button="export-members">
{{#if this.showingAll}}
<span>Export all members</span>
{{else}}
<span>Export selected members ({{this.members.length}})</span>
<span>Export selected members ({{this.members.length}})</span>
{{/if}}
</button>
{{else}}
<button class="mr2" disabled="true" type="button">
<button class="mr2" disabled="true" type="button" data-test-button="export-members">
<span>Export selected members (0)</span>
</button>
{{/if}}
@ -80,7 +81,7 @@
</button>
</li>
<li>
<button class="mr2" data-test-button="remove-label-selected" type="button" {{on "click" this.bulkUnsubscribe}}>
<button class="mr2" data-test-button="unsubscribe-selected" type="button" {{on "click" this.bulkUnsubscribe}}>
<span>Unsubscribe selected members ({{this.members.length}})</span>
</button>
</li>
@ -106,7 +107,7 @@
{{else}}
<section class="view-container {{if (or (not this.members) (lt this.members.length 6)) "members-list-container-stretch"}}">
{{#if this.members}}
<div class="gh-list-scrolling {{if (lt this.members.length 6) "gh-list-with-helpsection"}}">
<div class="gh-list-scrolling {{if (lt this.members.length 6) "gh-list-with-helpsection"}}" daat-test-table="members">
<table class="gh-list">
<thead>
<tr>
@ -137,10 +138,10 @@
{{#if this.showingAll}}
<GhMembersNoMembers @afterCreate={{this.refreshData}} @members={{this.members}} />
{{else}}
<div class="gh-members-empty">
<div class="gh-members-empty" data-test-no-matching-members>
{{svg-jar "members-placeholder" class="gh-members-placeholder"}}
<h4>No members match the current filter</h4>
<LinkTo @route="members" @query={{reset-query-params "members.index"}} class="gh-btn mt4">
<LinkTo @route="members" @query={{reset-query-params "members.index"}} class="gh-btn mt4" data-test-button="show-all-members">
<span>Show all members</span>
</LinkTo>
</div>

View File

@ -68,12 +68,12 @@ export default function mockMembers(server) {
});
server.get('/members/', function ({members}, {queryParams}) {
let {filter, page, limit} = queryParams;
let {filter, search, page, limit} = queryParams;
page = +page || 1;
limit = +limit || 15;
let labelFilter = extractFilterParam('label', filter);
const labelFilter = extractFilterParam('label', filter);
let collection = members.all().filter((member) => {
let matchesLabel = true;
@ -91,6 +91,15 @@ export default function mockMembers(server) {
return matchesLabel;
});
if (search) {
const query = search.toLowerCase();
collection = collection.filter((member) => {
return member.name.toLowerCase().indexOf(query) !== -1
|| member.email.toLowerCase().indexOf(query) !== -1;
});
}
return paginateModelCollection('members', collection, page, limit);
});

View File

@ -0,0 +1,129 @@
import {authenticateSession} from 'ember-simple-auth/test-support';
import {click, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit';
describe('Acceptance: Members filtering', function () {
let hooks = setupApplicationTest();
setupMirage(hooks);
beforeEach(async function () {
this.server.loadFixtures('configs');
let role = this.server.create('role', {name: 'Owner'});
this.server.create('user', {roles: [role]});
return await authenticateSession();
});
it('has a known base-state', async function () {
this.server.createList('member', 7);
await visit('/members');
// members are listed
expect(findAll('[data-test-list="members-list-item"]').length, '# of member rows').to.equal(7);
// export is available
expect(find('[data-test-button="export-members"]'), 'export members button').to.exist;
expect(find('[data-test-button="export-members"]'), 'export members button').to.not.have.attribute('disabled');
// bulk actions are hidden
expect(find('[data-test-button="add-label-selected"]'), 'add label to selected button').to.not.exist;
expect(find('[data-test-button="remove-label-selected"]'), 'remove label from selected button').to.not.exist;
expect(find('[data-test-button="unsubscribe-selected"]'), 'unsubscribe selected button').to.not.exist;
expect(find('[data-test-button="delete-selected"]'), 'delete selected button').to.not.exist;
// filter and search are inactive
expect(find('[data-test-input="members-search"]'), 'search input').to.exist;
expect(find('[data-test-input="members-search"]'), 'search input').to.not.have.class('active');
expect(find('[data-test-button="members-filter-actions"]'), 'filter button').to.not.have.class('gh-btn-label-green');
});
describe('search', function () {
beforeEach(function () {
// specific member names+emails so search is deterministic
// (default factory has random names+emails)
this.server.create('member', {name: 'X', email: 'x@x.xxx'});
this.server.create('member', {name: 'Y', email: 'y@y.yyy'});
this.server.create('member', {name: 'Z', email: 'z@z.zzz'});
});
it('works', async function () {
await visit('/members');
expect(findAll('[data-test-list="members-list-item"]').length, '# of initial member rows')
.to.equal(3);
await fillIn('[data-test-input="members-search"]', 'X');
// list updates
expect(findAll('[data-test-list="members-list-item"]').length, '# of members matching "X"')
.to.equal(1);
// URL reflects search
expect(currentURL()).to.equal('/members?search=X');
// search input is active
expect(find('[data-test-input="members-search"]')).to.have.class('active');
// bulk actions become available
expect(find('[data-test-button="add-label-selected"]'), 'add label to selected button').to.exist;
expect(find('[data-test-button="remove-label-selected"]'), 'remove label from selected button').to.exist;
expect(find('[data-test-button="unsubscribe-selected"]'), 'unsubscribe selected button').to.exist;
expect(find('[data-test-button="delete-selected"]'), 'delete selected button').to.exist;
// clearing search returns us to starting state
await fillIn('[data-test-input="members-search"]', '');
expect(findAll('[data-test-list="members-list-item"]').length, '# of members after clearing search')
.to.equal(3);
expect(find('[data-test-input="members-search"]')).to.not.have.class('active');
});
it('populates from query param', async function () {
await visit('/members?search=Y');
expect(findAll('[data-test-list="members-list-item"]').length, '# of initial member rows')
.to.equal(1);
expect(find('[data-test-input="members-search"]')).to.have.value('Y');
expect(find('[data-test-input="members-search"]')).to.have.class('active');
});
it('has an empty state', async function () {
await visit('/members');
await fillIn('[data-test-input="members-search"]', 'unknown');
expect(currentURL()).to.equal('/members?search=unknown');
// replaces members table with the no-matching members state
expect(find('[data-test-table="members"]')).to.not.exist;
expect(find('[data-test-no-matching-members]')).to.exist;
// search input is still shown
expect(find('[data-test-input="members-search"]')).to.be.visible;
expect(find('[data-test-input="members-search"]')).to.have.class('active');
// export is disabled
expect(find('[data-test-button="export-members"]')).to.have.attribute('disabled');
// bulk actions are hidden
expect(find('[data-test-button="add-label-selected"]')).to.not.exist;
expect(find('[data-test-button="remove-label-selected"]')).to.not.exist;
expect(find('[data-test-button="unsubscribe-selected"]')).to.not.exist;
expect(find('[data-test-button="delete-selected"]')).to.not.exist;
// can clear the search
await click('[data-test-no-matching-members] [data-test-button="show-all-members"]');
expect(currentURL()).to.equal('/members');
expect(find('[data-test-input="members-search"]')).to.have.value('');
expect(find('[data-test-input="members-search"]')).to.not.have.class('active');
expect(findAll('[data-test-list="members-list-item"]').length).to.equal(3);
});
});
});