fix(input-tel): rename and export PhoneNumber validator

This commit is contained in:
Thijs Louisse 2022-03-21 23:04:45 +01:00 committed by Thijs Louisse
parent 7c5a25d011
commit 5d5f041ab9
11 changed files with 49 additions and 47 deletions

View file

@ -25,7 +25,7 @@ import { LionInputTel } from '@lion/input-tel';
* @typedef {import('../types').OnDropdownChangeEvent} OnDropdownChangeEvent * @typedef {import('../types').OnDropdownChangeEvent} OnDropdownChangeEvent
* @typedef {import('../types').DropdownRef} DropdownRef * @typedef {import('../types').DropdownRef} DropdownRef
* @typedef {import('../types').RegionMeta} RegionMeta * @typedef {import('../types').RegionMeta} RegionMeta
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
* @typedef {import('@lion/select-rich').LionSelectRich} LionSelectRich * @typedef {import('@lion/select-rich').LionSelectRich} LionSelectRich
* @typedef {import('@lion/overlays').OverlayController} OverlayController * @typedef {import('@lion/overlays').OverlayController} OverlayController
* @typedef {TemplateDataForDropdownInputTel & {data: {regionMetaList:RegionMeta[]}}} TemplateDataForIntlInputTel * @typedef {TemplateDataForDropdownInputTel & {data: {regionMetaList:RegionMeta[]}}} TemplateDataForIntlInputTel

View file

@ -41,6 +41,7 @@ class WithFormControlInputTelDropdown extends LionInputTelDropdown {
}; };
} }
// @ts-expect-error
runInputTelSuite({ klass: LionInputTelDropdown }); runInputTelSuite({ klass: LionInputTelDropdown });
runInputTelDropdownSuite(); runInputTelDropdownSuite();

View file

@ -1,3 +1,4 @@
export { LionInputTel } from './src/LionInputTel.js'; export { LionInputTel } from './src/LionInputTel.js';
export { PhoneNumber } from './src/validators.js';
export { PhoneUtilManager } from './src/PhoneUtilManager.js'; export { PhoneUtilManager } from './src/PhoneUtilManager.js';
export { liveFormatPhoneNumber } from './src/preprocessors.js'; export { liveFormatPhoneNumber } from './src/preprocessors.js';

View file

@ -5,14 +5,14 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
import { liveFormatPhoneNumber } from './preprocessors.js'; import { liveFormatPhoneNumber } from './preprocessors.js';
import { formatPhoneNumber } from './formatters.js'; import { formatPhoneNumber } from './formatters.js';
import { parsePhoneNumber } from './parsers.js'; import { parsePhoneNumber } from './parsers.js';
import { IsPhoneNumber } from './validators.js'; import { PhoneNumber } from './validators.js';
/** /**
* @typedef {import('../types').FormatStrategy} FormatStrategy * @typedef {import('../types').FormatStrategy} FormatStrategy
* @typedef {import('../types').RegionCode} RegionCode * @typedef {import('../types').RegionCode} RegionCode
* @typedef {import('../types').PhoneNumberType} PhoneNumberType * @typedef {import('../types').PhoneNumberType} PhoneNumberType
* @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatOptions} FormatOptions * @typedef {import('@lion/form-core/types/FormatMixinTypes').FormatOptions} FormatOptions
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
* @typedef {FormatOptions & {regionCode: RegionCode; formatStrategy: FormatStrategy}} FormatOptionsTel * @typedef {FormatOptions & {regionCode: RegionCode; formatStrategy: FormatStrategy}} FormatOptionsTel
*/ */
@ -131,17 +131,17 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
this.allowedRegions = []; this.allowedRegions = [];
/** @private */ /** @private */
this.__isPhoneNumberValidatorInstance = new IsPhoneNumber(); this.__isPhoneNumberValidatorInstance = new PhoneNumber();
/** @configures ValidateMixin */ /** @configures ValidateMixin */
this.defaultValidators.push(this.__isPhoneNumberValidatorInstance); this.defaultValidators.push(this.__isPhoneNumberValidatorInstance);
// Expose awesome-phonenumber lib for Subclassers // Expose awesome-phonenumber lib for Subclassers
/** /**
* @protected * @protected
* @type {PhoneNumber|null} * @type {AwesomePhoneNumber|null}
*/ */
this._phoneUtil = PhoneUtilManager.isLoaded this._phoneUtil = PhoneUtilManager.isLoaded
? /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneNumber) ? /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil)
: null; : null;
/** /**
@ -270,7 +270,7 @@ export class LionInputTel extends LocalizeMixin(LionInput) {
*/ */
_onPhoneNumberUtilReady() { _onPhoneNumberUtilReady() {
// This should trigger a rerender in shadow dom // This should trigger a rerender in shadow dom
this._phoneUtil = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneNumber); this._phoneUtil = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneUtil);
// This should trigger a rerender in light dom // This should trigger a rerender in light dom
this._scheduleLightDomRender(); this._scheduleLightDomRender();
// Format when libPhoneNumber is loaded // Format when libPhoneNumber is loaded

