1
0
Fork 0

Merge pull request #27 from algolia/test/formatHits

test(formatHits): Add tests for formatHits
This commit is contained in:
Tim Carry 2015-12-21 16:57:15 +01:00
commit 40ef992c5b
7 changed files with 432 additions and 26 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.DS_Store
node_modules/
dist/
npm-debug.log*

View file

@ -45,7 +45,6 @@
"mocha-jsdom": "^1.0.0",
"mversion": "^1.10.1",
"nd": "^1.2.0",
"node-fixtures": "0.0.1",
"node-sass": "^3.4.2",
"npm-shrinkwrap": "^200.4.0",
"onchange": "^2.0.0",

View file

@ -102,16 +102,19 @@ class DocSearch {
query: query,
params: this.algoliaOptions
}]).then((data) => {
callback(this.formatHits(data.results[0].hits));
callback(DocSearch.formatHits(data.results[0].hits));
});
};
}
// Given a list of hits returned by the API, will reformat them to be used in
// a Hogan template
formatHits(receivedHits) {
let hits = receivedHits.map((hit) => {
hit._highlightResult = utils.mergeKeyWithParent(hit._highlightResult, 'hierarchy');
static formatHits(receivedHits) {
let clonedHits = utils.deepClone(receivedHits);
let hits = clonedHits.map((hit) => {
if (hit._highlightResult) {
hit._highlightResult = utils.mergeKeyWithParent(hit._highlightResult, 'hierarchy');
}
return utils.mergeKeyWithParent(hit, 'hierarchy');
});
@ -140,7 +143,7 @@ class DocSearch {
return {
isCategoryHeader: hit.isCategoryHeader,
isSubcategoryHeader: hit.isSubcategoryHeader,
isSubCategoryHeader: hit.isSubCategoryHeader,
category: category,
subcategory: subcategory,
title: displayTitle,

View file

@ -24,10 +24,10 @@ let utils = {
*/
mergeKeyWithParent(object, property) {
if (object[property] === undefined) {
throw new Error(`[mergeKeyWithParent]: Object has no key ${property}`);
return object;
}
if (typeof object[property] !== 'object') {
throw new Error(`[mergeKeyWithParent]: Key ${property} is not an object`);
return object;
}
let newObject = $.extend({}, object, object[property]);
delete newObject[property];
@ -144,9 +144,11 @@ let utils = {
* @return {array}
*/
flattenAndFlagFirst(object, flag) {
let values = this.values(object).map(value => {
value[0][flag] = true;
return value;
let values = this.values(object).map(collection => {
return collection.map((item, index) => {
item[flag] = (index === 0);
return item;
});
});
return this.flatten(values);
},
@ -230,9 +232,16 @@ let utils = {
snippet = `${snippet}`;
}
return snippet;
},
/*
* Deep clone an object.
* Note: This will not clone functions and dates
* @param {object} object Object to clone
* @return {object}
*/
deepClone(object) {
return JSON.parse(JSON.stringify(object));
}
};
export default utils;

View file

@ -4,11 +4,11 @@
import jsdom from 'mocha-jsdom';
import expect from 'expect';
import sinon from 'sinon';
// import fixtures from 'node-fixtures';
describe('DocSearch', () => {
let DocSearch;
let $;
jsdom({useEach: true});
beforeEach(() => {
@ -309,4 +309,362 @@ describe('DocSearch', () => {
});
});
});
describe('formatHits', () => {
it('should not mutate the input', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(input).toNotBe(actual);
});
it('should set category headers to the first of each category', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Ruby',
lvl1: 'Geo-search',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Python',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].isCategoryHeader).toEqual(true);
expect(actual[2].isCategoryHeader).toEqual(true);
});
it('should group items of same category together', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Python',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Ruby',
lvl1: 'Geo-search',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].category).toEqual('Ruby');
expect(actual[1].category).toEqual('Ruby');
expect(actual[2].category).toEqual('Python');
});
it('should mark all first elements as subcategories', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Python',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Ruby',
lvl1: 'Geo-search',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].isSubCategoryHeader).toEqual(true);
expect(actual[2].isSubCategoryHeader).toEqual(true);
});
it('should mark new subcategories as such', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: 'Foo',
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Python',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: 'Bar',
lvl3: null,
lvl4: null,
lvl5: null
}
}, {
hierarchy: {
lvl0: 'Ruby',
lvl1: 'Geo-search',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].isSubCategoryHeader).toEqual(true);
expect(actual[1].isSubCategoryHeader).toEqual(false);
expect(actual[2].isSubCategoryHeader).toEqual(true);
expect(actual[3].isSubCategoryHeader).toEqual(true);
});
it('should use highlighted category and subcategory if exists', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: 'Foo',
lvl3: null,
lvl4: null,
lvl5: null
},
_highlightResult: {
hierarchy: {
lvl0: {
value: '<mark>Ruby</mark>'
},
lvl1: {
value: '<mark>API</mark>'
}
}
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].category).toEqual('<mark>Ruby</mark>');
expect(actual[0].subcategory).toEqual('<mark>API</mark>');
});
it('should use lvl2 as title', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: 'Foo',
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].title).toEqual('Foo');
});
it('should use lvl1 as title if no lvl2', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].title).toEqual('API');
});
it('should use lvl0 as title if no lvl2 nor lvl2', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: null,
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].title).toEqual('Ruby');
});
it('should concatenate lvl2+ for title if more', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: 'Geo-search',
lvl3: 'Foo',
lvl4: 'Bar',
lvl5: 'Baz'
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].title).toEqual('Geo-search Foo Bar Baz');
});
it('should concatenate highlighted elements', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: 'Geo-search',
lvl3: 'Foo',
lvl4: 'Bar',
lvl5: 'Baz'
},
_highlightResult: {
hierarchy: {
lvl0: {
value: '<mark>Ruby</mark>'
},
lvl1: {
value: '<mark>API</mark>'
},
lvl2: {
value: '<mark>Geo-search</mark>'
},
lvl3: {
value: '<mark>Foo</mark>'
},
lvl4: {
value: '<mark>Bar</mark>'
},
lvl5: {
value: '<mark>Baz</mark>'
}
}
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].title).toEqual('<mark>Geo-search</mark> <mark>Foo</mark> <mark>Bar</mark> <mark>Baz</mark>');
});
it('should add ellipsis to content', () => {
// Given
let input = [{
hierarchy: {
lvl0: 'Ruby',
lvl1: 'API',
lvl2: null,
lvl3: null,
lvl4: null,
lvl5: null
},
content: 'foo bar',
_snippetResult: {
content: {
value: 'lorem <mark>foo</mark> bar ipsum.'
}
}
}];
// When
let actual = DocSearch.formatHits(input);
// Then
expect(actual[0].text).toEqual('…lorem <mark>foo</mark> bar ipsum.');
});
});
});

