mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 22:27:26 +01:00
Fix for search guilds result being inconsistent between "My Guilds" and "Discover Guilds" (#11903)
* Fix bug to allow guild summary and description to match against search term in MyGuilds component * Add unit test to groupUtilities to test filterGroup function * Changes made after running npm:run:lint * Fix bug when filter guild function does not match against guild size correctly when the guild has member count = 100 or 1000 According to habitica wiki Guilds Guide, gold-tier guilds are guilds with 1000 or more members. However, under the current code of filter guild function, it matches guilds as gold-tier as strictly more than 1000 members, excluding 1000 members. Similar silver-tier guilds should have 100 to 999 members, but the current code it matches guilds as silver-tier for members between 101 and 999 members. * Added unit tests to test the newly added code in the groupsUtilities mixin for the current issue * Add unit testing to test search guild name, summary, and description in myGuilds component * Add suggestions from lint * Added searching by guild summary and white space handling in search terms. For discover guilds component, added the following: 1) handling of searching by guild summary 2) preventing white space in search terms to display all guilds 3) added test cases for testing the search functionality in discove guilds to ensure consistent behaviour between the searching in MyGuilds and public guilds. * Remove console statements from test file * Implement suggestions from lint. Co-authored-by: osiris <eynsan@yahoo.co.uk>
This commit is contained in:
@@ -11,7 +11,9 @@ import apiError from '../../../../../website/server/libs/apiError';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
let user;
|
||||
let userInGuild;
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER = 2;
|
||||
const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1;
|
||||
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
|
||||
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
|
||||
@@ -33,14 +35,20 @@ describe('GET /groups', () => {
|
||||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'ohayou kombonwa',
|
||||
description: 'oyasumi',
|
||||
});
|
||||
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id] });
|
||||
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
|
||||
|
||||
userInGuild = await generateUser({ guilds: [publicGuildUserIsMemberOf._id] });
|
||||
|
||||
publicGuildNotMember = await generateGroup(leader, {
|
||||
name: 'public guild - is not member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
summary: 'Natsume Soseki',
|
||||
description: 'Kinnosuke no Hondana',
|
||||
categories,
|
||||
});
|
||||
|
||||
@@ -150,6 +158,35 @@ describe('GET /groups', () => {
|
||||
|
||||
expect(guilds.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('filters public guilds by leader role', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&leader=true');
|
||||
expect(guilds.length).to.equal(NUMBER_OF_PUBLIC_GUILDS_USER_IS_LEADER);
|
||||
});
|
||||
|
||||
it('filters public guilds by member role', async () => {
|
||||
const guilds = await userInGuild.get('/groups?type=publicGuilds&member=true');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].name).to.have.string('is member');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kom');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by single-word search term left and right-padded by spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=++++ohayou+kombonwa+++++');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].summary).to.have.string('ohayou kombonwa');
|
||||
});
|
||||
|
||||
it('filters public guilds by two-words search term separated by multiple spaces', async () => {
|
||||
const guilds = await user.get('/groups?type=publicGuilds&search=kinnosuke+++++hon');
|
||||
expect(guilds.length).to.equal(1);
|
||||
expect(guilds[0].description).to.have.string('Kinnosuke');
|
||||
});
|
||||
});
|
||||
|
||||
describe('public guilds pagination', () => {
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
import intersection from 'lodash/intersection';
|
||||
import _ from 'lodash';
|
||||
|
||||
const containsAnyCi = (target, patterns) => patterns.some(el => target.match(new RegExp(el, 'i')));
|
||||
const isPassedSearch = ({ name, summary, description }, search) => {
|
||||
if (!search) return false;
|
||||
|
||||
const searchWords = _.escapeRegExp(search.trim()).split(/\s+/);
|
||||
|
||||
if (containsAnyCi(name, searchWords)) return true;
|
||||
|
||||
if (!summary) return false;
|
||||
if (containsAnyCi(summary, searchWords)) return true;
|
||||
|
||||
if (!description) return false;
|
||||
if (containsAnyCi(description, searchWords)) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export default {
|
||||
filters: {
|
||||
@@ -56,9 +74,7 @@ export default {
|
||||
|
||||
if (group._id === this.$store.state.constants.TAVERN_ID || group._id === 'habitrpg') return false;
|
||||
|
||||
if (search) {
|
||||
passedSearch = group.name.toLowerCase().indexOf(search.toLowerCase()) >= 0;
|
||||
}
|
||||
if (search) passedSearch = isPassedSearch(group, search);
|
||||
|
||||
if (filters.categories && filters.categories.length > 0) {
|
||||
const intersectingCats = intersection(filters.categories, group.categorySlugs);
|
||||
@@ -75,11 +91,11 @@ export default {
|
||||
}
|
||||
|
||||
if (filters.guildSize && filters.guildSize.indexOf('gold_tier') !== -1) {
|
||||
correctSize = group.memberCount > 1000;
|
||||
correctSize = group.memberCount >= 1000;
|
||||
}
|
||||
|
||||
if (filters.guildSize && filters.guildSize.indexOf('silver_tier') !== -1) {
|
||||
correctSize = group.memberCount > 100 && group.memberCount < 1000;
|
||||
correctSize = group.memberCount >= 100 && group.memberCount < 1000;
|
||||
}
|
||||
|
||||
if (filters.guildSize && filters.guildSize.indexOf('bronze_tier') !== -1) {
|
||||
|
||||
107
website/client/tests/unit/components/groups/myGuilds.spec.js
Normal file
107
website/client/tests/unit/components/groups/myGuilds.spec.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import Store from '@/libs/store';
|
||||
import myGuilds from '@/components/groups/myGuilds';
|
||||
import PublicGuildItem from '@/components/groups/publicGuildItem';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Store);
|
||||
|
||||
describe('myGuilds component', () => {
|
||||
let computed;
|
||||
const guilds = [{
|
||||
_id: '1',
|
||||
type: 'guild',
|
||||
name: 'Crimson Vow',
|
||||
summary: 'testing',
|
||||
description: 'testing',
|
||||
}, {
|
||||
_id: '2',
|
||||
type: 'guild',
|
||||
name: 'Log Horizon',
|
||||
summary: 'testing',
|
||||
description: 'testing',
|
||||
}, {
|
||||
_id: '3',
|
||||
type: 'guild',
|
||||
name: 'CAD Cads',
|
||||
summary: '3D',
|
||||
description: '3D',
|
||||
}, {
|
||||
_id: '4',
|
||||
type: 'guild',
|
||||
name: 'Santa Claus',
|
||||
summary: '3d',
|
||||
description: 'hohoho',
|
||||
}];
|
||||
const store = new Store({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
_id: '999',
|
||||
guilds: ['1', '2', '3', '4'],
|
||||
},
|
||||
},
|
||||
editingGroup: {},
|
||||
constants: {
|
||||
TAVERN_ID: '9999',
|
||||
},
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
'guilds:getMyGuilds': () => guilds,
|
||||
},
|
||||
});
|
||||
|
||||
function makeWrapper (opts = {}) {
|
||||
return shallowMount(myGuilds, {
|
||||
data () {
|
||||
return {
|
||||
filter: {},
|
||||
search: '',
|
||||
};
|
||||
},
|
||||
store,
|
||||
localVue,
|
||||
...opts,
|
||||
});
|
||||
}
|
||||
|
||||
before(() => {
|
||||
computed = {
|
||||
guilds: () => guilds,
|
||||
};
|
||||
});
|
||||
|
||||
it('renders all guilds with no filter and no search', () => {
|
||||
const wrapper = makeWrapper({ computed });
|
||||
expect(wrapper.findAll(PublicGuildItem).length).to.equal(4);
|
||||
});
|
||||
|
||||
it('renders guilds with name matching against a single-word search term', () => {
|
||||
const search = 'vow';
|
||||
const wrapper = makeWrapper({ computed });
|
||||
wrapper.setData({ search });
|
||||
expect(wrapper.findAll(PublicGuildItem).length).to.equal(1);
|
||||
});
|
||||
|
||||
it('renders guilds with summary matching against a single-word search term', () => {
|
||||
const search = '3d';
|
||||
const wrapper = makeWrapper({ computed });
|
||||
wrapper.setData({ search });
|
||||
expect(wrapper.findAll(PublicGuildItem).length).to.equal(2);
|
||||
});
|
||||
|
||||
it('renders guilds with description matching against a single-word search term', () => {
|
||||
const search = 'hoho';
|
||||
const wrapper = makeWrapper({ computed });
|
||||
wrapper.setData({ search });
|
||||
expect(wrapper.findAll(PublicGuildItem).length).to.equal(1);
|
||||
});
|
||||
|
||||
it('renders guilds with summary matching against two search terms with space in between', () => {
|
||||
const search = '3d ohayou';
|
||||
const wrapper = makeWrapper({ computed });
|
||||
wrapper.setData({ search });
|
||||
expect(wrapper.findAll(PublicGuildItem).length).to.equal(2);
|
||||
});
|
||||
});
|
||||
@@ -61,4 +61,172 @@ describe('Groups Utilities Mixin', () => {
|
||||
})).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterGuild', () => {
|
||||
let testGroup;
|
||||
let testGroup2;
|
||||
|
||||
before(() => {
|
||||
testGroup = {
|
||||
type: 'guild',
|
||||
_id: user.guilds[0],
|
||||
name: 'Crimson Vow',
|
||||
summary: 'testing',
|
||||
description: 'dummy 1',
|
||||
leader: user.guilds[0], // test user is not guild leader
|
||||
categories: [{
|
||||
_id: '123',
|
||||
slug: 'hobbies_occupations',
|
||||
name: 'hobbies_occupations',
|
||||
}],
|
||||
categorySlugs: ['hobbies_occupations'],
|
||||
memberCount: 1000,
|
||||
};
|
||||
testGroup2 = {
|
||||
type: 'guild',
|
||||
_id: '790',
|
||||
name: 'CAD Cads',
|
||||
summary: '3D',
|
||||
description: 'My dummy',
|
||||
leader: user._id, // test user is guild leader
|
||||
categories: [{
|
||||
_id: '123',
|
||||
slug: 'hobbies_occupations',
|
||||
name: 'hobbies_occupations',
|
||||
}],
|
||||
categorySlugs: ['hobbies_occupations'],
|
||||
memberCount: 100,
|
||||
};
|
||||
});
|
||||
|
||||
it('returns true with no filter and no search', () => {
|
||||
const filter = {};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with no filter and one search word not matching against any of the guild name, summary, and description', () => {
|
||||
const filter = {};
|
||||
const search = '3d';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true with no filter and one search word matched successfully against guild name', () => {
|
||||
const filter = {};
|
||||
const search = 'vow';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no filter and one search word matched successfully against guild summary', () => {
|
||||
const filter = {};
|
||||
const search = 'test';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no filter and one search word matched successfully against guild description', () => {
|
||||
const filter = {};
|
||||
const search = 'dum';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no filter and two search words with two spaces in between matched successfully against guild name', () => {
|
||||
const filter = {};
|
||||
const search = 'cad test';
|
||||
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no filter and two search words with two spaces in between matched successfully against guild summary', () => {
|
||||
const filter = {};
|
||||
const search = 'cad 3d';
|
||||
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no filter and two search words with two spaces in between matched successfully against guild description', () => {
|
||||
const filter = {};
|
||||
const search = 'my dummy';
|
||||
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with no search word and one filter category that does not match against any guild categories', () => {
|
||||
const filter = {
|
||||
categories: ['academics'],
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true with no search word and one filter category that matches successfully against any guild categories', () => {
|
||||
const filter = {
|
||||
categories: ['hobbies_occupations'],
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with no search word and one filter role that does not match against guild role', () => {
|
||||
const filter = {
|
||||
roles: ['guild_leader'],
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true with no search word and one filter role that matches successfully against guild role', () => {
|
||||
const filter = {
|
||||
roles: ['member'],
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no search word and filter size silver tier that matches against a guild size of 1000, the max guild size belonging to silver tier', () => {
|
||||
const filter = {
|
||||
guildSize: 'gold_tier',
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true with no search word and filter size bronze tier that matches against a guild size of 100, the max guild size belonging to bronze tier', () => {
|
||||
const filter = {
|
||||
guildSize: 'silver_tier',
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with no search word and filter category that matches successfully against one guild category and filter role that does not match against guild role', () => {
|
||||
const filter = {
|
||||
categories: ['hobbies_occupations'],
|
||||
roles: ['guild_leader'],
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true with no search word and filter category that matches successfully against one guild category and filter role that matches successfully against guild role', () => {
|
||||
const filter = {
|
||||
categories: ['hobbies_occupations'],
|
||||
roles: ['guild_leader'],
|
||||
};
|
||||
const search = '';
|
||||
expect(instance.filterGuild(testGroup2, filter, search, user)).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false with one search word that does not match against guild name and one filter category that matches successfully against guild categories', () => {
|
||||
const filter = {
|
||||
categories: ['hobbies_occupations'],
|
||||
};
|
||||
const search = 'konnichiwa';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true with one search word that matches against guild name and one filter role that matches successfully against guild role', () => {
|
||||
const filter = {
|
||||
categories: ['hobbies_occupations'],
|
||||
};
|
||||
const search = 'vow';
|
||||
expect(instance.filterGuild(testGroup, filter, search, user)).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -349,9 +349,10 @@ api.getGroups = {
|
||||
|
||||
if (req.query.search) {
|
||||
filters.$or = [];
|
||||
const searchWords = _.escapeRegExp(req.query.search).split(' ').join('|');
|
||||
const searchWords = _.escapeRegExp(req.query.search.trim()).split(/\s+/).join('|');
|
||||
const searchQuery = { $regex: new RegExp(`${searchWords}`, 'i') };
|
||||
filters.$or.push({ name: searchQuery });
|
||||
filters.$or.push({ summary: searchQuery });
|
||||
filters.$or.push({ description: searchQuery });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user