diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index aea93db6b..f0c12a8d6 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/ember-phone-input/rollup.config.mjs b/ember-phone-input/rollup.config.mjs index 6e6ae0e91..325c3f0df 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 f4c20f129..6942389fd 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 000000000..4bbb3e735 --- /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 aa5bc4859..f0f911060 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 d8e2088b6..1ba934244 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 76d17cae8..6968e1e37 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 03a279079..038d1e2b7 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 9f513ae81..639c476bc 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 1154e9d04..000000000 --- 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 000000000..fb3dac3b2 --- /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 9dd4db7e0..fa1750864 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 6d69f8a17..ff297e662 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 2a6c30570..753f50dbb 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 000000000..8aba43c64 --- /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