@rowboat/mongoose-fuzzy-searching
Version:
Mongoose fuzzy searching plugin
784 lines (675 loc) • 23.7 kB
JavaScript
const fuzzySearching = require('..');
const db = require('./support/db');
beforeAll(async () => {
await db.openConnection();
});
afterAll(async () => {
await db.closeConnection();
});
describe('fuzzySearch', () => {
describe('mongoose_fuzzy_searching without the right options', () => {
const Model = db.createSchema('without the right options', { name: String })(fuzzySearching, [
{
name: 'name',
minSize: 2,
},
]);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe' });
});
describe('Error handling', () => {
it('fuzzySearch() -> should return an TypeError when the first argument is undefined', () => {
expect(Model.fuzzySearch.bind(this)).toThrow(
'Fuzzy Search: First argument is mandatory and must be a string or an object.',
);
});
it('fuzzySearch() -> should return an TypeError when the first argument is a function', () => {
expect(Model.fuzzySearch.bind(this, () => {})).toThrow(
'Fuzzy Search: First argument is mandatory and must be a string or an object.',
);
});
});
});
describe('mongoose_fuzzy_searching without options and callback', () => {
const Model = db.createSchema('without options and callback', { name: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe' });
});
it('fuzzySearch() -> should return Promise', () => {
const result = Model.fuzzySearch('jo');
expect(result).toHaveProperty('then');
});
it('fuzzySearch() -> should find one user with empty string as first parameter', async () => {
const result = await Model.fuzzySearch('');
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with string as first parameter', async () => {
const result = await Model.fuzzySearch('jo');
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object with empty query as first parameter', async () => {
const result = await Model.fuzzySearch({ query: '' });
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object as first parameter', async () => {
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object with empty query as first parameter (with `prefixOnly`)', async () => {
const result = await Model.fuzzySearch({ query: '', prefixOnly: true });
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object as first parameter (with `prefixOnly`)', async () => {
const result = await Model.fuzzySearch({
query: 'Joe',
prefixOnly: true,
});
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching with options and without callback', () => {
const Model = db.createSchema('with options and without callback', {
name: String,
lastName: String,
})(fuzzySearching, [
{
name: 'name',
minSize: 2,
},
]);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe', lastName: 'Doe' });
});
it('fuzzySearch() -> should not be able to find users when the options searches for `lastName` with value `test`', async () => {
const result = await Model.fuzzySearch('jo', { lastName: 'test' });
expect(result).toHaveLength(0);
});
it('fuzzySearch() -> should be able to find users when the options searches for `lastName` with value `Doe`', async () => {
const result = await Model.fuzzySearch('jo', { lastName: 'Doe' });
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching with callback and without options', () => {
const Model = db.createSchema('with callback and without options', { name: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe' });
});
it('fuzzySearch() -> should return the results with callback', () => {
return new Promise((done) => {
Model.fuzzySearch('jo', (err, doc) => {
expect(err).toBe(null);
expect(doc).toHaveLength(1);
done(err);
});
});
});
});
describe('mongoose_fuzzy_searching with options and callback', () => {
const Model = db.createSchema('with options and callback', { name: String, lastName: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe', lastName: 'Doe' });
});
it('fuzzySearch() -> should not be able to find users when the options searches for `lastName` with value `test` and return the result with callback', () => {
return new Promise((done) => {
Model.fuzzySearch('jo', { lastName: 'test' }, (err, doc) => {
expect(err).toBe(null);
expect(doc).toHaveLength(0);
done(err);
});
});
});
it('fuzzySearch() -> should not be able to find users when the options searches for `lastName` with value `Doe` and return the result with callback', () => {
return new Promise((done) => {
Model.fuzzySearch('jo', { lastName: 'Doe' }, (err, doc) => {
expect(err).toBe(null);
expect(doc).toHaveLength(1);
done(err);
});
});
});
});
describe('mongoose_fuzzy_searching with `keys` attribute in fields', () => {
describe('mongoose_fuzzy_searching with array of objects attribute', () => {
const Model = db.createSchema('keys with array of objects attribute', {
texts: [
{
title: String,
description: String,
language: String,
},
],
})(fuzzySearching, [
{
name: 'texts',
escapeSpecialCharacters: false,
keys: ['title', 'language'],
},
]);
beforeAll(async () => {
await db.seed(Model, {
texts: [
{
title: 'this is a title',
description: 'descr!',
language: 'en',
},
{
title: 'awesome!',
description: 'descr!',
language: 'el',
},
],
});
});
it('fuzzySearch() -> should be able to find the title when the text is `this is`', async () => {
const result = await Model.fuzzySearch('this is');
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching with single object attribute', () => {
const Model = db.createSchema('keys with single object attribute', {
title: {
en: String,
de: String,
it: String,
},
})(fuzzySearching, [
{
name: 'title',
escapeSpecialCharacters: false,
keys: ['en', 'de', 'it'],
minSize: 3,
},
]);
beforeAll(async () => {
await db.seed(Model, {
title: {
en: 'start wars',
de: 'Krieg der Sterne',
it: 'guerre stellari',
},
});
});
it('fuzzySearch() -> should be able to find the title when the text is `stellari`', async () => {
const ItalianResult = await Model.fuzzySearch('ste');
const GermanResult = await Model.fuzzySearch('kri');
const EnglishResult = await Model.fuzzySearch('war');
expect(ItalianResult).toHaveLength(1);
expect(GermanResult).toHaveLength(1);
expect(EnglishResult).toHaveLength(1);
});
});
});
describe('mongoose_fuzzy_searching with `exact` option', () => {
describe('mongoose_fuzzy_searching with array of objects attribute', () => {
const Model = db.createSchema('exact with array of objects attribute', {
name: String,
})(fuzzySearching, ['name']);
beforeAll(async () => {
await Model.insertMany([{ name: 'Peter Pan' }, { name: 'Peter Ofori-Quaye' }]);
});
it('fuzzySearch() -> should be able to find the title when the text is `Peter Pan`', async () => {
const exactResult = await Model.fuzzySearch({ query: 'Peter Pan', exact: true });
const fuzzyResult = await Model.fuzzySearch({ query: 'Peter Pan' });
expect(exactResult).toHaveLength(1);
expect(fuzzyResult).toHaveLength(2);
});
});
describe('mongoose_fuzzy_searching with single object attribute', () => {
const Model = db.createSchema('exact with single object attribute', {
title: [
{
en: String,
de: String,
it: String,
},
],
})(fuzzySearching, [
{
name: 'title',
escapeSpecialCharacters: false,
keys: ['en', 'de', 'it'],
},
]);
beforeAll(async () => {
await db.seed(Model, {
title: {
en: 'start wars',
de: 'Krieg der Sterne',
it: 'guerre stellari',
},
});
});
it('fuzzySearch() -> should be able to find the title when the text is `stellari`', async () => {
const result = await Model.fuzzySearch('stellari');
expect(result).toHaveLength(1);
});
});
});
describe('mongoose_fuzzy_searching should run user provided toObject & toJSON', () => {
const Model = db.createSchema(
'toObject and toJSON',
{ name: String },
{
toObject: {
transform(doc, ret) {
// eslint-disable-next-line no-param-reassign
ret.toObjectTest = true;
return ret;
},
},
toJSON: {
transform(doc, ret) {
// eslint-disable-next-line no-param-reassign
ret.toJSONTest = true;
return ret;
},
},
},
)(fuzzySearching, [
{
name: 'name',
minSize: 2,
},
]);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe' });
});
it("Should run user provided toObject and fuzzy searching's toObject", async () => {
const result = await Model.fuzzySearch('Joe');
expect(result).toHaveLength(1);
expect(result[0].toObject()).toHaveProperty('toObjectTest');
expect(result[0].toObject()).not.toHaveProperty('name_fuzzy');
});
it("Should run user provided toJSON and fuzzy searching's toJSON", async () => {
const result = await Model.fuzzySearch('Joe');
expect(result).toHaveLength(1);
expect(result[0].toJSON()).toHaveProperty('toJSONTest');
expect(result[0].toJSON()).not.toHaveProperty('name_fuzzy');
});
});
describe('mongoose_fuzzy_searching with array of strings', () => {
const Model = db.createSchema('with array of strings', {
tags: [String],
})(fuzzySearching, ['tags']);
beforeAll(async () => {
await db.seed(Model, {
tags: ['nature', 'fire', 'beauty', 'tenten'],
});
await db.seed(Model, {
tags: ['test1', 'test2', 'test3'],
});
});
it('fuzzySearch() -> should be able to find the tag when the text is `nature`', async () => {
const result = await Model.fuzzySearch('natu');
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should be able to return empty array when the text is `bollocks`', async () => {
const result = await Model.fuzzySearch('bollocks');
expect(result).toHaveLength(0);
});
});
describe('mongoose_fuzzy_searching pre middlewares', () => {
describe('mongoose_fuzzy_searching update user with `findOneAndUpdate`', () => {
const Model = db.createSchema('pre update user with findOneAndUpdate', {
name: String,
age: Number,
})(fuzzySearching, [
{
name: 'name',
minSize: 2,
},
]);
beforeAll(async () => {
const obj = await db.seed(Model, { name: 'Joe' });
await Model.findOneAndUpdate({ _id: obj._id }, { age: 30 });
});
it('fuzzySearch() -> should return Promise', () => {
const result = Model.fuzzySearch('jo');
expect(result).toHaveProperty('then');
});
it('fuzzySearch() -> should find one user with string as first parameter', async () => {
const result = await Model.fuzzySearch('jo');
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object as first parameter', async () => {
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching update user with `update`', () => {
const Model = db.createSchema('pre update user with update', { name: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
const obj = await db.seed(Model, { name: 'Joe' });
await Model.update({ _id: obj._id }, { name: 'Someone' });
});
it('fuzzySearch() -> should return Promise', () => {
const result = Model.fuzzySearch('some');
expect(result).toHaveProperty('then');
});
it('fuzzySearch() -> should find one user with string as first parameter', async () => {
const result = await Model.fuzzySearch('some');
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object as first parameter', async () => {
const result = await Model.fuzzySearch({ query: 'some' });
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching update user with `updateOne`', () => {
const Model = db.createSchema('pre update user with updateOne', { name: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
const obj = await db.seed(Model, { name: 'Joe' });
await Model.updateOne({ _id: obj._id }, { name: 'Someone' });
});
it('fuzzySearch() -> should return Promise', () => {
const result = Model.fuzzySearch('some');
expect(result).toHaveProperty('then');
});
it('fuzzySearch() -> should find one user with string as first parameter', async () => {
const result = await Model.fuzzySearch('some');
expect(result).toHaveLength(1);
});
it('fuzzySearch() -> should find one user with object as first parameter', async () => {
const result = await Model.fuzzySearch({ query: 'some' });
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching insert users with `insertMany`', () => {
const Model = db.createSchema('pre insert user with insertMany', { name: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
await Model.insertMany([{ name: 'Vassilis' }, { name: 'Dimitris' }]);
});
it('fuzzySearch() -> should return Promise', () => {
const result = Model.fuzzySearch('vassi');
expect(result).toHaveProperty('then');
});
it('fuzzySearch() -> should find one user with string as first parameter', async () => {
const result = await Model.fuzzySearch('vassi');
expect(result).toHaveLength(1);
});
});
describe('mongoose_fuzzy_searching update users with `updateMany`', () => {
const Model = db.createSchema('pre update user with updateMany', { name: String })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
await Model.insertMany([{ name: 'Vassilis' }, { name: 'Dimitris' }]);
await Model.updateMany({ name: 'Vassilis' }, { name: 'Pallas' });
});
it('fuzzySearch() -> should return Promise', () => {
const result = Model.fuzzySearch('pall');
expect(result).toHaveProperty('then');
});
it('fuzzySearch() -> should find one user with string as first parameter', async () => {
const result = await Model.fuzzySearch('pall');
expect(result).toHaveLength(1);
});
});
});
describe('mongoose_fuzzy_searching custom `pre` middlewares', () => {
const schema = { name: String, age: Number, skill: String };
it('should call `preSave`', async () => {
const preSave = jest.fn().mockImplementation(function () {
this.skill = 'amazing';
});
const Model = db.createSchema('custom pre preSave', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preSave,
},
);
await db.seed(Model, { name: 'Joe', age: 30 });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preSave).toHaveBeenCalledTimes(1);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
it('should call `preUpdate`', async () => {
const preUpdate = jest.fn().mockImplementation(function () {});
const Model = db.createSchema('custom pre preUpdate', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preUpdate,
},
);
const obj = await db.seed(Model, { name: 'Joe', age: 30 });
await Model.update({ _id: obj._id }, { skill: 'amazing' });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preUpdate).toHaveBeenCalledTimes(1);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
it('should call `preFindOneAndUpdate`', async () => {
const preFindOneAndUpdate = jest.fn().mockImplementation(function () {});
const Model = db.createSchema('custom pre preFindOneAndUpdate', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preFindOneAndUpdate,
},
);
const obj = await db.seed(Model, { name: 'Joe', age: 30 });
await Model.findByIdAndUpdate(obj._id, { skill: 'amazing' });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preFindOneAndUpdate).toHaveBeenCalledTimes(1);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
it('should call `preInsertMany`', async () => {
const preInsertMany = jest.fn().mockImplementation(function (docs) {
docs.forEach((doc) => {
doc.skill = 'amazing';
});
});
const Model = db.createSchema('custom pre preInsertMany', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preInsertMany,
},
);
await Model.insertMany([
{ name: 'Joe', age: 30 },
{ name: 'Doe', age: 26 },
]);
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preInsertMany).toHaveBeenCalledTimes(1);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
it('should call `preUpdateMany`', async () => {
const preUpdateMany = jest.fn().mockImplementation(function () {});
const Model = db.createSchema('custom pre preUpdateMany', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preUpdateMany,
},
);
await Model.insertMany([
{ name: 'Joe', age: 30 },
{ name: 'Doe', age: 26 },
]);
await Model.updateMany({ name: 'Doe' }, { name: 'Foe' });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preUpdateMany).toHaveBeenCalledTimes(1);
});
it('should call `preUpdateOne`', async () => {
const preUpdateOne = jest.fn().mockImplementation(function () {});
const Model = db.createSchema('custom pre preUpdateOne', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preUpdateOne,
},
);
const obj = await db.seed(Model, { name: 'Joe', age: 30 });
await Model.updateOne({ _id: obj._id }, { skill: 'amazing' });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preUpdateOne).toHaveBeenCalledTimes(1);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
it('should call `preSave` and `preUpdate`', async () => {
const preUpdate = jest.fn().mockImplementation(function () {});
const preSave = jest.fn().mockImplementation(function () {});
const Model = db.createSchema('custom pre preSave and preUpdate', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preSave,
preUpdate,
},
);
const obj = await db.seed(Model, { name: 'Joe', age: 30 });
await Model.update({ _id: obj._id }, { skill: 'amazing' });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preSave).toHaveBeenCalledTimes(1);
expect(preUpdate).toHaveBeenCalledTimes(1);
// expect preSave to be called before preUpdate
expect(preSave.mock.invocationCallOrder[0]).toBeLessThan(
preUpdate.mock.invocationCallOrder[0],
);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
it('should call promise', async () => {
const preSave = jest.fn().mockImplementation(async function () {
return new Promise((resolve) => {
setTimeout(() => {
this.skill = 'amazing';
resolve();
}, 2000);
});
});
const Model = db.createSchema('custom pre preSave promise', schema)(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
{
preSave,
},
);
await db.seed(Model, { name: 'Joe', age: 30 });
const result = await Model.fuzzySearch({ query: 'jo' });
expect(result).toHaveLength(1);
expect(preSave).toHaveBeenCalledTimes(1);
expect(result[0]).toHaveProperty('skill', 'amazing');
});
});
describe('mongoose_fuzzy_searching with query helper', () => {
const Model = db.createSchema('with query helper', { name: String, age: Number })(
fuzzySearching,
[
{
name: 'name',
minSize: 2,
},
],
);
beforeAll(async () => {
await db.seed(Model, { name: 'Joe', age: 30 });
});
it('fuzzySearch() -> should return the results by chaing queries', async () => {
const result = await Model.find({ age: { $gte: 30 } }).fuzzySearch('jo');
expect(result).toHaveLength(1);
});
});
});