diff --git a/CHANGELOG.md b/CHANGELOG.md index 223848f..7ae0555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,9 +28,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## UNPUBLISHED +### Added + +- A string `source` can now contain `{{term}}`, to accommodate paths where `?term=XXX` isn't suitable + - This means you could now use `example.com/search/{{term}}/other-stuff` + ### Fixed - The mouse being over the popup when it's rendered no longer selects that value whilst typing +- A string `source` can now contain a query string (`?`) + - It now checks the source and adds either `?` or `&`, whichever is appropriate ### Changes diff --git a/README.md b/README.md index 5ffc500..31feec1 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ new Autocomplete( { // Query this source & expect a JSON response source: './relative-folder/query.html' + // or './relative-folder/{{term}}/query' } ) // Don't forget to start it @@ -107,7 +108,10 @@ The source of the autocompelte data #### `SourceTypes` - `string` - a URL we will `GET` with a `term` querystring parameter (expects a JSON response) + a URL that we will `GET`, expecting a JSON response. + **Note:** the search term is added to the URL in one of two ways + - if `{{term}}` is in the URL, this will be replaced, else + - a `term` querystring parameter is appended to the URL - `Record` set an object with string keys and values, treated as label, value respectively - `string[]` diff --git a/src/index.ts b/src/index.ts index 28cf6d6..837c5db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -326,9 +326,18 @@ export class Autocomplete { typeof this.options.source === 'string' ? await (( await window.fetch( - this.options.source + - '?term=' + - encodeURIComponent(data.term), + this.options.source.indexOf('{{term}}') > -1 + ? this.options.source.replace( + '{{term}}', + encodeURIComponent(data.term), + ) + : this.options.source + + (this.options.source.indexOf('?') > + -1 + ? '&' + : '?') + + 'term=' + + encodeURIComponent(data.term), ) ).json() as Promise[]>) : typeof this.options.source === 'function' diff --git a/tests/index.test.ts b/tests/index.test.ts index 75f07f5..9c83d4d 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,3 +1,5 @@ +'use strict'; + import { Autocomplete, AutocompleteStatus } from '../src/index'; describe('Core Tests', () => { diff --git a/tests/initialiseEnvironment.ts b/tests/initialiseEnvironment.ts new file mode 100644 index 0000000..84aee20 --- /dev/null +++ b/tests/initialiseEnvironment.ts @@ -0,0 +1,46 @@ +/* exported initialiseEnvironment */ + +'use strict'; +import { Autocomplete, AutocompleteStatus } from '../src'; +import { SourceTypes } from '../src/Types/SourceTypes'; + +export function initialiseEnvironment(): { + inputEL: HTMLInputElement; + autocomplete: Autocomplete<{ label: string; value: string }>; +}; +export function initialiseEnvironment(newSource: string): { + inputEL: HTMLInputElement; + autocomplete: Autocomplete<{ label: string; value: string }>; +}; +export function initialiseEnvironment( + newSource?: SourceTypes, +): { inputEL: HTMLInputElement; autocomplete: Autocomplete } { + let inputEL = document.createElement('input'); + + inputEL.classList.add('test'); + inputEL = document.body.insertAdjacentElement( + 'beforeend', + inputEL, + ) as HTMLInputElement; + + const autocomplete = new Autocomplete('.test', { + source: + newSource ?? + ([ + { label: 'First label', value: 'First Value' }, + { label: 'Second label', value: 'Second Value' }, + { label: 'Third label', value: 'Third Value' }, + { label: 'Final label', value: 'Final Value' }, + ] as SourceTypes), + onOpen: (e, data) => { + data.ul.style.width = `${(e.target as HTMLInputElement).width}px`; + }, + }); + + autocomplete.start(); + + it('Setup complete with "started" state', () => + expect(autocomplete.status).toBe(AutocompleteStatus.Started)); + + return { inputEL, autocomplete }; +} diff --git a/tests/mouseover.test.ts b/tests/mouseover.test.ts index 8d848fc..ec0a1cd 100644 --- a/tests/mouseover.test.ts +++ b/tests/mouseover.test.ts @@ -1,50 +1,11 @@ -import { Autocomplete, AutocompleteStatus } from '../src/index'; +'use strict'; + +import { initialiseEnvironment } from './initialiseEnvironment'; jest.useFakeTimers(); describe('Mouseover Tests', () => { - let inputEL: HTMLInputElement, autocomplete: Autocomplete; - - describe('Test environment:-', () => { - it('has added element', () => { - inputEL = document.createElement('input'); - - inputEL.classList.add('test'); - inputEL = document.body.insertAdjacentElement( - 'beforeend', - inputEL, - ) as HTMLInputElement; - - expect(inputEL).not.toBeNull(); - }); - - it('has created autocomplete', () => { - autocomplete = new Autocomplete('.test', { - source: [ - { label: 'First label', value: 'First Value' }, - { label: 'Second label', value: 'Second Value' }, - { label: 'Third label', value: 'Third Value' }, - { label: 'Final label', value: 'Final Value' }, - ], - onOpen: (e, data) => { - data.ul.style.width = `${ - (e.target as HTMLInputElement).width - }px`; - }, - }); - - expect(autocomplete).not.toBeNull(); - }); - - it('has initial state of "stopped"', () => - expect(autocomplete.status).toBe(AutocompleteStatus.Stopped)); - - it('"start" should not throw', () => - expect(autocomplete.start).not.toThrow()); - - it('now has "started" state', () => - expect(autocomplete.status).toBe(AutocompleteStatus.Started)); - }); + const { inputEL, autocomplete } = initialiseEnvironment(); describe('Mouse over', () => { beforeEach(() => { diff --git a/tests/term.test.ts b/tests/term.test.ts new file mode 100644 index 0000000..cdc57df --- /dev/null +++ b/tests/term.test.ts @@ -0,0 +1,53 @@ +'use strict'; + +import { initialiseEnvironment } from './initialiseEnvironment'; + +//@ts-ignore +global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve([{ label: 'Test', value: 'Test' }]), + }), +); + +describe('Term Tests', () => { + describe('simple string (no QS):-', () => { + const { inputEL } = initialiseEnvironment('https://example.com'); + + inputEL.value = 'Test'; + + inputEL.dispatchEvent(new Event('change')); + + it('appends "?term="', () => + expect(fetch).toHaveBeenCalledWith( + 'https://example.com?term=Test', + )); + }); + + describe('string with `?`:-', () => { + const { inputEL } = initialiseEnvironment('https://example.com?test=1'); + + inputEL.value = 'Test'; + + inputEL.dispatchEvent(new Event('change')); + + it('appends "&term="', () => + expect(fetch).toHaveBeenCalledWith( + 'https://example.com?test=1&term=Test', + )); + }); + + describe('string with `{{term}}`:-', () => { + const { inputEL } = initialiseEnvironment( + 'https://example.com/search/{{term}}/suffix', + ); + + inputEL.value = 'Test'; + + inputEL.dispatchEvent(new Event('change')); + + it('embeds the "term"', () => + expect(fetch).toHaveBeenCalledWith( + 'https://example.com/search/Test/suffix', + )); + }); +});