Repository: IduxFE/components
Pull request #84 (2020-12-29 07:26:07)

feat: add utils function
re #77

## PR Checklist

Please check if your PR fulfills the following requirements:

- [x] The commit message follows [our guidelines](https://github.com/IduxFE/components/blob/main/docs/contributing.zh.md#commit)
- [x] Tests for the changes have been added/updated or not needed
- [x] Docs and demo have been added/updated or not needed

## PR Type

What kind of change does this PR introduce?

- [ ] Bugfix
- [x] Feature
- [ ] Component style update
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Documentation content changes
- [ ] Application (the showcase website) / infrastructure changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

## What is the new behavior?

## Other information


codecov[bot]: # [Codecov](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=h1) Report
> Merging [#84](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=desc) (9ce076f) into [main](https://codecov.io/gh/IduxFE/components/commit/2d750523bdefd3c8a9f113ae43ae1b43888187a1?el=desc) (2d75052) will **increase** coverage by `0.71%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/IduxFE/components/pull/84/graphs/tree.svg?width=650&height=150&src=pr&token=PGAUXP06V3)](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             main      #84      +/-   ##
==========================================
+ Coverage   92.62%   93.33%   +0.71%     
==========================================
  Files          32       33       +1     
  Lines         393      405      +12     
  Branches       77       83       +6     
==========================================
+ Hits          364      378      +14     
+ Misses         11       10       -1     
+ Partials       18       17       -1     
```


| [Impacted Files](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/cdk/utils/convert.ts](https://codecov.io/gh/IduxFE/components/pull/84/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL3V0aWxzL2NvbnZlcnQudHM=) | `100.00% <100.00%> (ø)` | |
| [packages/cdk/utils/typeof.ts](https://codecov.io/gh/IduxFE/components/pull/84/diff?src=pr&el=tree#diff-cGFja2FnZXMvY2RrL3V0aWxzL3R5cGVvZi50cw==) | `100.00% <100.00%> (ø)` | |
| [packages/components/badge/src/Badge.vue](https://codecov.io/gh/IduxFE/components/pull/84/diff?src=pr&el=tree#diff-cGFja2FnZXMvY29tcG9uZW50cy9iYWRnZS9zcmMvQmFkZ2UudnVl) | `100.00% <100.00%> (+5.12%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=footer). Last update [2d75052...9ce076f](https://codecov.io/gh/IduxFE/components/pull/84?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/packages/cdk/utils/convert.ts b/packages/cdk/utils/convert.ts
new file mode 100644
index 00000000..80f2ae23
--- /dev/null
+++ b/packages/cdk/utils/convert.ts
@@ -0,0 +1,24 @@
+import { isNil, isNumber } from './typeof'
+
+export function toArray<T>(value: T | T[]): T[]
+export function toArray<T>(value: T | readonly T[]): readonly T[]
+export function toArray<T>(value: T | T[]): T[] {
+  return Array.isArray(value) ? value : [value]
+}
+
+export function toNumber(value: unknown): number
+export function toNumber<T>(value: unknown, fallback: T): number | T
+export function toNumber(value: unknown, fallback = 0): number {
+  return isNumber(value) ? Number(value) : fallback
+}
+
+export function toBoolean(value: unknown): boolean {
+  return `${value}` !== 'false' && !!value
+}
+
+export function toCssPixel(value: unknown): string {
+  if (isNil(value)) {
+    return ''
+  }
+  return typeof value === 'string' ? value : `${value}px`
+}
diff --git a/packages/cdk/utils/index.ts b/packages/cdk/utils/index.ts
new file mode 100644
index 00000000..9e57976a
--- /dev/null
+++ b/packages/cdk/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './convert'
+export * from './typeof'
diff --git a/packages/cdk/utils/typeof.ts b/packages/cdk/utils/typeof.ts
new file mode 100644
index 00000000..7d964fd3
--- /dev/null
+++ b/packages/cdk/utils/typeof.ts
@@ -0,0 +1,11 @@
+export function isNumber(value: unknown): boolean {
+  return !isNaN(parseFloat(value as string)) && !isNaN(Number(value))
+}
+
+export function isNil(value: unknown): value is null | undefined {
+  return typeof value === 'undefined' || value === null
+}
+
+export function isNonNil<T>(value: T): value is NonNullable<T> {
+  return typeof value !== 'undefined' && value !== null
+}
diff --git a/packages/components/badge/src/Badge.vue b/packages/components/badge/src/Badge.vue
index f788bb58..cf7ae2ce 100644
--- a/packages/components/badge/src/Badge.vue
+++ b/packages/components/badge/src/Badge.vue
@@ -10,16 +10,16 @@
 <script lang="ts">
 import { computed, ComputedRef, defineComponent, onUpdated, reactive } from 'vue'
 import { BadgeProps, SlotsExist } from './types'
-import { isNumeric } from './utils'
 import { useGlobalConfig } from '@idux/components/core/config'
+import { isNumber } from '@idux/cdk/utils'
 
 export default defineComponent({
   name: 'IxBadge',
   props: {
     count: { type: [Number, String], default: 0 },
-    showZero: Boolean,
+    showZero: { type: Boolean, default: undefined },
     overflowCount: { type: [Number, String], default: undefined },
-    dot: Boolean,
+    dot: { type: Boolean, default: undefined },
     color: { type: String, default: undefined },
   },
   setup(props: BadgeProps, { slots }) {
@@ -63,7 +63,7 @@ const useCountValue = (
   return computed(() => {
     if (!slots.count && !dot.value) {
       if (!showZero.value && +props.count === 0) return false
-      if (isNumeric(props.count) && isNumeric(overflowCount.value)) {
+      if (isNumber(props.count) && isNumber(overflowCount.value)) {
         return +props.count > +overflowCount.value ? `${overflowCount.value}+` : `${props.count}`
       }
       return props.count
diff --git a/packages/components/badge/src/utils.ts b/packages/components/badge/src/utils.ts
deleted file mode 100644
index 18c16986..00000000
--- a/packages/components/badge/src/utils.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export const isDefined = <T>(val: T): val is NonNullable<T> => {
-  return val !== undefined && val !== null
-}
-
-export const isNumeric = (val: string | number): val is number => {
-  return typeof val === 'number' || /^(-|\+)?\d+(\.\d+)?$/.test(val)
-}
diff --git a/scripts/gulp/site/generateSite.ts b/scripts/gulp/site/generateSite.ts
index 3a06f75e..d53af926 100644
--- a/scripts/gulp/site/generateSite.ts
+++ b/scripts/gulp/site/generateSite.ts
@@ -30,7 +30,7 @@ export function generateSite(target: string, targetName?: string): void {
   const isSyncSpecific = target && target !== 'init'
   const demoMeta: Record<string, Record<string, Meta>> = {}
 
-  const filterComponentName = ['style', 'core', 'locale', 'i18n', 'version']
+  const filterComponentName = ['style', 'core', 'locale', 'i18n', 'version', 'utils']
   readdirSync(packageRoot).forEach(packageName => {
     if (isSyncSpecific && packageName !== target) {
       return


Test patch:
diff --git a/packages/cdk/utils/__tests__/convert.spec.ts b/packages/cdk/utils/__tests__/convert.spec.ts
new file mode 100644
index 00000000..9e5b0337
--- /dev/null
+++ b/packages/cdk/utils/__tests__/convert.spec.ts
@@ -0,0 +1,60 @@
+import { toArray, toNumber, toBoolean, toCssPixel } from '../convert'
+
+describe('convert.ts', () => {
+  test('toArray work', async () => {
+    const stringValue = 'string'
+    expect(toArray(stringValue)).toEqual([stringValue])
+
+    const numberValue = 0
+    expect(toArray(numberValue)).toEqual([numberValue])
+
+    const arrayValue = [1, '2', null]
+    expect(toArray(arrayValue)).toEqual(arrayValue)
+  })
+
+  test('toNumber work', async () => {
+    expect(toNumber(100)).toEqual(100)
+    expect(toNumber(100, 1)).toEqual(100)
+
+    expect(toNumber('0')).toEqual(0)
+    expect(toNumber('0', 1)).toEqual(0)
+
+    expect(toNumber(undefined)).toEqual(0)
+    expect(toNumber(undefined, 1)).toEqual(1)
+
+    expect(toNumber(null)).toEqual(0)
+    expect(toNumber(null, 1)).toEqual(1)
+
+    expect(toNumber('')).toEqual(0)
+    expect(toNumber('', 1)).toEqual(1)
+
+    expect(toNumber('1.1')).toEqual(1.1)
+    expect(toNumber('1.1', 1)).toEqual(1.1)
+
+    expect(toNumber('1-1')).toEqual(0)
+    expect(toNumber('1-1', 1)).toEqual(1)
+  })
+
+  test('toBoolean work', async () => {
+    expect(toBoolean(undefined)).toEqual(false)
+    expect(toBoolean(null)).toEqual(false)
+    expect(toBoolean(0)).toEqual(false)
+    expect(toBoolean('')).toEqual(false)
+    expect(toBoolean('false')).toEqual(false)
+    expect(toBoolean(false)).toEqual(false)
+
+    expect(toBoolean('0')).toEqual(true)
+    expect(toBoolean(1)).toEqual(true)
+    expect(toBoolean(true)).toEqual(true)
+    expect(toBoolean('true')).toEqual(true)
+  })
+
+  test('toCssPixel work', async () => {
+    expect(toCssPixel(1)).toEqual('1px')
+    expect(toCssPixel('1px')).toEqual('1px')
+    expect(toCssPixel('1em')).toEqual('1em')
+
+    expect(toCssPixel(undefined)).toEqual('')
+    expect(toCssPixel(null)).toEqual('')
+  })
+})
diff --git a/packages/cdk/utils/__tests__/typeof.spec.ts b/packages/cdk/utils/__tests__/typeof.spec.ts
new file mode 100644
index 00000000..ab120949
--- /dev/null
+++ b/packages/cdk/utils/__tests__/typeof.spec.ts
@@ -0,0 +1,35 @@
+import { isNumber, isNil, isNonNil } from '../typeof'
+
+describe('typeof.ts', () => {
+  test('isNumber work', async () => {
+    expect(isNumber(1)).toEqual(true)
+    expect(isNumber('1')).toEqual(true)
+    expect(isNumber('1.1')).toEqual(true)
+
+    expect(isNumber('1-1')).toEqual(false)
+    expect(isNumber('')).toEqual(false)
+    expect(isNumber(NaN)).toEqual(false)
+  })
+
+  test('isNil work', async () => {
+    expect(isNil(undefined)).toEqual(true)
+    expect(isNil(null)).toEqual(true)
+
+    expect(isNil(1)).toEqual(false)
+    expect(isNil(0)).toEqual(false)
+    expect(isNil('1')).toEqual(false)
+    expect(isNil('')).toEqual(false)
+    expect(isNil({})).toEqual(false)
+  })
+
+  test('isNonNil work', async () => {
+    expect(isNonNil(undefined)).toEqual(false)
+    expect(isNonNil(null)).toEqual(false)
+
+    expect(isNonNil(1)).toEqual(true)
+    expect(isNonNil(0)).toEqual(true)
+    expect(isNonNil('1')).toEqual(true)
+    expect(isNonNil('')).toEqual(true)
+    expect(isNonNil({})).toEqual(true)
+  })
+})
diff --git a/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap b/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap
index 2c940ab3..bbcf8087 100644
--- a/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap
+++ b/packages/components/badge/__tests__/__snapshots__/badge.spec.ts.snap
@@ -8,6 +8,8 @@ exports[`Badge.vue count work 1`] = `"<sup class=\\"ix-badge ix-badge-empty ix-b
 
 exports[`Badge.vue count work 2`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count\\">50</sup>"`;
 
+exports[`Badge.vue count work 3`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count\\">1-1</sup>"`;
+
 exports[`Badge.vue dot work 1`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-count ix-badge-hide-zero\\"><!--v-if--></sup>"`;
 
 exports[`Badge.vue dot work 2`] = `"<sup class=\\"ix-badge ix-badge-empty ix-badge-dot\\"><!--v-if--></sup>"`;
diff --git a/packages/components/badge/__tests__/badge.spec.ts b/packages/components/badge/__tests__/badge.spec.ts
index ab7a7c39..485c0bee 100644
--- a/packages/components/badge/__tests__/badge.spec.ts
+++ b/packages/components/badge/__tests__/badge.spec.ts
@@ -30,6 +30,9 @@ describe('Badge.vue', () => {
     await wrapper.setProps({ count: '50' })
     expect(badge.text()).toBe('50')
     expect(badge.html()).toMatchSnapshot()
+    await wrapper.setProps({ count: '1-1' })
+    expect(badge.text()).toBe('1-1')
+    expect(badge.html()).toMatchSnapshot()
   })
 
   // 是否显示0
diff --git a/packages/components/badge/__tests__/badgeUtils.spec.ts b/packages/components/badge/__tests__/badgeUtils.spec.ts
deleted file mode 100644
index 085a6c43..00000000
--- a/packages/components/badge/__tests__/badgeUtils.spec.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { isDefined, isNumeric } from '../src/utils'
-
-describe('utils.ts', () => {
-  test('isDefined', async () => {
-    const testCases = [
-      { q: '', r: true },
-      { q: 'string', r: true },
-      { q: 1, r: true },
-      { q: {}, r: true },
-      { q: [], r: true },
-      { q: null, r: false },
-      { q: undefined, r: false },
-    ]
-    for (let i = 0; i < testCases.length; i++) {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      const { q, r }: any = testCases[i]
-
-      expect(isDefined(q)).toEqual(r)
-    }
-  })
-
-  test('isNumeric', async () => {
-    const testCases = [
-      { q: '1', r: true },
-      { q: '-1', r: true },
-      { q: '+1', r: true },
-      { q: '1-', r: false },
-      { q: '1-1', r: false },
-      { q: 1, r: true },
-      { q: -1, r: true },
-    ]
-    for (let i = 0; i < testCases.length; i++) {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      const { q, r }: any = testCases[i]
-      expect(isNumeric(q)).toEqual(r)
-    }
-  })
-})