Repository: exadel-inc/esl
Pull request #1074 (2022-06-30 17:46:02)

refactor: move parsers to esl-utils/misc/format


Patch:
diff --git a/src/modules/esl-utils/decorators/attr.ts b/src/modules/esl-utils/decorators/attr.ts
index 56eec5723..ed5e9c523 100644
--- a/src/modules/esl-utils/decorators/attr.ts
+++ b/src/modules/esl-utils/decorators/attr.ts
@@ -1,5 +1,5 @@
 import {identity} from '../misc/functions';
-import {toKebabCase} from '../misc/format';
+import {parseString, toKebabCase} from '../misc/format';
 import {getAttr, setAttr} from '../dom/attr';
 import type {AttributeDecorator, AttributeTarget} from '../dom/attr';
 
@@ -25,10 +25,6 @@ type AttrDescriptor<T = string> = {
 const buildAttrName =
   (propName: string, dataAttr: boolean): string => dataAttr ? `data-${toKebabCase(propName)}` : toKebabCase(propName);
 
-export const toString = (val: string | null): string => val ?? '';
-export const toNumber = parseFloat;
-export const toBoolean = (val: string | null): boolean => val !== null && val !== 'false';
-
 /**
  * Decorator to map current property to element attribute value.
  * Maps string type property.
@@ -41,7 +37,7 @@ export const attr = <T = string>(config: AttrDescriptor<T> = {}): AttributeDecor
     function get(): T | null {
       const val = getAttr(this, attrName);
       if (val === null && 'defaultValue' in config) return config.defaultValue as T;
-      return (config.parser || toString as AttrParser<any>)(val);
+      return (config.parser || parseString as AttrParser<any>)(val);
     }
     function set(value: T): void {
       setAttr(this, attrName, (config.serializer as AttrSerializer<any> || identity)(value));
diff --git a/src/modules/esl-utils/misc/format.ts b/src/modules/esl-utils/misc/format.ts
index 16b227166..eae4c727d 100644
--- a/src/modules/esl-utils/misc/format.ts
+++ b/src/modules/esl-utils/misc/format.ts
@@ -22,6 +22,12 @@ export const unwrapParenthesis = (str: string): string => {
   return str.trim().replace(/^\((.*)\)$/, '$1').trim();
 };
 
+/** Parses `null` and `undefined` as an empty string */
+export const parseString = (val: string | null): string => String(val ?? '');
+
+/** Parses string representation of the boolean value */
+export const parseBoolean = (val: string | null): boolean => val !== null && val !== 'false';
+
 /**
  * Parses number with the ability to pass an alternative fallback for NaN.
  * Note: falsy values except 0 are treated as NaN


Test patch:
diff --git a/src/modules/esl-utils/decorators/test/attr.test.ts b/src/modules/esl-utils/decorators/test/attr.test.ts
index d7e91d8d8..15be8e549 100644
--- a/src/modules/esl-utils/decorators/test/attr.test.ts
+++ b/src/modules/esl-utils/decorators/test/attr.test.ts
@@ -1,5 +1,6 @@
 import '../../../../polyfills/es5-target-shim';
-import {attr, toBoolean, toNumber} from '../attr';
+import {attr} from '../attr';
+import {parseBoolean} from '../../misc/format';
 
 describe('Decorator: attr', () => {
 
@@ -19,9 +20,9 @@ describe('Decorator: attr', () => {
     @attr({defaultValue: 'def'})
     public defField: string | boolean;
 
-    @attr({parser: toNumber})
+    @attr({parser: parseFloat})
     public attrNumber: number;
-    @attr({parser: toBoolean, serializer: Boolean})
+    @attr({parser: parseBoolean, serializer: Boolean})
     public attrBoolean: boolean;
   }
   customElements.define('test-el-attr', TestElement);
diff --git a/src/modules/esl-utils/misc/test/format.test.ts b/src/modules/esl-utils/misc/test/format.test.ts
index ce4057640..77fa80a8b 100644
--- a/src/modules/esl-utils/misc/test/format.test.ts
+++ b/src/modules/esl-utils/misc/test/format.test.ts
@@ -6,7 +6,9 @@ import {
   parseAspectRatio,
   evaluate,
   format,
-  parseNumber
+  parseNumber,
+  parseBoolean,
+  parseString
 } from '../format';
 
 describe('misc/format helper tests', () => {
@@ -96,6 +98,34 @@ describe('misc/format helper tests', () => {
     });
   });
 
+  describe('parseBoolean', () => {
+    test.each([
+      [null, false],
+      ['false', false],
+      ['', true],
+      ['true', true],
+      ['0', true],
+      ['1', true],
+      ['brr', true]
+    ])(
+      'args = %p, result: %p',
+      (args, exp) => expect(parseBoolean.call(null, args)).toBe(exp)
+    );
+  });
+
+  describe('parseString', () => {
+    test.each([
+      [undefined, ''],
+      [null, ''],
+      ['false', 'false'],
+      ['', ''],
+      ['true', 'true']
+    ])(
+      'args = %p, result: %p',
+      (args, exp) => expect(parseString.call(null, args)).toBe(exp)
+    );
+  });
+
   describe('parseNumber', () => {
     test.each([
       [['123'], 123],