View file

@ -23,7 +23,7 @@
"value": "API"
},
"lvl2": {
"value": "<mark>Search</mark>"
"value": "Search"
},
"lvl3": null,
"lvl4": null,

View file

@ -65,16 +65,17 @@ describe('utils', () => {
expect(actual.lvl0).toNotEqual(42);
expect(actual.lvl0).toEqual('bar');
});
it('should throw an error if no such key', () => {
it('should do nothing if no such key', () => {
// Given
let input = {
name: 'foo'
};
// When
expect(() => {
utils.mergeKeyWithParent(input, 'hierarchy');
}).toThrow(Error);
let actual = utils.mergeKeyWithParent(input, 'hierarchy');
// Then
expect(actual).toBe(input);
});
it('should throw an error if key is no an object', () => {
// Given
@ -84,9 +85,10 @@ describe('utils', () => {
};
// When
expect(() => {
utils.mergeKeyWithParent(input, 'hierarchy');
}).toThrow(Error);
let actual = utils.mergeKeyWithParent(input, 'hierarchy');
// Then
expect(actual).toBe(input);
});
});
@ -193,11 +195,11 @@ describe('utils', () => {
// Then
expect(actual).toEqual([
{name: 'Tim', category: 'dev', isTop: true},
{name: 'Vincent', category: 'dev'},
{name: 'AlexS', category: 'dev'},
{name: 'Vincent', category: 'dev', isTop: false},
{name: 'AlexS', category: 'dev', isTop: false},
{name: 'Ben', category: 'sales', isTop: true},
{name: 'Jeremy', category: 'sales'},
{name: 'AlexK', category: 'sales'}
{name: 'Jeremy', category: 'sales', isTop: false},
{name: 'AlexK', category: 'sales', isTop: false}
]);
});
});
@ -328,4 +330,38 @@ describe('utils', () => {
expect(actual).toEqual('This is an <mark>finished</mark> sentence…');
});
});
describe('deepClone', () => {
it('should create an object with the exact same value', () => {
// Given
let input = {
foo: {
bar: 'baz'
}
};
// When
let actual = utils.deepClone(input);
// Then
expect(actual.foo.bar).toEqual('baz');
});
it('should not change the initial object', () => {
// Given
let input = {
foo: {
bar: 'baz'
}
};
// When
let actual = utils.deepClone(input);
input.foo.bar = 42;
// Then
expect(input.foo.bar).toEqual(42);
expect(actual.foo.bar).toNotEqual(42);
expect(actual.foo.bar).toEqual('baz');
});
});
});