diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index aea93db6..f0c12a8d 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -48,9 +48,11 @@ jobs:
with:
node-version: 16.x
args: "--frozen-lockfile"
- - name: Update TS version
+ - name: Update TS version on addon package
run: pnpm add -D ${{ matrix.typescript-scenario }}
working-directory: ember-phone-input
+ - name: Update TS version on test-app package
+ run: pnpm add -D ${{ matrix.typescript-scenario }}
+ working-directory: test-app
- name: Type checking
run: pnpm lint:types
- working-directory: ember-phone-input
diff --git a/README.md b/README.md
index 48a9c537..c919bab5 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ ember install ember-phone-input
- Ember CLI v2.13 or above
- ember-auto-import v2 or above (ember-phone-input@6 is compatible with previous
versions of ember-auto-import)
+- TypeScript v5.0 or above
## Contributing
diff --git a/ember-phone-input/rollup.config.mjs b/ember-phone-input/rollup.config.mjs
index 6e6ae0e9..325c3f0d 100644
--- a/ember-phone-input/rollup.config.mjs
+++ b/ember-phone-input/rollup.config.mjs
@@ -21,7 +21,8 @@ export default {
addon.publicEntrypoints([
'components/**/*.js',
'services/**/*.js',
- 'instance-initializers/**/*.js'
+ 'instance-initializers/**/*.js',
+ 'template-registry.js'
]),
// These are the modules that should get reexported into the traditional
diff --git a/ember-phone-input/src/components/phone-input.ts b/ember-phone-input/src/components/phone-input.ts
index f4c20f12..6942389f 100644
--- a/ember-phone-input/src/components/phone-input.ts
+++ b/ember-phone-input/src/components/phone-input.ts
@@ -9,7 +9,7 @@ import type PhoneInputService from '../services/phone-input';
import 'intl-tel-input/build/css/intlTelInput.css';
import '../styles/styles.css';
-interface MetaData {
+export interface MetaData {
extension: string;
selectedCountryData: intlTelInput.CountryData;
isValidNumber: boolean;
diff --git a/ember-phone-input/src/template-registry.ts b/ember-phone-input/src/template-registry.ts
new file mode 100644
index 00000000..4bbb3e73
--- /dev/null
+++ b/ember-phone-input/src/template-registry.ts
@@ -0,0 +1,5 @@
+import type PhoneInput from './components/phone-input';
+
+export default interface Registry {
+ PhoneInput: typeof PhoneInput;
+}
diff --git a/package.json b/package.json
index aa5bc485..f0f91106 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"build": "pnpm --filter ember-phone-input build",
"lint": "pnpm --filter '*' lint",
"lint:fix": "pnpm --filter '*' lint:fix",
+ "lint:types": "pnpm --filter '*' lint:types",
"prepare": "pnpm build",
"start": "concurrently 'npm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow",
"start:addon": "pnpm --filter ember-phone-input start",
diff --git a/test-app/app/app.js b/test-app/app/app.ts
similarity index 87%
rename from test-app/app/app.js
rename to test-app/app/app.ts
index d8e2088b..1ba93424 100644
--- a/test-app/app/app.js
+++ b/test-app/app/app.ts
@@ -1,7 +1,7 @@
import Application from '@ember/application';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
-import config from './config/environment';
+import config from 'test-app/config/environment';
export default class App extends Application {
modulePrefix = config.modulePrefix;
diff --git a/test-app/config/environment.d.ts b/test-app/app/config/environment.d.ts
similarity index 100%
rename from test-app/config/environment.d.ts
rename to test-app/app/config/environment.d.ts
diff --git a/test-app/app/router.js b/test-app/app/router.ts
similarity index 100%
rename from test-app/app/router.js
rename to test-app/app/router.ts
diff --git a/test-app/config/ember-try.js b/test-app/config/ember-try.js
index 76d17cae..6968e1e3 100644
--- a/test-app/config/ember-try.js
+++ b/test-app/config/ember-try.js
@@ -3,6 +3,12 @@
const getChannelURL = require('ember-source-channel-url');
const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup');
+// Needed for ember-source < 4.8, when preview types were first shipped
+const emberTypesPackages = {
+ '@types/ember__application': '^4.0.8',
+ '@types/ember__routing': '^4.0.17'
+};
+
module.exports = async function () {
return {
usePnpm: true,
@@ -11,7 +17,8 @@ module.exports = async function () {
name: 'ember-lts-3.28',
npm: {
devDependencies: {
- 'ember-source': '~3.28.0'
+ 'ember-source': '~3.28.0',
+ ...emberTypesPackages
}
}
},
@@ -19,7 +26,8 @@ module.exports = async function () {
name: 'ember-lts-4.4',
npm: {
devDependencies: {
- 'ember-source': '~4.4.0'
+ 'ember-source': '~4.4.0',
+ ...emberTypesPackages
}
}
},
@@ -58,7 +66,8 @@ module.exports = async function () {
},
npm: {
devDependencies: {
- 'ember-source': '~3.28.0'
+ 'ember-source': '~3.28.0',
+ ...emberTypesPackages
},
ember: {
edition: 'classic'
diff --git a/test-app/package.json b/test-app/package.json
index 03a27907..038d1e2b 100644
--- a/test-app/package.json
+++ b/test-app/package.json
@@ -18,6 +18,7 @@
"lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint . --cache",
"lint:js:fix": "eslint . --fix",
+ "lint:types": "glint",
"start": "ember serve",
"test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\"",
"test:ember": "ember test",
diff --git a/test-app/tests/helpers/index.js b/test-app/tests/helpers/index.ts
similarity index 78%
rename from test-app/tests/helpers/index.js
rename to test-app/tests/helpers/index.ts
index 9f513ae8..639c476b 100644
--- a/test-app/tests/helpers/index.js
+++ b/test-app/tests/helpers/index.ts
@@ -1,3 +1,4 @@
+import type { SetupTestOptions } from 'ember-qunit';
import {
setupApplicationTest as upstreamSetupApplicationTest,
setupRenderingTest as upstreamSetupRenderingTest,
@@ -8,7 +9,10 @@ import {
// test setup functions. This way, you can easily extend the setup that is
// needed per test type.
-function setupApplicationTest(hooks, options) {
+function setupApplicationTest(
+ hooks: NestedHooks,
+ options: SetupTestOptions
+): void {
upstreamSetupApplicationTest(hooks, options);
// Additional setup for application tests can be done here.
@@ -27,13 +31,16 @@ function setupApplicationTest(hooks, options) {
// setupMirage(hooks); // ember-cli-mirage
}
-function setupRenderingTest(hooks, options) {
+function setupRenderingTest(
+ hooks: NestedHooks,
+ options: SetupTestOptions
+): void {
upstreamSetupRenderingTest(hooks, options);
// Additional setup for rendering tests can be done here.
}
-function setupTest(hooks, options) {
+function setupTest(hooks: NestedHooks, options: SetupTestOptions): void {
upstreamSetupTest(hooks, options);
// Additional setup for unit tests can be done here.
diff --git a/test-app/tests/integration/components/phone-input-test.js b/test-app/tests/integration/components/phone-input-test.js
deleted file mode 100644
index 1154e9d0..00000000
--- a/test-app/tests/integration/components/phone-input-test.js
+++ /dev/null
@@ -1,358 +0,0 @@
-import QUnit, { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { fillIn, render, find, typeIn, waitUntil } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-
-module('Integration | Component | phone-input', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(async function () {
- await this.owner.lookup('service:phone-input').load();
- });
-
- test('renders an input of type tel', async function (assert) {
- await render(hbs``);
-
- assert.dom('input').hasAttribute('type', 'tel');
- });
-
- test('renders the value', async function (assert) {
- assert.expect(3);
- const newValue = '2';
- this.set('number', null);
- this.set('update', () => {});
-
- await render(
- hbs``
- );
-
- assert.dom('input').hasValue('');
-
- this.set('update', (value) => {
- assert.strictEqual(value, newValue);
- this.set('number', newValue);
- });
-
- await fillIn('input', newValue);
-
- assert.dom('input').hasValue(newValue);
- });
-
- test('renders the custom placeholder', async function (assert) {
- this.set('number', null);
- this.set('update', () => {});
- this.set('customPlaceholder', 'A custom placeholder');
-
- await render(
- hbs``
- );
-
- assert.dom('input').hasAttribute('placeholder', this.customPlaceholder);
- });
-
- test('renders auto placeholder if custom placeholder is not provided', async function (assert) {
- this.set('number', null);
- this.set('update', () => {});
-
- await render(
- hbs``
- );
-
- assert.dom('input').hasAttribute('placeholder', '(201) 555-0123');
- });
-
- test('renders the value with separate dial code option', async function (assert) {
- const newValue = '2';
- this.set('separateDialNumber', null);
- this.set('update', (value) => {
- this.set('separateDialNumber', value);
- });
-
- await render(
- hbs``
- );
-
- assert.dom('input').hasValue('');
- assert.dom('.iti__selected-dial-code').hasText('+1');
-
- await fillIn('input', newValue);
-
- assert.dom('input').hasValue(newValue);
- });
-
- test('should not insert the dial code by default', async function (assert) {
- await render(hbs``);
-
- assert.dom('input').hasValue('');
- });
-
- test('can update the country', async function (assert) {
- this.set('number', null);
- this.set('update', () => {});
- this.set('country', 'us');
-
- await render(
- hbs``
- );
-
- assert.dom('.iti__flag').hasClass('iti__us');
-
- this.set('country', 'nz');
-
- assert.dom('.iti__flag').hasClass('iti__nz');
- });
-
- test('phoneNumber is correctly invalid when country is changed', async function (assert) {
- assert.expect(7);
- const country = 'fr';
- const validFrenchNumber = '0622334455';
- this.set('number', null);
- this.set('country', country);
- this.set('update', () => {});
-
- await render(
- hbs``
- );
-
- this.set('update', (number, { isValidNumber, numberFormat }) => {
- assert.ok(isValidNumber);
- assert.strictEqual(numberFormat.E164, '+33622334455');
- assert.strictEqual(numberFormat.INTERNATIONAL, '+33 6 22 33 44 55');
- assert.strictEqual(numberFormat.NATIONAL, '06 22 33 44 55');
- assert.strictEqual(numberFormat.RFC3966, 'tel:+33-6-22-33-44-55');
- });
-
- await fillIn('input', validFrenchNumber);
-
- this.set('update', (number, { isValidNumber, numberFormat }) => {
- assert.notOk(isValidNumber);
- assert.strictEqual(numberFormat, null);
- });
-
- this.set('country', 'pt');
- });
-
- test('can be disabled', async function (assert) {
- this.set('number', null);
- this.set('update', () => {});
-
- await render(
- hbs``
- );
- assert.ok(find('input').disabled);
- });
-
- test('can be required', async function (assert) {
- this.set('number', null);
-
- await render(
- hbs``
- );
-
- assert.ok(find('input').required);
- });
-
- test('can prevent the dropdown', async function (assert) {
- this.set('updateAllowDropdownNumber', () => {});
-
- await render(
- hbs``
- );
-
- assert.dom('ul.country-list').doesNotExist();
- });
-
- test('can set autocomplete', async function (assert) {
- await render(hbs``);
-
- assert.strictEqual(find('input').autocomplete, 'tel');
- });
-
- test('can update the country when the user types in the digits from Brazil code', async function (assert) {
- await render(hbs``);
-
- await typeIn('input', '+55');
-
- assert.dom('.iti__flag').hasClass('iti__br');
- });
-
- test('can update the country when the user types in the digits from Malaysia code', async function (assert) {
- await render(hbs``);
-
- await typeIn('input', '+60');
-
- assert.dom('.iti__flag').hasClass('iti__my');
- });
-
- module('resilience', function (hooks) {
- let originalOnUncaughtException;
-
- hooks.before(function () {
- originalOnUncaughtException = QUnit.onUncaughtException;
- QUnit.onUncaughtException = () => {};
- });
-
- hooks.after(function () {
- QUnit.onUncaughtException = originalOnUncaughtException;
- });
-
- test('intl-tel-input is loaded after user interaction', async function (assert) {
- let service = this.owner.lookup('service:phone-input');
- let load = service.load;
- let resolveLoading;
-
- service.load = () => new Promise((resolve) => (resolveLoading = resolve));
-
- this.number = null;
- this.metaData = null;
-
- this.set('update', (value, metaData) => {
- this.set('number', value);
- this.set('metaData', metaData);
- });
-
- await render(
- hbs``
- );
-
- assert.dom('input').doesNotHaveAttribute('data-intl-tel-input-id');
-
- assert.strictEqual(
- this.number,
- null,
- 'number is null when rendered but intl-tel-input is not loaded yet'
- );
- assert.strictEqual(
- this.metaData,
- null,
- 'metaData is null when rendered but intl-tel-input is not loaded yet'
- );
-
- await fillIn('input', '9');
-
- assert.strictEqual(
- this.number,
- '9',
- 'number is correct after input without intl-tel-input'
- );
- assert.deepEqual(
- this.metaData,
- {},
- 'metaData is an empty object after input without intl-tel-input'
- );
-
- resolveLoading(load.call(service));
-
- await waitUntil(() => find('input:not([data-test-loading-iti])'));
-
- assert.dom('input').hasAttribute('data-intl-tel-input-id');
-
- assert.strictEqual(
- this.number,
- '9',
- 'number is correct after intl-tel-input is loaded'
- );
-
- assert.deepEqual(
- this.metaData,
- {
- extension: '',
- isValidNumber: false,
- numberFormat: null,
- selectedCountryData: {}
- },
- 'metaData is correct after intl-tel-input is loaded'
- );
-
- await fillIn('input', '8');
-
- assert.strictEqual(
- this.number,
- '8',
- 'number is correct after input when the intl-tel-input is loaded'
- );
-
- assert.deepEqual(
- this.metaData,
- {
- extension: '',
- isValidNumber: false,
- numberFormat: null,
- selectedCountryData: {}
- },
- 'metaData is correct after input when the intl-tel-input is loaded'
- );
- });
-
- test('intl-tel-input fails to load', async function (assert) {
- let tmp = QUnit.onUncaughtException;
- QUnit.onUncaughtException = () => {};
-
- let service = this.owner.lookup('service:phone-input');
- let rejectLoading;
-
- service.load = () =>
- new Promise((_resolve, reject) => (rejectLoading = reject));
-
- this.number = null;
- this.metaData = null;
-
- this.set('update', (value, metaData) => {
- this.set('number', value);
- this.set('metaData', metaData);
- });
-
- await render(
- hbs``
- );
-
- assert.dom('input').doesNotHaveAttribute('data-intl-tel-input-id');
-
- assert.strictEqual(
- this.number,
- null,
- 'number is null when rendered but intl-tel-input is not loaded yet'
- );
- assert.strictEqual(
- this.metaData,
- null,
- 'metaData is null when rendered but intl-tel-input is not loaded yet'
- );
-
- await fillIn('input', '9');
-
- assert.strictEqual(
- this.number,
- '9',
- 'number is correct after input without intl-tel-input'
- );
- assert.deepEqual(
- this.metaData,
- {},
- 'metaData is an empty object after input without intl-tel-input'
- );
-
- rejectLoading();
-
- await waitUntil(() => find('input:not([data-test-loading-iti])'));
-
- assert.dom('input').doesNotHaveAttribute('data-intl-tel-input-id');
-
- await fillIn('input', '8');
-
- assert.strictEqual(
- this.number,
- '8',
- 'number is correct when intl-tel-input is loaded'
- );
-
- assert.deepEqual(
- this.metaData,
- {},
- 'metaData is correct when intl-tel-input is loaded'
- );
-
- QUnit.onUncaughtException = tmp;
- });
- });
-});
diff --git a/test-app/tests/integration/components/phone-input-test.ts b/test-app/tests/integration/components/phone-input-test.ts
new file mode 100644
index 00000000..fb3dac3b
--- /dev/null
+++ b/test-app/tests/integration/components/phone-input-test.ts
@@ -0,0 +1,439 @@
+import type { TestContext as TestContextBase } from '@ember/test-helpers';
+import { fillIn, find, render, typeIn, waitUntil } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+import type PhoneInputService from 'ember-phone-input/services/phone-input';
+import type {
+ PhoneInputArgs,
+ MetaData
+} from 'ember-phone-input/components/phone-input';
+import { setupRenderingTest } from 'ember-qunit';
+import QUnit, { module, test } from 'qunit';
+
+interface TestContext extends PhoneInputArgs, TestContextBase {
+ metaData: MetaData | null;
+ separateDialNumber: PhoneInputArgs['number'];
+ updateAllowDropdownNumber: () => void;
+}
+
+const NOOP = (): void => {};
+
+module('Integration | Component | phone-input', function (hooks) {
+ setupRenderingTest(hooks);
+
+ hooks.beforeEach(async function () {
+ const service = this.owner.lookup(
+ 'service:phone-input'
+ ) as unknown as PhoneInputService;
+ await service.load();
+ });
+
+ test('renders an input of type tel', async function (this: TestContext, assert) {
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').hasAttribute('type', 'tel');
+ });
+
+ test('renders the value', async function (this: TestContext, assert) {
+ assert.expect(3);
+
+ const newValue = '2';
+
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').hasValue('');
+
+ this.set('update', (number: PhoneInputArgs['number']): void => {
+ assert.strictEqual(number, newValue);
+ this.set('number', newValue);
+ });
+
+ await fillIn('input', newValue);
+
+ assert.dom('input').hasValue(newValue);
+ });
+
+ test('renders the custom placeholder', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+ this.customPlaceholder = 'A custom placeholder';
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').hasAttribute('placeholder', this.customPlaceholder);
+ });
+
+ test('renders auto placeholder if custom placeholder is not provided', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').hasAttribute('placeholder', '(201) 555-0123');
+ });
+
+ test('renders the value with separate dial code option', async function (this: TestContext, assert) {
+ const newValue = '2';
+
+ this.separateDialNumber = null;
+ this.update = (number: PhoneInputArgs['number']): void => {
+ this.separateDialNumber = number;
+ };
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').hasValue('');
+ assert.dom('.iti__selected-dial-code').hasText('+1');
+
+ await fillIn('input', newValue);
+
+ assert.dom('input').hasValue(newValue);
+ });
+
+ test('should not insert the dial code by default', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').hasValue('');
+ });
+
+ test('can update the country', async function (this: TestContext, assert) {
+ this.country = 'us';
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('.iti__flag').hasClass('iti__us');
+
+ this.set('country', 'nz');
+
+ assert.dom('.iti__flag').hasClass('iti__nz');
+ });
+
+ test('phoneNumber is correctly invalid when country is changed', async function (this: TestContext, assert) {
+ assert.expect(7);
+
+ const country = 'fr';
+ const validFrenchNumber = '0622334455';
+
+ this.country = country;
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ this.set(
+ 'update',
+ (
+ _number: PhoneInputArgs['number'],
+ {
+ isValidNumber,
+ numberFormat
+ }: Pick
+ ): void => {
+ assert.ok(isValidNumber);
+ assert.strictEqual(numberFormat?.E164, '+33622334455');
+ assert.strictEqual(numberFormat?.INTERNATIONAL, '+33 6 22 33 44 55');
+ assert.strictEqual(numberFormat?.NATIONAL, '06 22 33 44 55');
+ assert.strictEqual(numberFormat?.RFC3966, 'tel:+33-6-22-33-44-55');
+ }
+ );
+
+ await fillIn('input', validFrenchNumber);
+
+ this.set(
+ 'update',
+ (
+ _number: PhoneInputArgs['number'],
+ {
+ isValidNumber,
+ numberFormat
+ }: Pick
+ ): void => {
+ assert.notOk(isValidNumber);
+ assert.strictEqual(numberFormat, null);
+ }
+ );
+
+ this.set('country', 'pt');
+ });
+
+ test('can be disabled', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+ assert.ok(find('input').disabled);
+ });
+
+ test('can be required', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.ok(find('input').required);
+ });
+
+ test('can prevent the dropdown', async function (this: TestContext, assert) {
+ this.number = null;
+ this.updateAllowDropdownNumber = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('ul.country-list').doesNotExist();
+ });
+
+ test('can set autocomplete', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ assert.strictEqual(find('input').autocomplete, 'tel');
+ });
+
+ test('can update the country when the user types in the digits from Brazil code', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ await typeIn('input', '+55');
+
+ assert.dom('.iti__flag').hasClass('iti__br');
+ });
+
+ test('can update the country when the user types in the digits from Malaysia code', async function (this: TestContext, assert) {
+ this.number = null;
+ this.update = NOOP;
+
+ await render(
+ hbs``
+ );
+
+ await typeIn('input', '+60');
+
+ assert.dom('.iti__flag').hasClass('iti__my');
+ });
+
+ module('resilience', function (hooks) {
+ let originalOnUncaughtException: (error: unknown) => void;
+
+ hooks.before(function () {
+ originalOnUncaughtException = QUnit.onUncaughtException;
+ QUnit.onUncaughtException = NOOP;
+ });
+
+ hooks.after(function () {
+ QUnit.onUncaughtException = originalOnUncaughtException;
+ });
+
+ test('intl-tel-input is loaded after user interaction', async function (this: TestContext, assert) {
+ const service = this.owner.lookup(
+ 'service:phone-input'
+ ) as unknown as PhoneInputService;
+ const load = service.load;
+
+ let resolveLoading: ((value: PromiseLike | void) => void) | null =
+ null;
+
+ service.load = (): Promise =>
+ new Promise((resolve) => (resolveLoading = resolve));
+
+ this.metaData = null;
+ this.number = null;
+ this.set(
+ 'update',
+ (number: PhoneInputArgs['number'], metaData: MetaData): void => {
+ this.set('metaData', metaData);
+ this.set('number', number);
+ }
+ );
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').doesNotHaveAttribute('data-intl-tel-input-id');
+
+ assert.strictEqual(
+ this.number,
+ null,
+ 'number is null when rendered but intl-tel-input is not loaded yet'
+ );
+ assert.strictEqual(
+ this.metaData,
+ null,
+ 'metaData is null when rendered but intl-tel-input is not loaded yet'
+ );
+
+ await fillIn('input', '9');
+
+ assert.strictEqual(
+ this.number,
+ '9',
+ 'number is correct after input without intl-tel-input'
+ );
+ assert.deepEqual(
+ this.metaData,
+ {},
+ 'metaData is an empty object after input without intl-tel-input'
+ );
+
+ (
+ resolveLoading as unknown as (value: PromiseLike | void) => void
+ )?.(load.call(service));
+
+ await waitUntil(() => find('input:not([data-test-loading-iti])'));
+
+ assert.dom('input').hasAttribute('data-intl-tel-input-id');
+
+ assert.strictEqual(
+ this.number,
+ '9',
+ 'number is correct after intl-tel-input is loaded'
+ );
+
+ assert.deepEqual(
+ this.metaData,
+ {
+ extension: '',
+ isValidNumber: false,
+ numberFormat: null,
+ selectedCountryData: {}
+ },
+ 'metaData is correct after intl-tel-input is loaded'
+ );
+
+ await fillIn('input', '8');
+
+ assert.strictEqual(
+ this.number,
+ '8',
+ 'number is correct after input when the intl-tel-input is loaded'
+ );
+
+ assert.deepEqual(
+ this.metaData,
+ {
+ extension: '',
+ isValidNumber: false,
+ numberFormat: null,
+ selectedCountryData: {}
+ },
+ 'metaData is correct after input when the intl-tel-input is loaded'
+ );
+ });
+
+ test('intl-tel-input fails to load', async function (this: TestContext, assert) {
+ const tmp = QUnit.onUncaughtException;
+ QUnit.onUncaughtException = NOOP;
+
+ const service = this.owner.lookup(
+ 'service:phone-input'
+ ) as unknown as PhoneInputService;
+
+ let rejectLoading: ((reason?: unknown) => void) | null = null;
+
+ service.load = (): Promise =>
+ new Promise((_resolve, reject) => (rejectLoading = reject));
+
+ this.number = null;
+ this.metaData = null;
+
+ this.update = (
+ number: PhoneInputArgs['number'],
+ metaData: MetaData
+ ): void => {
+ this.number = number;
+ this.metaData = metaData;
+ };
+
+ await render(
+ hbs``
+ );
+
+ assert.dom('input').doesNotHaveAttribute('data-intl-tel-input-id');
+
+ assert.strictEqual(
+ this.number,
+ null,
+ 'number is null when rendered but intl-tel-input is not loaded yet'
+ );
+ assert.strictEqual(
+ this.metaData,
+ null,
+ 'metaData is null when rendered but intl-tel-input is not loaded yet'
+ );
+
+ await fillIn('input', '9');
+
+ assert.strictEqual(
+ this.number,
+ '9',
+ 'number is correct after input without intl-tel-input'
+ );
+ assert.deepEqual(
+ this.metaData,
+ {},
+ 'metaData is an empty object after input without intl-tel-input'
+ );
+
+ (rejectLoading as unknown as (reason?: unknown) => void)?.();
+
+ await waitUntil(() => find('input:not([data-test-loading-iti])'));
+
+ assert.dom('input').doesNotHaveAttribute('data-intl-tel-input-id');
+
+ await fillIn('input', '8');
+
+ assert.strictEqual(
+ this.number,
+ '8',
+ 'number is correct when intl-tel-input is loaded'
+ );
+
+ assert.deepEqual(
+ this.metaData,
+ {},
+ 'metaData is correct when intl-tel-input is loaded'
+ );
+
+ QUnit.onUncaughtException = tmp;
+ });
+ });
+});
diff --git a/test-app/tests/test-helper.js b/test-app/tests/test-helper.ts
similarity index 100%
rename from test-app/tests/test-helper.js
rename to test-app/tests/test-helper.ts
diff --git a/test-app/tests/unit/services/phone-input-test.js b/test-app/tests/unit/services/phone-input-test.ts
similarity index 71%
rename from test-app/tests/unit/services/phone-input-test.js
rename to test-app/tests/unit/services/phone-input-test.ts
index 9dd4db7e..fa175086 100644
--- a/test-app/tests/unit/services/phone-input-test.js
+++ b/test-app/tests/unit/services/phone-input-test.ts
@@ -1,12 +1,16 @@
-import { module, test } from 'qunit';
+import type PhoneInputService from 'ember-phone-input/services/phone-input';
import { setupTest } from 'ember-qunit';
+import { module, test } from 'qunit';
module('Unit | Service | phone-input', function (hooks) {
setupTest(hooks);
test('load is thenable on first and subsequent renders', function (assert) {
assert.expect(2);
- let service = this.owner.lookup('service:phone-input');
+
+ const service = this.owner.lookup(
+ 'service:phone-input'
+ ) as unknown as PhoneInputService;
service.load().then(() => {
assert.ok(true, 'the first load is thenable');
diff --git a/test-app/tsconfig.json b/test-app/tsconfig.json
index 6d69f8a1..ff297e66 100644
--- a/test-app/tsconfig.json
+++ b/test-app/tsconfig.json
@@ -5,8 +5,10 @@
// layout, which is not resolvable with the Node resolution algorithm, to
// work with TypeScript.
"baseUrl": ".",
+ "lib": ["es2015"],
// Type check only the code specifically refered to in the source code.
"skipLibCheck": true,
+ "esModuleInterop": true,
"paths": {
"test-app/tests/*": ["tests/*"],
"test-app/*": ["app/*"],
diff --git a/test-app/types/glint.d.ts b/test-app/types/glint.d.ts
index 2a6c3057..753f50db 100644
--- a/test-app/types/glint.d.ts
+++ b/test-app/types/glint.d.ts
@@ -1,5 +1,6 @@
import '@glint/environment-ember-loose';
+import type PhoneInputRegistry from 'ember-phone-input/template-registry';
declare module '@glint/environment-ember-loose/registry' {
- export default interface Registry {}
+ export default interface Registry extends PhoneInputRegistry {}
}
diff --git a/test-app/types/global.d.ts b/test-app/types/global.d.ts
new file mode 100644
index 00000000..8aba43c6
--- /dev/null
+++ b/test-app/types/global.d.ts
@@ -0,0 +1,7 @@
+// Types for compiled templates
+declare module 'test-app/templates/*' {
+ import type { TemplateFactory } from 'ember-cli-htmlbars';
+
+ const tmpl: TemplateFactory;
+ export default tmpl;
+}
diff --git a/test-app/types/index.d.ts b/test-app/types/test-app/index.d.ts
similarity index 100%
rename from test-app/types/index.d.ts
rename to test-app/types/test-app/index.d.ts