From 6712934782e2d29c6a545c64104af3bc44f3ff80 Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Wed, 1 Feb 2023 10:17:29 +0100 Subject: [PATCH] feat(@lion/ui): getLocalizeManager method, for side-effect-free localize --- .../localize/src/getLocalizeManager.js | 74 +++++++++++++++++++ .../ui/components/localize/src/singleton.js | 15 ++-- .../localize/test/LocalizeManager.test.js | 2 +- .../localize/test/LocalizeMixin.test.js | 32 ++++---- .../localize/test/date/formatDate.test.js | 49 ++++++------ .../date/getDateFormatBasedOnLocale.test.js | 11 ++- .../localize/test/date/parseDate.test.js | 11 +-- .../localize/test/getLocalizeManager.test.js | 30 ++++++++ .../components/localize/test/localize.test.js | 9 ++- .../localize/test/number/formatNumber.test.js | 60 +++++++-------- .../test/number/formatNumberToParts.test.js | 8 +- .../test/number/getCurrencyName.test.js | 2 +- .../localize/test/number/parseNumber.test.js | 8 +- .../test/side-effect-free-entrypoint.test.js | 38 ++++++++++ .../ui/exports/localize-no-side-effects.js | 19 +++++ 15 files changed, 271 insertions(+), 97 deletions(-) create mode 100644 packages/ui/components/localize/src/getLocalizeManager.js create mode 100644 packages/ui/components/localize/test/getLocalizeManager.test.js create mode 100644 packages/ui/components/localize/test/side-effect-free-entrypoint.test.js diff --git a/packages/ui/components/localize/src/getLocalizeManager.js b/packages/ui/components/localize/src/getLocalizeManager.js new file mode 100644 index 000000000..42306960b --- /dev/null +++ b/packages/ui/components/localize/src/getLocalizeManager.js @@ -0,0 +1,74 @@ +// @ts-ignore +import { singletonManager } from 'singleton-manager'; +import { LocalizeManager } from './LocalizeManager.js'; + +/** + * Side-effect-free alternative for `localize` (the globally shared instance of LocalizeManager). + * When this function is imported, no side-effect happened yet, i.e. no global instance was registered yet. + * The side effect-free approach generates: + * - smaller, optimized bundles + * - a predictable loading order, that allows for: + * - deduping strategies when multiple instances of the localizeManager are on a page + * - providing a customized extension of LocalizeManager + * + * Use it like this: + * + * ```js + * function myFunction() { + * // note that 'localizeManager' is the same as former 'localize' + * const localize = getLocalizeManger(); + * // ... + * } + * ``` + * + * In a class, we advise a shared instance: + * ```js + * class MyClass { + * constructor() { + * this._localizeManager = getLocalizeManger(); + * } + * // ... + * } + * ``` + * + * Make sure to always call this method inside a function or class (otherwise side effects are created) + * + * Do you want to register your own LocalizeManager? + * Make sure it's registered before anyone called `getLocalizeManager()` + * + * ```js + * import { singletonManager } from 'singleton-manager'; + * import { getLocalizeManger } from '@lion/ui/localize-no-side-effects.js'; + * + * // First register your own LocalizeManager (for deduping or other reasons) + * singletonManager.set('lion/ui::localize::0.x', class MyLocalizeManager extends LocalizeManager {}); + * + * // Now, all your code gets the right instance + * function myFuntion() { + * const localizeManager = getLocalizeManager(); + * // ... + * } + * + * class myClass() { + * constructor() { + * this._localizeManager = getLocalizeManager(); + * // ... + * } + * } + * ``` + * + * @returns {LocalizeManager} + */ +export function getLocalizeManager() { + if (singletonManager.has('@lion/ui::localize::0.x')) { + return singletonManager.get('@lion/ui::localize::0.x'); + } + + const localizeManager = new LocalizeManager({ + autoLoadOnLocaleChange: true, + fallbackLocale: 'en-GB', + }); + singletonManager.set('@lion/ui::localize::0.x', localizeManager); + + return localizeManager; +} diff --git a/packages/ui/components/localize/src/singleton.js b/packages/ui/components/localize/src/singleton.js index 78dcc3fdf..760878c7a 100644 --- a/packages/ui/components/localize/src/singleton.js +++ b/packages/ui/components/localize/src/singleton.js @@ -1,11 +1,8 @@ -import { singletonManager } from 'singleton-manager'; -import { LocalizeManager } from './LocalizeManager.js'; +import { getLocalizeManager } from './getLocalizeManager.js'; -/** @type {LocalizeManager} */ +/** + * Use the side-effect-free `const localizeManager = getLocalizeManager()` instead. + * @deprecated + */ // eslint-disable-next-line import/no-mutable-exports -export const localize = - /** @type {LocalizeManager} */ (singletonManager.get('@lion/ui::localize::0.x')) || - new LocalizeManager({ - autoLoadOnLocaleChange: true, - fallbackLocale: 'en-GB', - }); +export const localize = getLocalizeManager(); diff --git a/packages/ui/components/localize/test/LocalizeManager.test.js b/packages/ui/components/localize/test/LocalizeManager.test.js index 4215f85a7..00b0b0cae 100644 --- a/packages/ui/components/localize/test/LocalizeManager.test.js +++ b/packages/ui/components/localize/test/LocalizeManager.test.js @@ -4,7 +4,7 @@ import sinon from 'sinon'; import { fetchMock } from '@bundled-es-modules/fetch-mock'; import { setupFakeImport, resetFakeImport, fakeImport } from '@lion/ui/localize-test-helpers.js'; -import { LocalizeManager } from '@lion/ui/localize.js'; +import { LocalizeManager } from '@lion/ui/localize-no-side-effects.js'; /** * @param {LocalizeManager} localizeManagerEl diff --git a/packages/ui/components/localize/test/LocalizeMixin.test.js b/packages/ui/components/localize/test/LocalizeMixin.test.js index f5d014c50..d1d277b94 100644 --- a/packages/ui/components/localize/test/LocalizeMixin.test.js +++ b/packages/ui/components/localize/test/LocalizeMixin.test.js @@ -12,7 +12,7 @@ import { unsafeStatic, } from '@open-wc/testing'; import sinon from 'sinon'; -import { localize, LocalizeMixin } from '@lion/ui/localize.js'; +import { LocalizeMixin, getLocalizeManager } from '@lion/ui/localize-no-side-effects.js'; import { fakeImport, localizeTearDown, @@ -26,6 +26,8 @@ import { */ describe('LocalizeMixin', () => { + const localizeManager = getLocalizeManager(); + afterEach(() => { resetFakeImport(); localizeTearDown(); @@ -48,7 +50,7 @@ describe('LocalizeMixin', () => { setupEmptyFakeImportsFor(['my-element'], ['en-GB']); - const loadNamespaceSpy = sinon.spy(localize, 'loadNamespace'); + const loadNamespaceSpy = sinon.spy(localizeManager, 'loadNamespace'); await fixture(html`<${tag}>`); expect(loadNamespaceSpy.callCount).to.equal(1); @@ -88,7 +90,7 @@ describe('LocalizeMixin', () => { setupEmptyFakeImportsFor(['default', 'parent-element', 'child-element'], ['en-GB']); - const loadNamespaceSpy = sinon.spy(localize, 'loadNamespace'); + const loadNamespaceSpy = sinon.spy(localizeManager, 'loadNamespace'); await fixture(html`<${tag}>`); expect(loadNamespaceSpy.callCount).to.equal(3); @@ -151,7 +153,7 @@ describe('LocalizeMixin', () => { await el.localizeNamespacesLoaded; - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; await el.localizeNamespacesLoaded; expect(onLocaleChangedSpy.callCount).to.equal(0); @@ -159,7 +161,7 @@ describe('LocalizeMixin', () => { wrapper.appendChild(el); // Changing locale will result in onLocaleChanged to be invoked - localize.locale = 'ru-RU'; + localizeManager.locale = 'ru-RU'; await el.localizeNamespacesLoaded; expect(onLocaleChangedSpy.callCount).to.equal(2); expect(onLocaleChangedSpy.calledWithExactly('ru-RU', 'nl-NL')).to.be.true; @@ -183,10 +185,10 @@ describe('LocalizeMixin', () => { onLocaleChanged(newLocale, oldLocale) { super.onLocaleChanged(newLocale, oldLocale); - // Can call localize.msg immediately, without having to await localize.loadingComplete + // Can call localizeManager.msg immediately, without having to await localizeManager.loadingComplete // This is because localeChanged event is fired only after awaiting loading // unless the user disables _autoLoadOnLocaleChange property - this.foo = localize.msg('my-element:foo'); + this.foo = localizeManager.msg('my-element:foo'); } } @@ -198,11 +200,11 @@ describe('LocalizeMixin', () => { const wrapper = await fixture('
'); wrapper.appendChild(el); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; await el.localizeNamespacesLoaded; expect(el.foo).to.equal('bar-nl-NL'); - localize.locale = 'ru-RU'; + localizeManager.locale = 'ru-RU'; await el.localizeNamespacesLoaded; expect(el.foo).to.equal('bar-ru-RU'); }); @@ -233,7 +235,7 @@ describe('LocalizeMixin', () => { await el.localizeNamespacesLoaded; expect(onLocaleUpdatedSpy.callCount).to.equal(1); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; await el.localizeNamespacesLoaded; expect(onLocaleUpdatedSpy.callCount).to.equal(2); }); @@ -261,7 +263,7 @@ describe('LocalizeMixin', () => { async onLocaleUpdated() { super.onLocaleUpdated(); - this.label = localize.msg('my-element:label'); + this.label = localizeManager.msg('my-element:label'); } } @@ -272,7 +274,7 @@ describe('LocalizeMixin', () => { await nextFrame(); // needed as both are added to the micro task que expect(el.label).to.equal('one'); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; await el.localizeNamespacesLoaded; expect(el.label).to.equal('two'); }); @@ -297,7 +299,7 @@ describe('LocalizeMixin', () => { el.connectedCallback(); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; await el.localizeNamespacesLoaded; // await next frame for requestUpdate to be fired @@ -325,7 +327,7 @@ describe('LocalizeMixin', () => { const tagString = defineCE(MyElement); const el = /** @type {MyElement} */ (document.createElement(tagString)); el.connectedCallback(); - const lionLocalizeMessageSpy = sinon.spy(localize, 'msg'); + const lionLocalizeMessageSpy = sinon.spy(localizeManager, 'msg'); const messageDirective = el.msgLit('my-element:greeting'); expect(lionLocalizeMessageSpy.callCount).to.equal(0); @@ -442,7 +444,7 @@ describe('LocalizeMixin', () => { if (el.shadowRoot) { const p = /** @type {HTMLParagraphElement} */ (el.shadowRoot.querySelector('p')); expect(p.innerText).to.equal('Hi!'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(p.innerText).to.equal('Hi!'); await el.localizeNamespacesLoaded; await nextFrame(); // needed because msgLit relies on until directive to re-render diff --git a/packages/ui/components/localize/test/date/formatDate.test.js b/packages/ui/components/localize/test/date/formatDate.test.js index 0ed55dab5..30f4e5a31 100644 --- a/packages/ui/components/localize/test/date/formatDate.test.js +++ b/packages/ui/components/localize/test/date/formatDate.test.js @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { localize, formatDate, parseDate } from '@lion/ui/localize.js'; +import { getLocalizeManager, formatDate, parseDate } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; const SUPPORTED_LOCALES = { @@ -41,6 +41,7 @@ const SUPPORTED_LOCALES = { */ describe('formatDate', () => { + const localizeManager = getLocalizeManager(); beforeEach(() => { localizeTearDown(); }); @@ -50,16 +51,16 @@ describe('formatDate', () => { expect(formatDate(testDate)).to.equal('21/05/2012'); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatDate(testDate)).to.equal('21-05-2012'); - localize.locale = 'fr-FR'; + localizeManager.locale = 'fr-FR'; expect(formatDate(testDate)).to.equal('21/05/2012'); - localize.locale = 'de-DE'; + localizeManager.locale = 'de-DE'; expect(formatDate(testDate)).to.equal('21.05.2012'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(formatDate(testDate)).to.equal('05/21/2012'); }); @@ -74,13 +75,13 @@ describe('formatDate', () => { }; expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatDate(testDate, options)).to.equal('maandag 21 mei 2012'); - localize.locale = 'fr-FR'; + localizeManager.locale = 'fr-FR'; expect(formatDate(testDate, options)).to.equal('lundi 21 mai 2012'); - localize.locale = 'de-DE'; + localizeManager.locale = 'de-DE'; expect(formatDate(testDate, options)).to.equal('Montag, 21. Mai 2012'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); }); @@ -93,7 +94,7 @@ describe('formatDate', () => { day: '2-digit', locale: 'en-US', }; - localize.locale = 'hu-HU'; + localizeManager.locale = 'hu-HU'; let date = /** @type {Date} */ (parseDate('2018-5-28')); expect(formatDate(date)).to.equal('2018. 05. 28.'); @@ -110,7 +111,7 @@ describe('formatDate', () => { day: '2-digit', locale: 'en-US', }; - localize.locale = 'bg-BG'; + localizeManager.locale = 'bg-BG'; let date = /** @type {Date} */ (parseDate('29-12-2017')); expect(formatDate(date)).to.equal('29.12.2017 г.'); @@ -130,7 +131,7 @@ describe('formatDate', () => { day: '2-digit', locale: 'en-US', }; - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; let date = /** @type {Date} */ (parseDate('12-29-1940')); expect(formatDate(date)).to.equal('12/29/1940'); @@ -276,11 +277,11 @@ describe('formatDate', () => { // locale is en-GB expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatDate(testDate, options)).to.equal('MAANDAG 21 MEI 2012'); - localize.locale = 'de-DE'; + localizeManager.locale = 'de-DE'; expect(formatDate(testDate, options)).to.equal('montag, 21. mai 2012'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); }); @@ -293,21 +294,21 @@ describe('formatDate', () => { month: 'long', day: '2-digit', }; - localize.setDatePostProcessorForLocale({ + localizeManager.setDatePostProcessorForLocale({ locale: 'nl-NL', postProcessor: upperCaseProcessor, }); - localize.setDatePostProcessorForLocale({ + localizeManager.setDatePostProcessorForLocale({ locale: 'de-DE', postProcessor: upperCaseProcessor, }); expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatDate(testDate, options)).to.equal('MAANDAG 21 MEI 2012'); - localize.locale = 'de-DE'; + localizeManager.locale = 'de-DE'; expect(formatDate(testDate, options)).to.equal('MONTAG, 21. MAI 2012'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); }); @@ -326,17 +327,17 @@ describe('formatDate', () => { postProcessors, }; - localize.setDatePostProcessorForLocale({ + localizeManager.setDatePostProcessorForLocale({ locale: 'de-DE', postProcessor: lowerCaseProcessor, }); expect(formatDate(testDate, options)).to.equal('Monday, 21 May 2012'); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatDate(testDate, options)).to.equal('MAANDAG 21 MEI 2012'); - localize.locale = 'de-DE'; + localizeManager.locale = 'de-DE'; expect(formatDate(testDate, options)).to.equal('MONTAG, 21. MAI 2012'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(formatDate(testDate, options)).to.equal('Monday, May 21, 2012'); }); }); diff --git a/packages/ui/components/localize/test/date/getDateFormatBasedOnLocale.test.js b/packages/ui/components/localize/test/date/getDateFormatBasedOnLocale.test.js index 579366da3..fb1c90820 100644 --- a/packages/ui/components/localize/test/date/getDateFormatBasedOnLocale.test.js +++ b/packages/ui/components/localize/test/date/getDateFormatBasedOnLocale.test.js @@ -1,16 +1,21 @@ import { expect } from '@open-wc/testing'; -import { localize, getDateFormatBasedOnLocale } from '@lion/ui/localize.js'; +import { + getLocalizeManager, + getDateFormatBasedOnLocale, +} from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; describe('getDateFormatBasedOnLocale()', () => { + const localizeManager = getLocalizeManager(); + beforeEach(() => { localizeTearDown(); }); it('returns the positions of day, month and year', async () => { - localize.locale = 'en-GB'; + localizeManager.locale = 'en-GB'; expect(getDateFormatBasedOnLocale()).to.equal('day-month-year'); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(getDateFormatBasedOnLocale()).to.equal('month-day-year'); }); }); diff --git a/packages/ui/components/localize/test/date/parseDate.test.js b/packages/ui/components/localize/test/date/parseDate.test.js index 05436f802..f68385725 100644 --- a/packages/ui/components/localize/test/date/parseDate.test.js +++ b/packages/ui/components/localize/test/date/parseDate.test.js @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { parseDate, localize } from '@lion/ui/localize.js'; +import { parseDate, getLocalizeManager } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; /** @@ -18,6 +18,7 @@ function equalsDate(value, date) { } describe('parseDate()', () => { + const localizeManager = getLocalizeManager(); beforeEach(() => { localizeTearDown(); }); @@ -45,9 +46,9 @@ describe('parseDate()', () => { }); it('handles different locales', () => { - localize.locale = 'en-GB'; + localizeManager.locale = 'en-GB'; expect(equalsDate(parseDate('31-12-1976'), new Date('1976/12/31'))).to.equal(true); - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(equalsDate(parseDate('12-31-1976'), new Date('1976/12/31'))).to.equal(true); }); @@ -55,11 +56,11 @@ describe('parseDate()', () => { expect(parseDate('12.12.1976.,')).to.equal(undefined); // wrong delimiter expect(parseDate('foo')).to.equal(undefined); // no date - localize.locale = 'en-GB'; + localizeManager.locale = 'en-GB'; expect(parseDate('31.02.2020')).to.equal(undefined); // non existing date expect(parseDate('12.31.2020')).to.equal(undefined); // day & month switched places - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(parseDate('02.31.2020')).to.equal(undefined); // non existing date expect(parseDate('31.12.2020')).to.equal(undefined); // day & month switched places }); diff --git a/packages/ui/components/localize/test/getLocalizeManager.test.js b/packages/ui/components/localize/test/getLocalizeManager.test.js new file mode 100644 index 000000000..3fe4c2f39 --- /dev/null +++ b/packages/ui/components/localize/test/getLocalizeManager.test.js @@ -0,0 +1,30 @@ +import { expect } from '@open-wc/testing'; +// @ts-ignore +import { singletonManager } from 'singleton-manager'; +import { LocalizeManager } from '../src/LocalizeManager.js'; +import { getLocalizeManager } from '../src/getLocalizeManager.js'; + +describe('getLocalizeManager', () => { + beforeEach(() => { + singletonManager._map.clear(); + }); + + it('gets a default instance when nothing registered on singletonManager with "@lion/ui::localize::0.x"', () => { + expect(singletonManager.get('@lion/ui::localize::0.x')).to.be.undefined; + const localizeManager = getLocalizeManager(); + expect(localizeManager).to.equal(singletonManager.get('@lion/ui::localize::0.x')); + }); + + it('gets the same instance when called multiple times', () => { + const localizeManager = getLocalizeManager(); + const localizeManagerSecondCall = getLocalizeManager(); + expect(localizeManager).to.equal(localizeManagerSecondCall); + }); + + it('gets the instance that was registered on singletonManager with "@lion/ui::localize::0.x"', () => { + // Set your own for custom behavior or for deduping purposes + class MyLocalizeManager extends LocalizeManager {} + singletonManager.set('@lion/ui::localize::0.x', MyLocalizeManager); + expect(getLocalizeManager()).to.equal(MyLocalizeManager); + }); +}); diff --git a/packages/ui/components/localize/test/localize.test.js b/packages/ui/components/localize/test/localize.test.js index 8a8292ce0..d3fa6cc90 100644 --- a/packages/ui/components/localize/test/localize.test.js +++ b/packages/ui/components/localize/test/localize.test.js @@ -1,6 +1,6 @@ import { expect } from '@open-wc/testing'; -import { localize, LocalizeManager } from '@lion/ui/localize.js'; +import { getLocalizeManager, LocalizeManager } from '@lion/ui/localize-no-side-effects.js'; /** * @param {LocalizeManager} localizeManagerEl @@ -16,6 +16,7 @@ function getProtectedMembers(localizeManagerEl) { } describe('localize', () => { + const localizeManager = getLocalizeManager(); // this is an important mindset: // we don't test the singleton // we check that it is an instance of the right class @@ -23,16 +24,16 @@ describe('localize', () => { // this allows to avoid any side effects caused by changing singleton state between tests it('is an instance of LocalizeManager', () => { - expect(localize).to.be.an.instanceOf(LocalizeManager); + expect(localizeManager).to.be.an.instanceOf(LocalizeManager); }); it('is configured to automatically load namespaces if locale is changed', () => { - const { autoLoadOnLocaleChange } = getProtectedMembers(localize); + const { autoLoadOnLocaleChange } = getProtectedMembers(localizeManager); expect(autoLoadOnLocaleChange).to.equal(true); }); it('is configured to fallback to the locale "en-GB"', () => { - const { fallbackLocale } = getProtectedMembers(localize); + const { fallbackLocale } = getProtectedMembers(localizeManager); expect(fallbackLocale).to.equal('en-GB'); }); }); diff --git a/packages/ui/components/localize/test/number/formatNumber.test.js b/packages/ui/components/localize/test/number/formatNumber.test.js index bc9540d6b..21a4552a1 100644 --- a/packages/ui/components/localize/test/number/formatNumber.test.js +++ b/packages/ui/components/localize/test/number/formatNumber.test.js @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { localize, formatNumber } from '@lion/ui/localize.js'; +import { getLocalizeManager, formatNumber } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; // TODO: This is broken only in Safari 13.1.2 Wait till ci is on 13.1.3 and remove @@ -20,6 +20,8 @@ const currencySymbol = /** @param {string} currency */ currency => ({ }); describe('formatNumber', () => { + const localizeManager = getLocalizeManager(); + afterEach(localizeTearDown); it('displays the appropriate amount of decimal places based on currencies spec http://www.currency-iso.org/en/home/tables/table-a1.html', () => { @@ -74,14 +76,14 @@ describe('formatNumber', () => { expect(formatNumber(undefined)).to.equal(''); }); - it('uses `localize.formatNumberOptions.returnIfNaN`', () => { - const savedReturnIfNaN = localize.formatNumberOptions.returnIfNaN; + it('uses `localizeManager.formatNumberOptions.returnIfNaN`', () => { + const savedReturnIfNaN = localizeManager.formatNumberOptions.returnIfNaN; - localize.formatNumberOptions.returnIfNaN = '-'; + localizeManager.formatNumberOptions.returnIfNaN = '-'; // @ts-ignore expect(formatNumber('foo')).to.equal('-'); - localize.formatNumberOptions.returnIfNaN = savedReturnIfNaN; + localizeManager.formatNumberOptions.returnIfNaN = savedReturnIfNaN; }); it("can set what to returns when NaN via `returnIfNaN: 'foo'`", () => { @@ -89,11 +91,11 @@ describe('formatNumber', () => { expect(formatNumber('foo', { returnIfNaN: '-' })).to.equal('-'); }); - it('uses `localize.locale`', () => { + it('uses `localizeManager.locale`', () => { expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( '123,456.79', ); - localize.locale = 'de-DE'; + localizeManager.locale = 'de-DE'; expect(formatNumber(123456.789, { style: 'decimal', maximumFractionDigits: 2 })).to.equal( '123.456,79', ); @@ -136,7 +138,7 @@ describe('formatNumber', () => { }); it('formats numbers correctly', () => { - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatNumber(0, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,00'); expect(formatNumber(0.1, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,10'); expect(formatNumber(0.12, { style: 'decimal', minimumFractionDigits: 2 })).to.equal('0,12'); @@ -190,7 +192,7 @@ describe('formatNumber', () => { }); it('throws when decimal and group separator are the same value, only when problematic', () => { - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; const fn = () => formatNumber(112345678, { style: 'decimal', @@ -239,7 +241,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t }); it('formats 2-digit decimals correctly', () => { - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; Array.from(new Array(100), (val, index) => index).forEach(i => { const iString = `${i}`; let number = 0.0; @@ -253,7 +255,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('normalization', () => { describe('en-GB', () => { it('supports basics', () => { - localize.locale = 'en-GB'; + localizeManager.locale = 'en-GB'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); @@ -265,7 +267,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('en-US', () => { it('supports basics', () => { - localize.locale = 'en-US'; + localizeManager.locale = 'en-US'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); @@ -282,7 +284,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t } it('supports basics', () => { - localize.locale = 'en-AU'; + localizeManager.locale = 'en-AU'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); @@ -294,7 +296,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('en-PH', () => { it('supports basics', () => { - localize.locale = 'en-PH'; + localizeManager.locale = 'en-PH'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('EUR 123,456.79'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('USD 123,456.79'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('JPY 123,457'); @@ -306,7 +308,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('nl-NL', () => { it('supports basics', () => { - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); @@ -323,7 +325,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t } it('supports basics', () => { - localize.locale = 'nl-BE'; + localizeManager.locale = 'nl-BE'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); @@ -335,7 +337,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('fr-FR', () => { it('supports basics', () => { - localize.locale = 'fr-FR'; + localizeManager.locale = 'fr-FR'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); @@ -351,7 +353,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t if (isSafari) { return; } - localize.locale = 'fr-BE'; + localizeManager.locale = 'fr-BE'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); @@ -363,7 +365,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('bg-BG', () => { it('supports basics', () => { - localize.locale = 'bg-BG'; + localizeManager.locale = 'bg-BG'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); @@ -373,7 +375,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t }); it('normalizes group separator', () => { - localize.locale = 'bg-BG'; + localizeManager.locale = 'bg-BG'; expect(formatNumber(1.234, currencyCode('EUR'))).to.equal('1,23 EUR'); expect(formatNumber(1234.567, currencyCode('EUR'))).to.equal('1 234,57 EUR'); expect(formatNumber(-1234.567, currencyCode('EUR'))).to.equal('−1 234,57 EUR'); @@ -382,7 +384,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t describe('cs-CZ', () => { it('supports basics', () => { - localize.locale = 'cs-CZ'; + localizeManager.locale = 'cs-CZ'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123 456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123 456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123 457 JPY'); @@ -401,7 +403,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t } it('supports basics', () => { - localize.locale = 'tr-TR'; + localizeManager.locale = 'tr-TR'; expect(formatNumber(123456.789, currencyCode('EUR'))).to.equal('123.456,79 EUR'); expect(formatNumber(123456.789, currencyCode('USD'))).to.equal('123.456,79 USD'); expect(formatNumber(123456.789, currencyCode('JPY'))).to.equal('123.457 JPY'); @@ -413,7 +415,7 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t }); it('forces turkish currency code ', () => { - localize.locale = 'tr-TR'; + localizeManager.locale = 'tr-TR'; expect( formatNumber(1234.56, { style: 'currency', currencyDisplay: 'code', currency: 'TRY' }), ).to.equal('1.234,56 TL'); @@ -428,10 +430,10 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t /** @type {Map} */ let savedpostProcessors; beforeEach(() => { - savedpostProcessors = localize.formatNumberOptions.postProcessors; + savedpostProcessors = localizeManager.formatNumberOptions.postProcessors; }); afterEach(() => { - localize.formatNumberOptions.postProcessors = savedpostProcessors; + localizeManager.formatNumberOptions.postProcessors = savedpostProcessors; }); /** @@ -460,8 +462,8 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t ).to.equal('112 345 678'); }); - it('uses `localize.formatNumberOptions.postProcessors`', () => { - localize.setNumberPostProcessorForLocale({ + it('uses `localizeManager.formatNumberOptions.postProcessors`', () => { + localizeManager.setNumberPostProcessorForLocale({ locale: 'en-GB', postProcessor: commaToSpaceProcessor, }); @@ -469,11 +471,11 @@ Please specify .groupSeparator / .decimalSeparator on the formatOptions object t expect(formatNumber(112345678)).to.equal('112 345 678'); }); - it('uses `options.postProcessors` and `localize.formatNumberOptions.postProcessors`', () => { + it('uses `options.postProcessors` and `localizeManager.formatNumberOptions.postProcessors`', () => { const postProcessors = new Map(); postProcessors.set('en-GB', commaToSpaceProcessor); - localize.setNumberPostProcessorForLocale({ + localizeManager.setNumberPostProcessorForLocale({ locale: 'en-GB', postProcessor: firstSpaceToDotProcessor, }); diff --git a/packages/ui/components/localize/test/number/formatNumberToParts.test.js b/packages/ui/components/localize/test/number/formatNumberToParts.test.js index 9c7871452..d008a8dbe 100644 --- a/packages/ui/components/localize/test/number/formatNumberToParts.test.js +++ b/packages/ui/components/localize/test/number/formatNumberToParts.test.js @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { localize, formatNumberToParts } from '@lion/ui/localize.js'; +import { getLocalizeManager, formatNumberToParts } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; // TODO: This is broken only in Safari 13.1.2 Wait till ci is on 13.1.3 and remove @@ -28,6 +28,8 @@ const stringifyParts = parts => parts.map(part => part.value).join(''); describe('formatNumberToParts', () => { + const localizeManager = getLocalizeManager(); + afterEach(localizeTearDown); describe("style: 'currency symbol'", () => { @@ -114,7 +116,7 @@ describe('formatNumberToParts', () => { it(`formats ${locale} ${amount} as "${stringifyParts( /** @type {FormatNumberPart[]} */ (expectedResult), )}"`, () => { - localize.locale = locale; + localizeManager.locale = locale; expect( formatNumberToParts(Number(amount), { style: 'decimal', @@ -143,7 +145,7 @@ describe('formatNumberToParts', () => { it(`formats ${locale} ${amount} as "${stringifyParts( /** @type {FormatNumberPart[]} */ (expectedResult), )}"`, () => { - localize.locale = locale; + localizeManager.locale = locale; expect( formatNumberToParts(Number(amount), { style: 'decimal', diff --git a/packages/ui/components/localize/test/number/getCurrencyName.test.js b/packages/ui/components/localize/test/number/getCurrencyName.test.js index 4181fed97..e43e287ed 100644 --- a/packages/ui/components/localize/test/number/getCurrencyName.test.js +++ b/packages/ui/components/localize/test/number/getCurrencyName.test.js @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { getCurrencyName } from '@lion/ui/localize.js'; +import { getCurrencyName } from '@lion/ui/localize-no-side-effects.js'; import { localizeTearDown } from '@lion/ui/localize-test-helpers.js'; describe('getCurrencyName', () => { diff --git a/packages/ui/components/localize/test/number/parseNumber.test.js b/packages/ui/components/localize/test/number/parseNumber.test.js index 1f0a57250..b76dcb729 100644 --- a/packages/ui/components/localize/test/number/parseNumber.test.js +++ b/packages/ui/components/localize/test/number/parseNumber.test.js @@ -1,7 +1,9 @@ import { expect } from '@open-wc/testing'; -import { localize, parseNumber } from '@lion/ui/localize.js'; +import { getLocalizeManager, parseNumber } from '@lion/ui/localize-no-side-effects.js'; describe('parseNumber()', () => { + const localizeManager = getLocalizeManager(); + it('parses integers', () => { expect(parseNumber('1')).to.equal(1); expect(parseNumber('12')).to.equal(12); @@ -79,13 +81,13 @@ describe('parseNumber()', () => { }); it('uses locale to parse amount if there is only one separator e.g. 1.234', () => { - localize.locale = 'en-GB'; + localizeManager.locale = 'en-GB'; expect(parseNumber('12.34')).to.equal(12.34); expect(parseNumber('12,34')).to.equal(1234); expect(parseNumber('1.234')).to.equal(1.234); expect(parseNumber('1,234')).to.equal(1234); - localize.locale = 'nl-NL'; + localizeManager.locale = 'nl-NL'; expect(parseNumber('12.34')).to.equal(1234); expect(parseNumber('12,34')).to.equal(12.34); expect(parseNumber('1.234')).to.equal(1234); diff --git a/packages/ui/components/localize/test/side-effect-free-entrypoint.test.js b/packages/ui/components/localize/test/side-effect-free-entrypoint.test.js new file mode 100644 index 000000000..36a54ab66 --- /dev/null +++ b/packages/ui/components/localize/test/side-effect-free-entrypoint.test.js @@ -0,0 +1,38 @@ +import { expect } from '@open-wc/testing'; +import sinon from 'sinon'; +// @ts-ignore +import { singletonManager } from 'singleton-manager'; + +/** + * @typedef {import('../types/LocalizeMixinTypes.js').LocalizeMixin} LocalizeMixinHost + */ + +describe('Entrypoints localize', () => { + /** @type {import('sinon').SinonSpy} */ + let singletonManagerSetSpy; + beforeEach(() => { + singletonManagerSetSpy = sinon.spy(singletonManager, 'set'); + }); + afterEach(() => { + singletonManagerSetSpy.restore(); + }); + + it('"@lion/ui/localize-no-side-effects.js" has no side effects (c.q. does not register itself on singletonManager)', async () => { + await import('@lion/ui/localize-no-side-effects.js'); + + expect(singletonManagerSetSpy).to.not.have.been.called; + }); + + it('"@lion/ui/localize.js" has side effects (c.q. registers itself on singletonManager)', async () => { + await import('@lion/ui/localize.js'); + + expect(singletonManagerSetSpy).to.have.been.calledOnce; + + const { getLocalizeManager } = await import('@lion/ui/localize-no-side-effects.js'); + + expect(singletonManagerSetSpy).to.have.been.calledWith( + '@lion/ui::localize::0.x', + getLocalizeManager(), + ); + }); +}); diff --git a/packages/ui/exports/localize-no-side-effects.js b/packages/ui/exports/localize-no-side-effects.js index c98021fa5..087478b32 100644 --- a/packages/ui/exports/localize-no-side-effects.js +++ b/packages/ui/exports/localize-no-side-effects.js @@ -1 +1,20 @@ export { LocalizeManager } from '../components/localize/src/LocalizeManager.js'; +export { formatDate } from '../components/localize/src/date/formatDate.js'; +export { getDateFormatBasedOnLocale } from '../components/localize/src/date/getDateFormatBasedOnLocale.js'; +export { getMonthNames } from '../components/localize/src/date/getMonthNames.js'; +export { getWeekdayNames } from '../components/localize/src/date/getWeekdayNames.js'; +export { normalizeDateTime } from '../components/localize/src/date/normalizeDateTime.js'; +export { parseDate } from '../components/localize/src/date/parseDate.js'; +export { LocalizeMixin } from '../components/localize/src/LocalizeMixin.js'; +export { formatNumber } from '../components/localize/src/number/formatNumber.js'; +export { formatNumberToParts } from '../components/localize/src/number/formatNumberToParts.js'; +export { getCurrencyName } from '../components/localize/src/number/getCurrencyName.js'; +export { getDecimalSeparator } from '../components/localize/src/number/getDecimalSeparator.js'; +export { getFractionDigits } from '../components/localize/src/number/getFractionDigits.js'; +export { getGroupSeparator } from '../components/localize/src/number/getGroupSeparator.js'; +export { getSeparatorsFromNumber } from '../components/localize/src/number/getSeparatorsFromNumber.js'; +export { normalizeCurrencyLabel } from '../components/localize/src/number/normalizeCurrencyLabel.js'; +export { parseNumber } from '../components/localize/src/number/parseNumber.js'; +export { getLocale } from '../components/localize/src/utils/getLocale.js'; + +export { getLocalizeManager } from '../components/localize/src/getLocalizeManager.js';