Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: 🎸 initial working implementation of getAutocomplete()
This change introduces the getAutocomplete() operation and a listCompletions() method. The listCompletions() method can be used to produce a list of options that can be easily used to refine further getAutocomplete() calls. ✅ Closes: #43
- Loading branch information
1 parent
d8c982d
commit 4775b42
Showing
6 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
const { listCompletions } = require('./methods'); | ||
const { ParameterValidationError } = require('./errors'); | ||
|
||
const OPERATION_URL = 'https://docs.firstclasspostcodes.com/operation/getAutocomplete'; | ||
|
||
const OPERATION_PATH = '/autocomplete'; | ||
|
||
module.exports = { | ||
async getAutocomplete(search) { | ||
let errorObj; | ||
|
||
if (!search || typeof search !== 'string' || search.length === 0) { | ||
errorObj = { | ||
message: `Unexpected search parameter: "${search}"`, | ||
docUrl: OPERATION_URL, | ||
}; | ||
} | ||
|
||
const params = { | ||
path: OPERATION_PATH, | ||
qs: { | ||
search, | ||
}, | ||
}; | ||
|
||
this.debug(`Executing operation getAutocomplete (${OPERATION_PATH}): %o`, params); | ||
|
||
this.emit('operation:getAutocomplete', params); | ||
|
||
if (errorObj) { | ||
const error = new ParameterValidationError(errorObj); | ||
this.debug('Encountered ParameterValidationError: %o', errorObj); | ||
this.emit('error', error); | ||
throw error; | ||
} | ||
|
||
const responseObject = await this.request(params); | ||
|
||
const isCompleted = responseObject.length === 1 && responseObject[0][1].length <= 1; | ||
|
||
Object.assign(responseObject, { isCompleted, listCompletions }); | ||
|
||
return responseObject; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
const callable = require('./getAutocomplete'); | ||
const { ParameterValidationError } = require('./errors'); | ||
|
||
describe('#getAutocomplete', () => { | ||
let testClass; | ||
|
||
beforeEach(() => { | ||
const TestClass = class {}; | ||
|
||
const classMethods = { | ||
emit: jest.fn(), | ||
debug: jest.fn(), | ||
request: jest.fn(), | ||
}; | ||
|
||
Object.assign(TestClass.prototype, classMethods, callable); | ||
|
||
testClass = new TestClass(); | ||
}); | ||
|
||
it('it is a function', () => ( | ||
expect(testClass.getAutocomplete).toEqual(expect.any(Function)) | ||
)); | ||
|
||
describe('when the request is valid', () => { | ||
const search = 'test'; | ||
|
||
let testResponseBody; | ||
|
||
describe('when there are multiple results', () => { | ||
beforeEach(() => { | ||
testResponseBody = [ | ||
[ | ||
'TEST 1', | ||
[ | ||
'STREET A', | ||
'STREET B', | ||
], | ||
], | ||
[ | ||
'TEST 2', | ||
[ | ||
'STREET A', | ||
'STREET B', | ||
], | ||
], | ||
]; | ||
}); | ||
|
||
it('resolves with the correct response', async () => { | ||
const expectedParams = expect.objectContaining({ | ||
path: expect.stringMatching(/^\/[a-z]+$/), | ||
qs: { | ||
search, | ||
}, | ||
}); | ||
testClass.request.mockImplementationOnce(async (params) => { | ||
expect(params).toEqual(expectedParams); | ||
return testResponseBody; | ||
}); | ||
const response = await testClass.getAutocomplete(search); | ||
expect(response).toBe(testResponseBody); | ||
expect(response.listCompletions).toEqual(expect.any(Function)); | ||
expect(response.isCompleted).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('when there is a single result with multiple streets', () => { | ||
beforeEach(() => { | ||
testResponseBody = [ | ||
[ | ||
'TEST', | ||
[ | ||
'STREET A', | ||
'STREET B', | ||
], | ||
], | ||
]; | ||
}); | ||
|
||
it('resolves with the correct response', async () => { | ||
testClass.request.mockImplementationOnce(async () => testResponseBody); | ||
const response = await testClass.getAutocomplete(search); | ||
expect(response).toBe(testResponseBody); | ||
expect(response.isCompleted).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('when there is only a single result', () => { | ||
beforeEach(() => { | ||
testResponseBody = [ | ||
[ | ||
'TEST', | ||
[ | ||
'STREET A', | ||
], | ||
], | ||
]; | ||
}); | ||
|
||
it('resolves with the correct response', async () => { | ||
testClass.request.mockImplementationOnce(async () => testResponseBody); | ||
const response = await testClass.getAutocomplete(search); | ||
expect(response).toBe(testResponseBody); | ||
expect(response.isCompleted).toBe(true); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when the request is invalid', () => { | ||
it('throws the correct error', async () => { | ||
await expect(testClass.getAutocomplete()).rejects.toThrow(ParameterValidationError); | ||
expect(testClass.emit).toHaveBeenNthCalledWith(1, 'operation:getAutocomplete', expect.any(Object)); | ||
expect(testClass.emit).toHaveBeenNthCalledWith(2, 'error', expect.any(ParameterValidationError)); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
const { getAutocomplete } = require('./getAutocomplete'); | ||
const { getPostcode } = require('./getPostcode'); | ||
const { getLookup } = require('./getLookup'); | ||
|
||
module.exports = { | ||
getAutocomplete, | ||
getPostcode, | ||
getLookup, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
const { listCompletions } = require('./listCompletions'); | ||
const { listAddresses } = require('./listAddresses'); | ||
const { formatAddress } = require('./formatAddress'); | ||
|
||
module.exports = { | ||
listAddresses, | ||
listCompletions, | ||
formatAddress, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module.exports = { | ||
listCompletions() { | ||
return this.reduce((completions, [postcode, streets]) => { | ||
if (!streets || streets.length === 0) { | ||
completions.push(postcode); | ||
} | ||
|
||
streets.forEach((street) => { | ||
completions.push(`${street}, ${postcode}`); | ||
}); | ||
|
||
return completions; | ||
}, []); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
const callable = require('./listCompletions'); | ||
|
||
describe('#listCompletions', () => { | ||
let testResponse = []; | ||
|
||
beforeEach(() => { | ||
Object.assign(testResponse, callable); | ||
}); | ||
|
||
it('it is a function', () => ( | ||
expect(testResponse.listCompletions).toEqual(expect.any(Function)) | ||
)); | ||
|
||
describe('when there are no matching completions', () => { | ||
it('returns an empty list', () => { | ||
expect(testResponse.listCompletions()).toEqual([]); | ||
}); | ||
}); | ||
|
||
describe('when there are matching completions', () => { | ||
describe('when there are matching streets', () => { | ||
beforeEach(() => { | ||
testResponse = Object.assign([ | ||
[ | ||
'TEST', | ||
[ | ||
'STREET A', | ||
'STREET B', | ||
], | ||
], | ||
], callable); | ||
}); | ||
|
||
it('returns a list containing streets', () => { | ||
expect(testResponse.listCompletions()).toEqual([ | ||
'STREET A, TEST', | ||
'STREET B, TEST', | ||
]); | ||
}); | ||
}); | ||
|
||
describe('when there are no matching streets', () => { | ||
beforeEach(() => { | ||
testResponse = Object.assign([ | ||
[ | ||
'TEST', [], | ||
], | ||
], callable); | ||
}); | ||
|
||
it('returns a list containing the postcode', () => { | ||
expect(testResponse.listCompletions()).toEqual([ | ||
'TEST', | ||
]); | ||
}); | ||
}); | ||
}); | ||
}); |