diff --git a/addon/utils/google-maps-mock.ts b/addon-test-support/services/autocomplete-handler.ts similarity index 83% rename from addon/utils/google-maps-mock.ts rename to addon-test-support/services/autocomplete-handler.ts index b654971a..929945dc 100644 --- a/addon/utils/google-maps-mock.ts +++ b/addon-test-support/services/autocomplete-handler.ts @@ -1,15 +1,27 @@ -export type MockGoogleAddressComponent = { +import Service from '@ember/service'; + +import { AutocompleteHandlerBase } from '@upfluence/ember-upf-utils/services/autocomplete-handler'; + +type MockGoogleAddressComponent = { types: string[]; long_name: string; short_name: string; }; -export type MockPlaceResult = { +type MockPlaceResult = { address_components?: MockGoogleAddressComponent[]; formatted_address?: string; }; -export class MockAutocomplete { +class AutocompleteHandlerServiceMock extends Service implements AutocompleteHandlerBase { + private mockLoader: MockLoader = new MockLoader({ apiKey: 'test-key' }); + + getLoader(): MockLoader { + return this.mockLoader; + } +} + +class MockAutocomplete { private listeners: Map = new Map(); private mockPlace: MockPlaceResult = {}; @@ -41,7 +53,7 @@ export class MockAutocomplete { } } -export class MockLoader { +class MockLoader { private mockAutocompleteInstance: MockAutocomplete | null = null; constructor(public config?: Record) {} @@ -66,7 +78,7 @@ export class MockLoader { } } -export function createSampleAddressComponents( +function createSampleAddressComponents( overrides: Partial<{ streetNumber: string; route: string; @@ -149,7 +161,7 @@ export function createSampleAddressComponents( return components; } -export function createMockPlaceResult( +function createMockPlaceResult( addressComponents: MockGoogleAddressComponent[], formattedAddress?: string ): MockPlaceResult { @@ -158,3 +170,11 @@ export function createMockPlaceResult( formatted_address: formattedAddress }; } + +export { + AutocompleteHandlerServiceMock, + MockAutocomplete, + MockLoader, + createSampleAddressComponents, + createMockPlaceResult +}; diff --git a/addon/modifiers/setup-autocomplete.ts b/addon/modifiers/setup-autocomplete.ts index 7537ba2c..a6151929 100644 --- a/addon/modifiers/setup-autocomplete.ts +++ b/addon/modifiers/setup-autocomplete.ts @@ -1,14 +1,13 @@ import Modifier, { type ArgsFor, type PositionalArgs, type NamedArgs } from 'ember-modifier'; import { registerDestructor } from '@ember/destroyable'; import { assert } from '@ember/debug'; -import { getOwner } from '@ember/application'; -import { isTesting } from '@embroider/macros'; - -import { Loader } from '@googlemaps/js-api-loader'; +import { inject as service } from '@ember/service'; import { parseAddressComponents } from '@upfluence/ember-upf-utils/utils/address-parser'; -import { MockLoader } from '@upfluence/ember-upf-utils/utils/google-maps-mock'; import { CountryData } from '@upfluence/oss-components/utils/country-codes'; +import type AutocompleteHandlerService from '@upfluence/ember-upf-utils/services/autocomplete-handler'; + +import { Loader } from '@googlemaps/js-api-loader'; type GooglePlaceResult = google.maps.places.PlaceResult; type GoogleAutocomplete = google.maps.places.Autocomplete; @@ -28,7 +27,6 @@ interface SetupAutocompleteSignature { Args: { Named: { callback(result: AutocompletionAddress): void; - loader?: Loader; }; }; } @@ -57,6 +55,8 @@ function cleanup(instance: SetupAutocompleteModifier): void { } export default class SetupAutocompleteModifier extends Modifier { + @service declare autocompleteHandler: AutocompleteHandlerService; + targetElement: HTMLElement | null = null; targetInput: HTMLInputElement | null = null; result: AutocompletionAddress | null = null; @@ -72,7 +72,7 @@ export default class SetupAutocompleteModifier extends Modifier, - { callback, loader }: NamedArgs + { callback }: NamedArgs ): void { const input: HTMLInputElement | null = this.getInputElement(element); if (!input) return; @@ -87,7 +87,7 @@ export default class SetupAutocompleteModifier extends Modifier { - const loaderInstance: Loader | MockLoader = isTesting() - ? loader ?? new MockLoader({ apiKey: 'test-key' }) - : new Loader({ - apiKey: getOwner(this).resolveRegistration('config:environment').google_map_api_key, - version: 'weekly' - }); + private setupAutoComplete(): Promise { + const loaderInstance: Loader = this.autocompleteHandler.getLoader(); // @ts-ignore return loaderInstance.importLibrary('places').then(({ Autocomplete }: google.maps.PlacesLibrary) => { diff --git a/addon/services/autocomplete-handler.ts b/addon/services/autocomplete-handler.ts new file mode 100644 index 00000000..7c663bbc --- /dev/null +++ b/addon/services/autocomplete-handler.ts @@ -0,0 +1,23 @@ +import Service from '@ember/service'; +import { getOwner } from '@ember/application'; + +import { Loader } from '@googlemaps/js-api-loader'; + +export interface AutocompleteHandlerBase { + getLoader(): T; +} + +export default class AutocompleteHandler extends Service implements AutocompleteHandlerBase { + getLoader(): Loader { + return new Loader({ + apiKey: getOwner(this).resolveRegistration('config:environment').google_map_api_key, + version: 'weekly' + }); + } +} + +declare module '@ember/service' { + interface Registry { + 'autocomplete-handler': AutocompleteHandler; + } +} diff --git a/app/services/autocomplete-handler.js b/app/services/autocomplete-handler.js new file mode 100644 index 00000000..4c6330c5 --- /dev/null +++ b/app/services/autocomplete-handler.js @@ -0,0 +1 @@ +export { default } from '@upfluence/ember-upf-utils/services/autocomplete-handler'; diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js index bd4ae7b8..5b04fd28 100644 --- a/tests/dummy/config/environment.js +++ b/tests/dummy/config/environment.js @@ -42,6 +42,7 @@ module.exports = function (environment) { ENV.APP.rootElement = '#ember-testing'; ENV.APP.autoboot = false; + ENV.google_map_api_key = 'foobar'; } if (environment === 'production') { diff --git a/tests/integration/components/http-errors-code-test.ts b/tests/integration/components/http-errors-code-test.ts index 09c6fe84..381abd8f 100644 --- a/tests/integration/components/http-errors-code-test.ts +++ b/tests/integration/components/http-errors-code-test.ts @@ -3,7 +3,6 @@ import { setupRenderingTest } from 'ember-qunit'; import { click, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import Service from '@ember/service'; -// @ts-ignore import { setupIntl } from 'ember-intl/test-support'; import sinon from 'sinon'; diff --git a/tests/integration/components/utils/address-form-test.ts b/tests/integration/components/utils/address-form-test.ts index e8f4dbd2..1741d126 100644 --- a/tests/integration/components/utils/address-form-test.ts +++ b/tests/integration/components/utils/address-form-test.ts @@ -6,11 +6,14 @@ import EmberObject from '@ember/object'; import { click, fillIn, findAll, render, typeIn } from '@ember/test-helpers'; import sinon from 'sinon'; +import { AutocompleteHandlerServiceMock } from '@upfluence/ember-upf-utils/test-support/services/autocomplete-handler'; + module('Integration | Component | utils/address-form', function (hooks) { setupRenderingTest(hooks); setupIntl(hooks); hooks.beforeEach(function () { + this.owner.register('service:autocomplete-handler', AutocompleteHandlerServiceMock); this.address = EmberObject.create({ firstName: 'iam', lastName: 'groot', diff --git a/tests/integration/components/utils/address-inline-test.ts b/tests/integration/components/utils/address-inline-test.ts index aba351b1..77949913 100644 --- a/tests/integration/components/utils/address-inline-test.ts +++ b/tests/integration/components/utils/address-inline-test.ts @@ -6,11 +6,14 @@ import EmberObject from '@ember/object'; import { render, typeIn } from '@ember/test-helpers'; import sinon from 'sinon'; +import { AutocompleteHandlerServiceMock } from '@upfluence/ember-upf-utils/test-support/services/autocomplete-handler'; + module('Integration | Component | utils/address-inline', function (hooks) { setupRenderingTest(hooks); setupIntl(hooks); hooks.beforeEach(function () { + this.owner.register('service:autocomplete-handler', AutocompleteHandlerServiceMock); this.address = EmberObject.create({ address: '123 Main St', resolved_address: null diff --git a/tests/integration/modifiers/setup-autocomplete-test.ts b/tests/integration/modifiers/setup-autocomplete-test.ts index 852213c0..4716a824 100644 --- a/tests/integration/modifiers/setup-autocomplete-test.ts +++ b/tests/integration/modifiers/setup-autocomplete-test.ts @@ -6,22 +6,22 @@ import { type AutocompletionAddress } from '@upfluence/ember-upf-utils/modifiers import { createMockPlaceResult, - createSampleAddressComponents, - MockLoader -} from '@upfluence/ember-upf-utils/utils/google-maps-mock'; + createSampleAddressComponents +} from '@upfluence/ember-upf-utils/test-support/services/autocomplete-handler'; +import { AutocompleteHandlerServiceMock } from '@upfluence/ember-upf-utils/test-support/services/autocomplete-handler'; module('Integration | Modifier | setup-autocomplete', function (hooks) { setupRenderingTest(hooks); hooks.beforeEach(function () { - this.mockLoader = new MockLoader({ apiKey: 'test-key' }); + this.owner.register('service:autocomplete-handler', AutocompleteHandlerServiceMock); + const service = this.owner.lookup('service:autocomplete-handler'); + this.mockLoader = service.getLoader(); }); module('Element setup', () => { test('it works with a text input element directly', async function (assert) { - await render( - hbs`
` - ); + await render(hbs`
`); assert.dom('input[type="text"]').exists(); const input = find('input[type="text"]'); @@ -30,7 +30,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { test('it works with input inside a container element', async function (assert) { await render(hbs` -
+
`); @@ -49,7 +49,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { }; await render(hbs` -
+
`); const mockAutocomplete = this.mockLoader.getMockAutocompleteInstance(); @@ -71,7 +71,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { }; await render(hbs` - + `); const mockAutocomplete = this.mockLoader.getMockAutocompleteInstance(); @@ -84,7 +84,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { module('Cleanup', () => { test('pac-container is removed on teardown', async function (assert) { await render(hbs` - + `); const pacContainer = document.createElement('div'); @@ -100,7 +100,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { test('wrapper is properly unwrapped during cleanup', async function (assert) { await render( - hbs`
` + hbs`
` ); assert.dom('#test-input').exists(); @@ -117,9 +117,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { }); test('cleanup handles already removed elements gracefully', async function (assert) { - await render( - hbs`
` - ); + await render(hbs`
`); assert.dom('input[type="text"]').exists(); const input = find('input[type="text"]'); @@ -135,7 +133,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { test('wrapper is not created when modifier is on container element', async function (assert) { await render(hbs` -
+
`); @@ -156,7 +154,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { this.value = '123 Main Street'; await render(hbs` - + `); assert.dom('input[type="text"]').hasValue('123 Main Street'); @@ -169,7 +167,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { id="address-input" class="custom-input" placeholder="Enter address" - {{setup-autocomplete callback=(fn (mut this.result)) loader=this.mockLoader}} + {{setup-autocomplete callback=(fn (mut this.result))}} /> `); @@ -189,7 +187,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { }; await render(hbs` - + `); const mockAutocomplete = this.mockLoader.getMockAutocompleteInstance(); @@ -215,7 +213,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { ); }); - await render(hbs`
`); + await render(hbs`
`); }); test('handles missing input element gracefully', async function (assert) { @@ -227,7 +225,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { ); }); - await render(hbs`
`); + await render(hbs`
`); }); test('handles missing input element in its children gracefully', async function (assert) { @@ -239,9 +237,7 @@ module('Integration | Modifier | setup-autocomplete', function (hooks) { ); }); - await render( - hbs`
` - ); + await render(hbs`
`); }); }); }); diff --git a/tests/unit/services/autocomplete-handler-test.ts b/tests/unit/services/autocomplete-handler-test.ts new file mode 100644 index 00000000..ac44bc56 --- /dev/null +++ b/tests/unit/services/autocomplete-handler-test.ts @@ -0,0 +1,70 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +import AutocompleteHandlerService from '@upfluence/ember-upf-utils/services/autocomplete-handler'; +import { AutocompleteHandlerServiceMock } from '@upfluence/ember-upf-utils/test-support/services/autocomplete-handler'; + +module('Unit | Service | autocomplete-handler', function (hooks) { + setupTest(hooks); + + module('Production service', function () { + test('it exists', function (assert) { + const service = this.owner.lookup('service:autocomplete-handler'); + + assert.ok(service); + assert.true(service instanceof AutocompleteHandlerService); + }); + + test('getLoader returns a Loader instance', function (assert) { + const service = this.owner.lookup('service:autocomplete-handler') as AutocompleteHandlerService; + const loader = service.getLoader(); + + assert.ok(loader); + }); + + test('Loader is configured with API key from environment', function (assert) { + const service = this.owner.lookup('service:autocomplete-handler') as AutocompleteHandlerService; + const loader = service.getLoader(); + + assert.ok(loader); + assert.strictEqual((loader as any).options.version, 'weekly'); + assert.strictEqual((loader as any).options.apiKey, 'foobar'); + }); + }); + + module('Test service', function (hooks) { + hooks.beforeEach(function () { + this.owner.register('service:autocomplete-handler', AutocompleteHandlerServiceMock); + }); + + test('it exists', function (assert) { + const service = this.owner.lookup('service:autocomplete-handler'); + + assert.ok(service); + assert.true(service instanceof AutocompleteHandlerServiceMock); + }); + + test('getLoader returns a MockLoader instance', function (assert) { + const service = this.owner.lookup('service:autocomplete-handler') as AutocompleteHandlerServiceMock; + const loader = service.getLoader(); + + assert.ok(loader); + }); + + test('MockLoader importLibrary returns places library', async function (assert) { + const service = this.owner.lookup('service:autocomplete-handler') as AutocompleteHandlerServiceMock; + const loader = service.getLoader(); + const placesLib = await loader.importLibrary('places'); + + assert.ok(placesLib); + assert.ok(placesLib.Autocomplete); + }); + + test('MockLoader can be accessed for test assertions', function (assert) { + const service = this.owner.lookup('service:autocomplete-handler') as AutocompleteHandlerServiceMock; + const mockLoader = service.getLoader(); + + assert.ok(mockLoader.getMockAutocompleteInstance); + }); + }); +}); diff --git a/tests/unit/utils/address-parser-test.ts b/tests/unit/utils/address-parser-test.ts index da43a375..a8edf5fe 100644 --- a/tests/unit/utils/address-parser-test.ts +++ b/tests/unit/utils/address-parser-test.ts @@ -1,7 +1,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { parseAddressComponents } from '@upfluence/ember-upf-utils/utils/address-parser'; -import { createSampleAddressComponents } from '@upfluence/ember-upf-utils/utils/google-maps-mock'; +import { createSampleAddressComponents } from '@upfluence/ember-upf-utils/test-support/services/autocomplete-handler'; module('Unit | Utility | address-parser', function (hooks) { setupTest(hooks);