Skip to content

Commit 5ee91c1

Browse files
authored
feat(BBD-628): Add transformRefinements (#77)
1 parent 22e4d7c commit 5ee91c1

3 files changed

Lines changed: 183 additions & 40 deletions

File tree

src/core/bridge.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as axios from 'axios';
22
import { Request } from '../models/request';
3-
import { Record, RefinementResults, Results } from '../models/response';
3+
import { Navigation, RangeRefinement, Record, RefinementResults, Results } from '../models/response';
44
import { Query } from './query';
55

66
const SEARCH = '/search';
@@ -43,7 +43,8 @@ export abstract class AbstractBridge {
4343
if (request === null) return this.generateError(INVALID_QUERY_ERROR, callback);
4444

4545
const response = this.fireRequest(this.bridgeUrl, request, queryParams)
46-
.then((res) => res.records ? Object.assign(res, { records: res.records.map(this.convertRecordFields) }) : res);
46+
.then(AbstractBridge.transformRecords)
47+
.then(AbstractBridge.transformRefinements);
4748
return this.handleResponse(response, callback);
4849
}
4950

@@ -106,7 +107,24 @@ export abstract class AbstractBridge {
106107
});
107108
}
108109

109-
private convertRecordFields(record: RawRecord): Record | RawRecord {
110+
static transform(response: any, key: string, callback: Function) {
111+
if (response[key]) {
112+
return Object.assign(response, { [key]: response[key].map(callback) });
113+
} else {
114+
return response;
115+
}
116+
}
117+
118+
static transformRecords(response: any) {
119+
return AbstractBridge.transform(response, 'records', AbstractBridge.convertRecordFields);
120+
}
121+
122+
static transformRefinements(response: any) {
123+
const transformed = AbstractBridge.transform(response, 'availableNavigation', AbstractBridge.convertRefinement);
124+
return AbstractBridge.transform(transformed, 'selectedNavigation', AbstractBridge.convertRefinement);
125+
}
126+
127+
static convertRecordFields(record: RawRecord): Record | RawRecord {
110128
const converted = Object.assign(record, { id: record._id, url: record._u, title: record._t });
111129
delete converted._id;
112130
delete converted._u;
@@ -119,6 +137,19 @@ export abstract class AbstractBridge {
119137

120138
return converted;
121139
}
140+
141+
static convertRefinement(navigation: Navigation): Navigation {
142+
if (navigation.range) {
143+
navigation.refinements = navigation.refinements
144+
.map((ref: RangeRefinement) => ({
145+
...ref,
146+
high: parseFloat(<any>ref.high),
147+
low: parseFloat(<any>ref.low)
148+
}));
149+
}
150+
151+
return navigation;
152+
}
122153
}
123154

124155
export class CloudBridge extends AbstractBridge {

test/unit/core/bridge.ts

Lines changed: 128 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as mock from 'xhr-mock';
2-
import { BrowserBridge, CloudBridge } from '../../../src/core/bridge';
2+
import { AbstractBridge, BrowserBridge, CloudBridge } from '../../../src/core/bridge';
33
import { Query } from '../../../src/core/query';
44
import suite from '../_suite';
55

66
const CLIENT_KEY = 'XXX-XXX-XXX-XXX';
77
const CUSTOMER_ID = 'services';
88

9-
suite('Bridge', ({ expect, spy }) => {
9+
suite('Bridge', ({ expect, spy, stub }) => {
1010
let bridge;
1111
let query;
1212

@@ -45,7 +45,7 @@ suite('Bridge', ({ expect, spy }) => {
4545
}));
4646
});
4747

48-
it('should be accept a direct query string', () => {
48+
it('should accept a direct query string', () => {
4949
mock.post(`https://${CUSTOMER_ID}.groupbycloud.com:443/api/v1/search`, (req, res) => {
5050
const body = JSON.parse(req.body());
5151
expect(body.query).to.eq('skirts');
@@ -57,7 +57,7 @@ suite('Bridge', ({ expect, spy }) => {
5757
return bridge.search('skirts');
5858
});
5959

60-
it('should be accept a raw request', () => {
60+
it('should accept a raw request', () => {
6161
mock.post(`https://${CUSTOMER_ID}.groupbycloud.com:443/api/v1/search`, (req, res) => {
6262
const body = JSON.parse(req.body());
6363
expect(body.fields).to.eql(['title', 'description']);
@@ -140,33 +140,18 @@ suite('Bridge', ({ expect, spy }) => {
140140
});
141141
});
142142

143-
it('should invoke any configured errorHandler on error', (done) => {
144-
mock.post(`https://${CUSTOMER_ID}.groupbycloud.com:443/api/v1/search`, (req, res) => {
145-
return res.status(400).body('error');
146-
});
147-
query = new Query('shoes');
148-
bridge.errorHandler = (err) => {
149-
expect(err.data).to.eq('error');
150-
expect(err.status).to.eq(400);
151-
done();
152-
};
153-
154-
bridge.search(query);
155-
});
156-
157-
it('should invoke any configured errorHandler on error and allow downstream promise catching', (done) => {
143+
it('should invoke any configured errorHandler on error and allow downstream promise catching', () => {
158144
const errorHandler = bridge.errorHandler = spy();
159145
mock.post(`https://${CUSTOMER_ID}.groupbycloud.com:443/api/v1/search`, (req, res) => {
160146
return res.status(400).body('error');
161147
});
162148
query = new Query('shoes');
163149

164-
bridge.search(query)
150+
return bridge.search(query)
165151
.catch((err) => {
166152
expect(err.data).to.eq('error');
167153
expect(err.status).to.eq(400);
168154
expect(errorHandler).to.be.calledWith(err);
169-
done();
170155
});
171156
});
172157

@@ -185,6 +170,33 @@ suite('Bridge', ({ expect, spy }) => {
185170
done();
186171
});
187172
});
173+
174+
it('should send HTTPS request', (done) => {
175+
mock.post(`https://${CUSTOMER_ID}-cors.groupbycloud.com:443/api/v1/search`, (req, res) => {
176+
return res.status(200).body('success');
177+
});
178+
179+
query = new Query('shoes');
180+
181+
new BrowserBridge(CUSTOMER_ID)
182+
.search(query, (err, results) => done());
183+
});
184+
185+
it('should include headers', (done) => {
186+
const headers = { a: 'b' };
187+
mock.post(`http://${CUSTOMER_ID}-cors.groupbycloud.com/api/v1/search`, (req, res) => {
188+
expect(req['_headers']).to.include.keys('a');
189+
return res.status(200).body('success');
190+
});
191+
192+
query = new Query('shoes');
193+
194+
Object.assign(new BrowserBridge(CUSTOMER_ID), { headers })
195+
.search(query, (err, results) => {
196+
expect(results).to.eq('success');
197+
done();
198+
});
199+
});
188200
});
189201

190202
describe('refinements()', () => {
@@ -203,32 +215,111 @@ suite('Bridge', ({ expect, spy }) => {
203215
});
204216
});
205217
});
218+
});
219+
220+
describe('AbstractBridge', () => {
221+
describe('transform()', () => {
222+
it('should execute callback on properties of key', () => {
223+
const key = [{ c: 'd' }, { e: 'f' }, { g: 'h' }];
224+
const response = <any>{ key, a: 'b' };
225+
const callback = spy((obj) => ({ ...obj, value: 'test' }));
226+
const result = { ...response, key: [callback(key[0]), callback(key[1]), callback(key[2])] };
206227

207-
it('should send HTTPS request', (done) => {
208-
mock.post(`https://${CUSTOMER_ID}-cors.groupbycloud.com:443/api/v1/search`, (req, res) => {
209-
return res.status(200).body('success');
228+
expect(AbstractBridge.transform(response, 'key', callback)).to.eql(result);
210229
});
230+
});
211231

212-
query = new Query('shoes');
232+
describe('transformRecords()', () => {
233+
it('should return transform() with response, records, and convertRecordFields', () => {
234+
const response = { a: 'b' };
235+
const returnValue = { c: 'd' };
236+
const transform = stub(AbstractBridge, 'transform')
237+
.withArgs(response, 'records', AbstractBridge.convertRecordFields).returns(returnValue);
213238

214-
new BrowserBridge(CUSTOMER_ID)
215-
.search(query, (err, results) => done());
239+
expect(AbstractBridge.transformRecords(response)).to.eq(returnValue);
240+
});
216241
});
217242

218-
it('should include headers', (done) => {
219-
const headers = { a: 'b' };
220-
mock.post(`http://${CUSTOMER_ID}-cors.groupbycloud.com/api/v1/search`, (req, res) => {
221-
expect(req['_headers']).to.include.keys('a');
222-
return res.status(200).body('success');
243+
describe('transformRefinements()', () => {
244+
it('should return transform() with response, records, and convertRecordFields', () => {
245+
const response = { a: 'b' };
246+
const transformedValue = { c: 'd' };
247+
const returnValue = { e: 'f' };
248+
const transform = stub(AbstractBridge, 'transform');
249+
transform.withArgs(response, 'availableNavigation', AbstractBridge.convertRefinement).returns(transformedValue);
250+
// tslint:disable-next-line max-line-length
251+
transform.withArgs(transformedValue, 'selectedNavigation', AbstractBridge.convertRefinement).returns(returnValue);
252+
253+
AbstractBridge.transformRefinements(response);
254+
expect(AbstractBridge.transformRefinements(response)).to.eq(returnValue);
223255
});
256+
});
224257

225-
query = new Query('shoes');
258+
describe('convertRecordFields()', () => {
259+
it('should update properties', () => {
260+
const id = '1239';
261+
const url = 'www.whateva.ca';
262+
const title = 'my stuff';
263+
const snippet = 'my snippet';
264+
const record = <any>{
265+
a: 'b',
266+
_id: id,
267+
_u: url,
268+
_t: title,
269+
_snippet: snippet
270+
};
271+
const transformedRecord = { a: 'b', id, url, title, snippet };
272+
273+
expect(BrowserBridge.convertRecordFields(record)).to.eql(transformedRecord);
274+
});
275+
});
226276

227-
Object.assign(new BrowserBridge(CUSTOMER_ID), { headers })
228-
.search(query, (err, results) => {
229-
expect(results).to.eq('success');
230-
done();
277+
describe('convertRefinement()', () => {
278+
it('should update value types', () => {
279+
let refinements = [
280+
{ type: 'Range', count: 49, high: '28.0', low: '0.0' },
281+
{ type: 'Range', count: 479, high: '56.0', low: '28.0' },
282+
{ type: 'Range', count: 1348, high: '84.0', low: '56.0' },
283+
];
284+
const navigation = <any>{
285+
name: 'Department',
286+
displayName: 'All',
287+
type: 'Range',
288+
range: true,
289+
or: false,
290+
refinements,
291+
metadata: []
292+
};
293+
const convertedRefinements = [
294+
{ type: 'Range', count: 49, high: 28.0, low: 0.0 },
295+
{ type: 'Range', count: 479, high: 56.0, low: 28.0 },
296+
{ type: 'Range', count: 1348, high: 84.0, low: 56.0 },
297+
];
298+
299+
expect(BrowserBridge.convertRefinement(navigation)).to.eql({
300+
...navigation,
301+
refinements: convertedRefinements
231302
});
303+
});
304+
305+
it('should not update value types', () => {
306+
let refinements = [
307+
{type: 'Value', count: 975, value: 'Black'},
308+
{type: 'Value', count: 1064, value: 'Blue'},
309+
{type: 'Value', count: 81, value: 'Brown'}
310+
];
311+
const navigation = <any>{
312+
name: 'Department',
313+
displayName: 'All',
314+
type: 'Value',
315+
range: false,
316+
or: false,
317+
refinements,
318+
metadata: []
319+
};
320+
321+
expect(BrowserBridge.convertRefinement(navigation)).to.eq(navigation);
322+
});
232323
});
233324
});
234325
});

wallaby.conf.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = function(wallaby) {
2+
return {
3+
env: {
4+
type: 'node'
5+
},
6+
testFramework: 'mocha',
7+
8+
files: [
9+
'src/**/*.ts',
10+
{
11+
pattern: 'test/**/_suite.ts',
12+
instrument: false
13+
}
14+
],
15+
tests: ['test/unit/**/*.ts'],
16+
setup() {
17+
const chai = require('chai');
18+
chai.use(require('sinon-chai'));
19+
}
20+
};
21+
};

0 commit comments

Comments
 (0)