View file

@ -9,17 +9,17 @@ let resolveLoaded;
*/ */
export class PhoneUtilManager { export class PhoneUtilManager {
static async loadLibPhoneNumber() { static async loadLibPhoneNumber() {
const PhoneNumber = (await import('../lib/awesome-phonenumber-esm.js')).default; const PhoneUtil = (await import('../lib/awesome-phonenumber-esm.js')).default;
this.PhoneNumber = PhoneNumber; this.PhoneUtil = PhoneUtil;
resolveLoaded(undefined); resolveLoaded(undefined);
return PhoneNumber; return PhoneUtil;
} }
/** /**
* Check if google-libphonenumber has been loaded * Check if google-libphonenumber has been loaded
*/ */
static get isLoaded() { static get isLoaded() {
return Boolean(this.PhoneNumber); return Boolean(this.PhoneUtil);
} }
} }

View file

@ -3,7 +3,7 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
/** /**
* @typedef {import('../types').FormatStrategy} FormatStrategy * @typedef {import('../types').FormatStrategy} FormatStrategy
* @typedef {import('../types').RegionCode} RegionCode * @typedef {import('../types').RegionCode} RegionCode
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
*/ */
/** /**
@ -20,11 +20,11 @@ export function formatPhoneNumber(modelValue, { regionCode, formatStrategy = 'in
} }
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
const PhoneNumber = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneNumber); const PhoneUtil = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
let pn; let pn;
try { try {
pn = new PhoneNumber(modelValue, regionCode); // phoneNumberUtil.parse(modelValue, regionCode); pn = new PhoneUtil(modelValue, regionCode);
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
} catch (_) {} } catch (_) {}

View file

@ -2,7 +2,7 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
/** /**
* @typedef {import('../types').RegionCode} RegionCode * @typedef {import('../types').RegionCode} RegionCode
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
*/ */
/** /**
@ -17,7 +17,7 @@ export function parsePhoneNumber(viewValue, { regionCode }) {
} }
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
const PhoneNumber = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneNumber); const PhoneNumber = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
let pn; let pn;
try { try {

View file

@ -4,7 +4,7 @@ import { formatPhoneNumber } from './formatters.js';
/** /**
* @typedef {import('../types').FormatStrategy} FormatStrategy * @typedef {import('../types').FormatStrategy} FormatStrategy
* @typedef {import('../types').RegionCode} RegionCode * @typedef {import('../types').RegionCode} RegionCode
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
*/ */
/** /**
@ -27,7 +27,7 @@ export function liveFormatPhoneNumber(
} }
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
const PhoneNumber = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneNumber); const PhoneNumber = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
const ayt = PhoneNumber.getAsYouType(regionCode); const ayt = PhoneNumber.getAsYouType(regionCode);
for (const char of viewValue) { for (const char of viewValue) {

View file

@ -3,7 +3,7 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
/** /**
* @typedef {import('../types').RegionCode} RegionCode * @typedef {import('../types').RegionCode} RegionCode
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
*/ */
/** /**
@ -13,7 +13,7 @@ import { PhoneUtilManager } from './PhoneUtilManager.js';
*/ */
function hasFeedback(modelValue, regionCode) { function hasFeedback(modelValue, regionCode) {
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
const PhoneNumber = /** @type {PhoneNumber} */ (PhoneUtilManager.PhoneNumber); const PhoneNumber = /** @type {AwesomePhoneNumber} */ (PhoneUtilManager.PhoneUtil);
let invalidCountryCode = false; let invalidCountryCode = false;
if (regionCode && modelValue?.length >= 4 && modelValue?.length <= 16) { if (regionCode && modelValue?.length >= 4 && modelValue?.length <= 16) {
@ -37,8 +37,8 @@ function hasFeedback(modelValue, regionCode) {
return 'unknown'; return 'unknown';
} }
export class IsPhoneNumber extends Validator { export class PhoneNumber extends Validator {
static validatorName = 'IsPhoneNumber'; static validatorName = 'PhoneNumber';
static get async() { static get async() {
// Will be run as async the first time if PhoneUtilManager hasn't loaded yet, sync afterwards // Will be run as async the first time if PhoneUtilManager hasn't loaded yet, sync afterwards

View file

@ -11,7 +11,7 @@ import sinon from 'sinon';
import { mimicUserInput } from '@lion/form-core/test-helpers'; import { mimicUserInput } from '@lion/form-core/test-helpers';
import { localize } from '@lion/localize'; import { localize } from '@lion/localize';
import { LionInputTel } from '../src/LionInputTel.js'; import { LionInputTel } from '../src/LionInputTel.js';
import { IsPhoneNumber } from '../src/validators.js'; import { PhoneNumber } from '../src/validators.js';
import { PhoneUtilManager } from '../src/PhoneUtilManager.js'; import { PhoneUtilManager } from '../src/PhoneUtilManager.js';
import { import {
mockPhoneUtilManager, mockPhoneUtilManager,
@ -97,7 +97,7 @@ function runActiveRegionTests({ tag, phoneUtilLoadedAfterInit }) {
]}" .modelValue="${'+31612345678'}" ></${tag}> `, ]}" .modelValue="${'+31612345678'}" ></${tag}> `,
); );
if (resolvePhoneUtilLoaded) { if (resolvePhoneUtilLoaded) {
resolvePhoneUtilLoaded(); resolvePhoneUtilLoaded(undefined);
await el.updateComplete; await el.updateComplete;
} }
expect(el.activeRegion).to.equal('NL'); expect(el.activeRegion).to.equal('NL');
@ -287,18 +287,18 @@ export function runInputTelSuite({ klass = LionInputTel } = {}) {
}); });
describe('Validation', () => { describe('Validation', () => {
it('applies IsPhoneNumber as default validator', async () => { it('applies PhoneNumber as default validator', async () => {
const el = await fixture(html` <${tag}></${tag}> `); const el = await fixture(html` <${tag}></${tag}> `);
expect(el.defaultValidators.find(v => v instanceof IsPhoneNumber)).to.be.not.undefined; expect(el.defaultValidators.find(v => v instanceof PhoneNumber)).to.be.not.undefined;
}); });
it('configures IsPhoneNumber with regionCode before first validation', async () => { it('configures PhoneNumber with regionCode before first validation', async () => {
const el = fixtureSync( const el = fixtureSync(
html` <${tag} .allowedRegions="${['NL']}" .modelValue="${'612345678'}"></${tag}> `, html` <${tag} .allowedRegions="${['NL']}" .modelValue="${'612345678'}"></${tag}> `,
); );
const spy = sinon.spy(el, 'validate'); const spy = sinon.spy(el, 'validate');
const validatorInstance = /** @type {IsPhoneNumber} */ ( const validatorInstance = /** @type {PhoneNumber} */ (
el.defaultValidators.find(v => v instanceof IsPhoneNumber) el.defaultValidators.find(v => v instanceof PhoneNumber)
); );
await el.updateComplete; await el.updateComplete;
expect(validatorInstance.param).to.equal('NL'); expect(validatorInstance.param).to.equal('NL');
@ -306,12 +306,12 @@ export function runInputTelSuite({ klass = LionInputTel } = {}) {
spy.restore(); spy.restore();
}); });
it('updates IsPhoneNumber param on regionCode change', async () => { it('updates PhoneNumber param on regionCode change', async () => {
const el = await fixture( const el = await fixture(
html` <${tag} .allowedRegions="${['NL']}" .modelValue="${'612345678'}"></${tag}> `, html` <${tag} .allowedRegions="${['NL']}" .modelValue="${'612345678'}"></${tag}> `,
); );
const validatorInstance = /** @type {IsPhoneNumber} */ ( const validatorInstance = /** @type {PhoneNumber} */ (
el.defaultValidators.find(v => v instanceof IsPhoneNumber) el.defaultValidators.find(v => v instanceof PhoneNumber)
); );
// @ts-expect-error allow protected in tests // @ts-expect-error allow protected in tests
el._setActiveRegion('DE'); el._setActiveRegion('DE');

View file

@ -1,6 +1,6 @@
import sinon from 'sinon'; import sinon from 'sinon';
import { expect, aTimeout } from '@open-wc/testing'; import { expect, aTimeout } from '@open-wc/testing';
import { IsPhoneNumber } from '../src/validators.js'; import { PhoneNumber } from '../src/validators.js';
import { PhoneUtilManager } from '../src/PhoneUtilManager.js'; import { PhoneUtilManager } from '../src/PhoneUtilManager.js';
import { import {
mockPhoneUtilManager, mockPhoneUtilManager,
@ -8,52 +8,52 @@ import {
} from '../test-helpers/mockPhoneUtilManager.js'; } from '../test-helpers/mockPhoneUtilManager.js';
/** /**
* @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} PhoneNumber * @typedef {* & import('@lion/input-tel/lib/awesome-phonenumber-esm').default} AwesomePhoneNumber
*/ */
// For enum output, see: https://www.npmjs.com/package/awesome-phonenumber // For enum output, see: https://www.npmjs.com/package/awesome-phonenumber
describe('IsPhoneNumber validation', () => { describe('PhoneNumber validation', () => {
beforeEach(async () => { beforeEach(async () => {
// Wait till PhoneUtilManager has been loaded // Wait till PhoneUtilManager has been loaded
await PhoneUtilManager.loadComplete; await PhoneUtilManager.loadComplete;
}); });
it('is invalid when no input is provided', () => { it('is invalid when no input is provided', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
expect(validator.execute('', 'NL')).to.equal('unknown'); expect(validator.execute('', 'NL')).to.equal('unknown');
}); });
it('is invalid when non digits are entered, returns "unknown"', () => { it('is invalid when non digits are entered, returns "unknown"', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
expect(validator.execute('foo', 'NL')).to.equal('unknown'); expect(validator.execute('foo', 'NL')).to.equal('unknown');
}); });
it('is invalid when wrong country code is entered, returns "invalid-country-code"', () => { it('is invalid when wrong country code is entered, returns "invalid-country-code"', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
// 32 is BE region code // 32 is BE region code
expect(validator.execute('+32612345678', 'NL')).to.equal('invalid-country-code'); expect(validator.execute('+32612345678', 'NL')).to.equal('invalid-country-code');
}); });
// TODO: find out why awesome-phonenumber does not detect too-short/too-long // TODO: find out why awesome-phonenumber does not detect too-short/too-long
it.skip('is invalid when number is too short, returns "too-short"', () => { it.skip('is invalid when number is too short, returns "too-short"', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
expect(validator.execute('+3161234567', 'NL')).to.equal('too-short'); expect(validator.execute('+3161234567', 'NL')).to.equal('too-short');
}); });
// TODO: find out why awesome-phonenumber does not detect too-short/too-long // TODO: find out why awesome-phonenumber does not detect too-short/too-long
it.skip('is invalid when number is too long, returns "too-long"', () => { it.skip('is invalid when number is too long, returns "too-long"', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
expect(validator.execute('+316123456789', 'NL')).to.equal('too-long'); expect(validator.execute('+316123456789', 'NL')).to.equal('too-long');
}); });
it('is valid when a phone number is entered', () => { it('is valid when a phone number is entered', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
expect(validator.execute('+31612345678', 'NL')).to.be.false; expect(validator.execute('+31612345678', 'NL')).to.be.false;
}); });
it('handles validation via awesome-phonenumber', () => { it('handles validation via awesome-phonenumber', () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
const spy = sinon.spy(PhoneUtilManager, 'PhoneNumber'); const spy = sinon.spy(PhoneUtilManager, 'PhoneUtil');
validator.execute('0123456789', 'NL'); validator.execute('0123456789', 'NL');
expect(spy).to.have.been.calledOnce; expect(spy).to.have.been.calledOnce;
expect(spy.lastCall.args[1]).to.equal('NL'); expect(spy.lastCall.args[1]).to.equal('NL');
@ -74,14 +74,14 @@ describe('IsPhoneNumber validation', () => {
}); });
it('behaves asynchronously when lib is still loading', () => { it('behaves asynchronously when lib is still loading', () => {
expect(IsPhoneNumber.async).to.be.true; expect(PhoneNumber.async).to.be.true;
resolveLoaded(undefined); resolveLoaded(undefined);
expect(IsPhoneNumber.async).to.be.false; expect(PhoneNumber.async).to.be.false;
}); });
it('waits for the lib to be loaded before execution completes when still in async mode', async () => { it('waits for the lib to be loaded before execution completes when still in async mode', async () => {
const validator = new IsPhoneNumber(); const validator = new PhoneNumber();
const spy = sinon.spy(PhoneUtilManager, 'PhoneNumber'); const spy = sinon.spy(PhoneUtilManager, 'PhoneUtil');
const validationResult = validator.execute('061234', 'NL'); const validationResult = validator.execute('061234', 'NL');
expect(validationResult).to.be.instanceOf(Promise); expect(validationResult).to.be.instanceOf(Promise);
expect(spy).to.not.have.been.called; expect(spy).to.not.have.been.called;