diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml
index bcb5b07..8644915 100755
--- a/.github/workflows/ci-test.yml
+++ b/.github/workflows/ci-test.yml
@@ -12,49 +12,27 @@ jobs:
strategy:
matrix:
- node_version: [18.x, 20.x, 22.x]
+ deno-version: [1.44.4]
steps:
- - uses: actions/checkout@v4
-
- - name: setup Node.js v${{ matrix.node_version }}
- uses: actions/setup-node@v4
- with:
- node-version: ${{ matrix.node_version }}
-
- - name: setup timezone
- uses: zcong1993/setup-timezone@master
- with:
- timezone: ${{ secrets.TZ }}
-
- - name: run npm scripts
- run: |
- npm install
- npm run lint
- npm run test
-
- - name: Coveralls Parallel
- uses: coverallsapp/github-action@v2
- with:
- flag-name: run-${{ join(matrix.*, '-') }}
- parallel: true
- github-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: cache node modules
- uses: actions/cache@v4
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
-
- finish:
- needs: test
- if: ${{ always() }}
- runs-on: ubuntu-latest
- steps:
- - name: Coveralls Finished
- uses: coverallsapp/github-action@v2
- with:
- parallel-finished: true
- carryforward: "run-18.x,run-20.x,run-22.x"
+ - name: Git Checkout Deno Module
+ uses: actions/checkout@v4
+ - name: Use Deno Version ${{ matrix.deno-version }}
+ uses: denoland/setup-deno@v1
+ with:
+ deno-version: ${{ matrix.deno-version }}
+ - name: format check
+ run: deno fmt --check mod.ts utils/* scripts/* tests/*
+ - name: run linter
+ run: deno lint mod.ts utils/* scripts/* tests/*
+ - name: run test
+ run: deno test --allow-all --coverage=cov/
+
+ - name: Generate coverage report
+ run: deno coverage --lcov cov > cov.lcov
+
+ - name: Upload coverage to Coveralls.io
+ uses: coverallsapp/github-action@v2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ path-to-lcov: cov.lcov
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a77d776..102df5a 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,14 +1,3 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
name: "CodeQL"
on:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..b1c0ff2
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,16 @@
+name: publish
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write # The OIDC ID token is used for authentication with JSR.
+ steps:
+ - uses: actions/checkout@v4
+ - run: npx jsr publish
diff --git a/.gitignore b/.gitignore
index 47be9c0..4b39c21 100755
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,8 @@ yarn.lock
coverage.lcov
package-lock.json
pnpm-lock.yaml
+deno.lock
+
+npm
+cov
+cov.lcov
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 7f1eb85..0000000
--- a/.npmignore
+++ /dev/null
@@ -1,8 +0,0 @@
-node_modules
-coverage
-.github
-pnpm-lock.yaml
-examples
-build.js
-build.test.js
-reset.js
diff --git a/README.md b/README.md
index c0ba8c8..11ac483 100755
--- a/README.md
+++ b/README.md
@@ -1,524 +1,604 @@
-BellaJS
-========
-
-Lightweight util for handling data type, string... in your Node.js and browser apps.
-
-[![NPM](https://badge.fury.io/js/bellajs.svg)](https://badge.fury.io/js/bellajs)
-![CodeQL](https://github.com/ndaidong/bellajs/workflows/CodeQL/badge.svg)
-![CI test](https://github.com/ndaidong/bellajs/workflows/ci-test/badge.svg)
-[![Coverage Status](https://coveralls.io/repos/github/ndaidong/bellajs/badge.svg)](https://coveralls.io/github/ndaidong/bellajs)
-
-# Contents
-
-* [Setup](#setup)
-* [APIs](#apis)
- * [DataType detection](#datatype-detection)
- * [String manipulation](#string-manipulation)
- * [Data handling](#data-handling): [`clone`](#cloneanything-val), [`copies`](#copiesobject-source-object-target-boolean-requirematching-array-excepts)
- * [Array utils](#array-utils): [`pick`](#pickarray-arr--number-count--1), [`sort`](#sortarray-arr--function-compare), [`sortBy`](#sortbyarray-arr-number-order-string-property), [`shuffle`](#shufflearray-arr), [`unique`](#uniquearray-arr)
- * [Functional utils](#functional-utils): [`curry`](#curryfn), [`compose`](#composef1-f2-fn), [`pipe`](#pipef1-f2-fn), [`maybe`](#maybeanything-val)
- * [Date utils](#date-utils): [`formatDateString`](#formatdatestringdate--timestamp--string-locale--object-options), [`formatTimeAgo`](#formattimeagodate--timestamp--string-locale--string-justnow)
- * [Random utils](#random-utils): [`randint`](#randintnumber-min--number-max), [`genid`](#genidnumber-length--string-prefix)
-
-* [Test](#test)
-
-* [License](#license)
-
-## Install & Usage
-
-### Node.js
-
-```bash
-npm i bellajs
-
-# pnpm
-pnpm i bellajs
-
-# yarn
-yarn add bellajs
-```
-
-### Deno
-
-```ts
-import { genid } from 'https://esm.sh/bellajs'
-
-console.log(genid())
-```
-
-### Browser
-
-```html
-
-```
-
-## APIs
-
-### DataType detection
-
-- `.isArray(Anything val)`
-- `.isBoolean(Anything val)`
-- `.isDate(Anything val)`
-- `.isElement(Anything val)`
-- `.isEmail(Anything val)`
-- `.isEmpty(Anything val)`
-- `.isFunction(Anything val)`
-- `.isInteger(Anything val)`
-- `.isLetter(Anything val)`
-- `.isNil(Anything val)`
-- `.isNull(Anything val)`
-- `.isNumber(Anything val)`
-- `.isObject(Anything val)`
-- `.isString(Anything val)`
-- `.isUndefined(Anything val)`
-
-### String manipulation
-
-- `.ucfirst(String s)`
-- `.ucwords(String s)`
-- `.escapeHTML(String s)`
-- `.unescapeHTML(String s)`
-- `.slugify(String s)`
-- `.stripTags(String s)`
-- `.stripAccent(String s)`
-- `.truncate(String s, Number limit)`
-- `.replaceAll(String s, String|Array search, String|Array replace)`
-
-
-### Data handling
-
-#### `clone(Anything val)`
-
-Make a deep copy of a variable.
-
-```js
-import { clone } from 'bellajs'
-
-const b = [
- 1, 5, 0, 'a', -10, '-10', '',
- {
- a: 1,
- b: 'Awesome'
- }
-]
-
-const cb = clone(b)
-console.log(cb)
-```
-
-*cb* now has the same values as *b*, while the properties are standalone, not reference. So that:
-
-```js
-cb[7].a = 2
-cb[7].b = 'Noop'
-
-console.log(b[7])
-```
-
-What you get is still:
-
-```js
-{
- a: 1,
- b: 'Awesome'
-}
-```
-
-#### `copies(Object source, Object target[[, Boolean requireMatching], Array excepts])`
-
-Copy the properties from *source* to *target*.
-
-- *requireMatching*: if true, BellaJS only copies the properties that are already exist in *target*.
-- *excepts*: array of the properties properties in *source* that you don't want to copy.
-
-After this action, target will be modified.
-
-```js
-import { copies } from 'bellajs'
-
-const a = {
- name: 'Toto',
- age: 30,
- level: 8,
- nationality: {
- name: 'America'
- }
-}
-const b = {
- level: 4,
- IQ: 140,
- epouse: {
- name: 'Alice',
- age: 27
- },
- nationality: {
- long: '18123.123123.12312',
- lat: '98984771.134231.1234'
- }
-}
-
-copies(a, b)
-console.log(b)
-```
-
-Output:
-
-```js
-{
- level: 8,
- IQ: 140,
- epouse: {
- name: 'Alice',
- age: 27
- },
- nationality: {
- long: '18123.123123.12312',
- lat: '98984771.134231.1234',
- name: 'America'
- },
- name: 'Toto',
- age: 30
-}
-```
-
-### Array utils
-
-#### `pick(Array arr [, Number count = 1])`
-
-Randomly choose N elements from array.
-
-```js
-import { pick } from 'bellajs'
-
-const arr = [1, 3, 8, 2, 5, 7]
-pick(arr, 2) // --> [3, 5]
-pick(arr, 2) // --> [8, 1]
-pick(arr) // --> [3]
-pick(arr) // --> [7]
-```
-
-
-#### `sort(Array arr [, Function compare])`
-
-Sort the array using a function.
-
-```js
-import { sort } from 'bellajs'
-
-const fn = (a, b) => {
- return a < b ? 1 : a > b ? -1 : 0
-}
-
-sort([3, 1, 5, 2], fn) // => [ 1, 2, 3, 5 ]
-```
-
-#### `sortBy(Array arr, Number order, String property)`
-
-Sort the array by specific property and direction.
-
-```js
-import { sortBy } from 'bellajs'
-
-const players = [
- {
- name: 'Jerome Nash',
- age: 24
- },
- {
- name: 'Jackson Valdez',
- age: 21
- },
- {
- name: 'Benjamin Cole',
- age: 23
- },
- {
- name: 'Manuel Delgado',
- age: 33
- },
- {
- name: 'Caleb McKinney',
- age: 28
- }
-]
-
-const result = sortBy(players, -1, 'age')
-console.log(result)
-```
-
-#### `shuffle(Array arr)`
-
-Shuffle the positions of elements in an array.
-
-```js
-import { shuffle } from 'bellajs'
-
-shuffle([1, 3, 8, 2, 5, 7])
-```
-
-#### `unique(Array arr)`
-
-Remove all duplicate elements from an array.
-
-```js
-import { unique } from 'bellajs'
-
-unique([1, 2, 3, 2, 3, 1, 5]) // => [ 1, 2, 3, 5 ]
-```
-
-### Functional utils
-
-#### `curry(fn)`
-
-Make a curried function.
-
-```js
-import { curry } from 'bellajs'
-
-const sum = curry((a, b, c) => {
- return a + b + c
-})
-
-sum(3)(2)(1) // => 6
-sum(1)(2)(3) // => 6
-sum(1, 2)(3) // => 6
-sum(1)(2, 3) // => 6
-sum(1, 2, 3) // => 6
-```
-
-#### `compose(f1, f2, ...fN)`
-
-Performs right-to-left function composition.
-
-```js
-import { compose } from 'bellajs'
-
-const f1 = (name) => {
- return `f1 ${name}`
-}
-const f2 = (name) => {
- return `f2 ${name}`
-}
-const f3 = (name) => {
- return `f3 ${name}`
-}
-
-const addF = compose(f1, f2, f3)
-
-addF('Hello') // => 'f1 f2 f3 Hello'
-
-const add1 = (num) => {
- return num + 1
-}
-
-const mult2 = (num) => {
- return num * 2
-}
-
-const add1AndMult2 = compose(add1, mult2)
-add1AndMult2(3) // => 7
-// because multiple to 2 first, then add 1 late => 3 * 2 + 1
-```
-
-
-#### `pipe(f1, f2, ...fN)`
-
-Performs left-to-right function composition.
-
-```js
-import { pipe } from 'bellajs'
-
-const f1 = (name) => {
- return `f1 ${name}`
-}
-const f2 = (name) => {
- return `f2 ${name}`
-}
-const f3 = (name) => {
- return `f3 ${name}`
-}
-
-const addF = pipe(f1, f2, f3)
-
-addF('Hello') // => 'f3 f2 f1 Hello'
-
-const add1 = (num) => {
- return num + 1
-}
-
-const mult2 = (num) => {
- return num * 2
-}
-
-const add1AndMult2 = pipe(add1, mult2)
-add1AndMult2(3) // => 8
-// because add 1 first, then multiple to 2 late => (3 + 1) * 2
-```
-
-#### `maybe(Anything val)`
-
-Return a static variant of `Maybe` monad.
-
-```js
-import { maybe } from 'bellajs'
-
-const plus5 = x => x + 5
-const minus2 = x => x - 2
-const isNumber = x => Number(x) === x
-const toString = x => 'The value is ' + String(x)
-const getDefault = () => 'This is default value'
-
-maybe(5)
- .map(plus5)
- .map(minus2)
- .value() // 8
-
-maybe('noop')
- .map(plus5)
- .map(minus2)
- .value() // null
-
-maybe(5)
- .if(isNumber)
- .map(plus5)
- .map(minus2)
- .else(getDefault)
- .map(toString)
- .value() // 'The value is 8'
-
-maybe()
- .if(isNumber)
- .map(plus5)
- .map(minus2)
- .map(toString)
- .value() // null
-
-maybe()
- .if(isNumber)
- .map(plus5)
- .map(minus2)
- .else(getDefault)
- .map(toString)
- .value() // 'This is default value'
-```
-
-### Date utils
-
-#### `formatDateString(Date | Timestamp [, String locale [, Object options]])`
-
-```js
-import {
- formatDateString
-} from 'bellajs'
-
-const today = new Date()
-
-formatDateString(today) // => Jan 3, 2022, 8:34:28 PM GMT+7
-
-// custom format
-formatDateString(today, {
- dateStyle: 'short',
- timeStyle: 'short',
- hour12: true
-}) // => 1/3/22, 8:34 PM
-
-// custom locale
-formatDateString(today, 'zh') // => 2022年1月3日 GMT+7 下午8:34:28
-
-// custom lang and format
-formatDateString(today, 'zh', {
- dateStyle: 'short',
- timeStyle: 'long',
- hour12: true
-}) // => 2022/1/3 GMT+7 下午8:34:28
-
-formatDateString(today, 'vi') // => 20:34:28 GMT+7, 3 thg 1, 2022
-formatDateString(today, 'vi', {
- dateStyle: 'full',
- timeStyle: 'full'
-}) // => 20:34:28 Giờ Đông Dương Thứ Hai, 3 tháng 1, 2022
-```
-
-
-#### `formatTimeAgo(Date | Timestamp [, String locale [, String justnow]])`
-
-```js
-import {
- formatTimeAgo
-} from 'bellajs'
-
-const today = new Date()
-
-const yesterday = today.setDate(today.getDate() - 1)
-formatTimeAgo(yesterday) // => 1 day ago
-
-const current = new Date()
-const aLittleWhile = current.setHours(current.getHours() - 3)
-formatTimeAgo(aLittleWhile) // => 3 hours ago
-
-// change locale
-formatTimeAgo(aLittleWhile, 'zh') // => 3小时前
-formatTimeAgo(aLittleWhile, 'vi') // => 3 giờ trước
-```
-
-The last param `justnow` can be used to display a custom 'just now' message, when the distance is lesser than 1s.
-
-```js
-const now = new Date()
-const aJiff = now.setTime(now.getTime() - 100)
-formatTimeAgo(aJiff) // => 'just now'
-formatTimeAgo(aJiff, 'fr', 'à l\'instant') // => à l'instant
-formatTimeAgo(aJiff, 'ja', 'すこし前') // => すこし前
-```
-
-These two functions based on recent features of built-in object `Intl`.
-
-Please refer the following resources for more info:
-
-- [Intl.DateTimeFormat() constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat)
-- [Intl.RelativeTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat)
-- [Intl.Locale](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale)
-
-
-### Random utils
-
-#### `randint([Number min [, Number max]])`
-
-Returns a number between `min` and `max`
-
-```js
-import { randint } from 'bellajs'
-
-randint() // => a random integer
-randint(1, 5) // => a random integer between 3 and 5, including 1 and 5
-```
-
-#### `genid([Number length [, String prefix]])`
-
-Create random ID string.
-
-```js
-import { genid } from 'bellajs'
-
-genid() // => random 32 chars
-genid(16) // => random 16 chars
-genid(5) // => random 5 chars
-genid(5, 'X_') // => X_{random 3 chars}
-```
-
-## Test
-
-```bash
-git clone https://github.com/ndaidong/bellajs.git
-cd bellajs
-npm install
-npm test
-```
-
-# License
-
-The MIT License (MIT)
+# BellaJS
+
+Lightweight util for handling data type, string... in your Node.js and browser
+apps.
+
+![CodeQL](https://github.com/ndaidong/bellajs/workflows/CodeQL/badge.svg)
+[![CI test](https://github.com/ndaidong/bellajs/workflows/ci-test/badge.svg)](https://github.com/ndaidong/bellajs/actions)
+[![Coverage Status](https://coveralls.io/repos/github/ndaidong/bellajs/badge.svg)](https://coveralls.io/github/ndaidong/bellajs)
+[![NPM](https://img.shields.io/npm/v/%40ndaidong%2Fbellajs?color=32bb24)](https://www.npmjs.com/package/@ndaidong/bellajs)
+[![JSR](https://jsr.io/badges/@ndaidong/bellajs?color=32bb24)](https://jsr.io/@ndaidong/bellajs)
+
+## Contents
+
+- [Setup](#setup)
+- [APIs](#apis)
+ - [DataType detection](#datatype-detection)
+ - [String manipulation](#string-manipulation)
+ - [Data handling](#data-handling): [`clone`](#cloneanything-val),
+ [`copies`](#copiesobject-source-object-target-boolean-requirematching-array-excepts)
+ - [Array utils](#array-utils): [`pick`](#pickarray-arr--number-count--1),
+ [`sort`](#sortarray-arr--function-compare),
+ [`sortBy`](#sortbyarray-arr-number-order-string-property),
+ [`shuffle`](#shufflearray-arr), [`unique`](#uniquearray-arr)
+ - [Functional utils](#functional-utils): [`curry`](#curryfn),
+ [`compose`](#composef1-f2-fn), [`pipe`](#pipef1-f2-fn),
+ [`maybe`](#maybeanything-val)
+ - [Date utils](#date-utils):
+ [`formatDateString`](#formatdatestringdate--timestamp--string-locale--object-options),
+ [`formatTimeAgo`](#formattimeagodate--timestamp--string-locale--string-justnow)
+ - [Random utils](#random-utils): [`randint`](#randintnumber-min--number-max),
+ [`genid`](#genidnumber-length--string-prefix)
+
+- [Development](#development)
+
+- [License](#license)
+
+## Setup & Usage
+
+### Deno
+
+https://jsr.io/@ndaidong/bellajs
+
+```ts
+import { genid } from "@ndaidong/bellajs";
+
+for (let i = 0; i < 5; i++) {
+ console.log(genid());
+}
+```
+
+You can use JSR packages without an install step using `jsr:` specifiers:
+
+```ts
+import { genid } from "jsr:@ndaidong/bellajs";
+
+for (let i = 0; i < 5; i++) {
+ console.log(genid());
+}
+```
+
+You can also use `npm:` specifiers as before:
+
+```ts
+import { genid } from "npm:@ndaidong/bellajs";
+
+for (let i = 0; i < 5; i++) {
+ console.log(genid());
+}
+```
+
+Or import from esm.sh
+
+```ts
+import { genid } from "https://esm.sh/@ndaidong/bellajs";
+
+for (let i = 0; i < 5; i++) {
+ console.log(genid());
+}
+```
+
+### Node.js & Bun
+
+https://www.npmjs.com/package/@ndaidong/bellajs
+
+```bash
+npm i @ndaidong/bellajs
+# pnpm
+pnpm i @ndaidong/bellajs
+# yarn
+yarn add @ndaidong/bellajs
+# bun
+bun add @ndaidong/bellajs
+```
+
+```js
+import { genid } from "@ndaidong/bellajs";
+
+for (let i = 0; i < 5; i++) {
+ console.log(genid());
+}
+```
+
+You can also use CJS style:
+
+```js
+const { genid } = require("@ndaidong/bellajs");
+
+for (let i = 0; i < 5; i++) {
+ console.log(genid());
+}
+```
+
+### Browsers:
+
+```html
+
+```
+
+## APIs
+
+### DataType detection
+
+- `.isArray(Anything val)`
+- `.isBoolean(Anything val)`
+- `.isDate(Anything val)`
+- `.isEmail(Anything val)`
+- `.isEmpty(Anything val)`
+- `.isFunction(Anything val)`
+- `.isInteger(Anything val)`
+- `.isLetter(Anything val)`
+- `.isNil(Anything val)`
+- `.isNull(Anything val)`
+- `.isNumber(Anything val)`
+- `.isObject(Anything val)`
+- `.isString(Anything val)`
+- `.isUndefined(Anything val)`
+
+### String manipulation
+
+- `.ucfirst(String s)`
+- `.ucwords(String s)`
+- `.escapeHTML(String s)`
+- `.unescapeHTML(String s)`
+- `.slugify(String s)`
+- `.stripTags(String s)`
+- `.stripAccent(String s)`
+- `.truncate(String s, Number limit)`
+- `.replaceAll(String s, String|Array search, String|Array replace)`
+
+### Data handling
+
+#### `clone(Anything val)`
+
+Make a deep copy of a variable.
+
+```js
+import { clone } from "@ndaidong/bellajs";
+
+const b = [
+ 1,
+ 5,
+ 0,
+ "a",
+ -10,
+ "-10",
+ "",
+ {
+ a: 1,
+ b: "Awesome",
+ },
+];
+
+const cb = clone(b);
+console.log(cb);
+```
+
+_cb_ now has the same values as _b_, while the properties are standalone, not
+reference. So that:
+
+```js
+cb[7].a = 2;
+cb[7].b = "Noop";
+
+console.log(b[7]);
+```
+
+What you get is still:
+
+```js
+{
+ a: 1,
+ b: 'Awesome'
+}
+```
+
+#### `copies(Object source, Object target[[, Boolean requireMatching], Array excepts])`
+
+Copy the properties from _source_ to _target_.
+
+- _requireMatching_: if true, BellaJS only copies the properties that are
+ already exist in _target_.
+- _excepts_: array of the properties properties in _source_ that you don't want
+ to copy.
+
+After this action, target will be modified.
+
+```js
+import { copies } from "@ndaidong/bellajs";
+
+const a = {
+ name: "Toto",
+ age: 30,
+ level: 8,
+ nationality: {
+ name: "America",
+ },
+};
+const b = {
+ level: 4,
+ IQ: 140,
+ epouse: {
+ name: "Alice",
+ age: 27,
+ },
+ nationality: {
+ long: "18123.123123.12312",
+ lat: "98984771.134231.1234",
+ },
+};
+
+copies(a, b);
+console.log(b);
+```
+
+Output:
+
+```js
+{
+ level: 8,
+ IQ: 140,
+ epouse: {
+ name: 'Alice',
+ age: 27
+ },
+ nationality: {
+ long: '18123.123123.12312',
+ lat: '98984771.134231.1234',
+ name: 'America'
+ },
+ name: 'Toto',
+ age: 30
+}
+```
+
+### Array utils
+
+#### `pick(Array arr [, Number count = 1])`
+
+Randomly choose N elements from array.
+
+```js
+import { pick } from "@ndaidong/bellajs";
+
+const arr = [1, 3, 8, 2, 5, 7];
+pick(arr, 2); // --> [3, 5]
+pick(arr, 2); // --> [8, 1]
+pick(arr); // --> [3]
+pick(arr); // --> [7]
+```
+
+#### `sort(Array arr [, Function compare])`
+
+Sort the array using a function.
+
+```js
+import { sort } from "@ndaidong/bellajs";
+
+const fn = (a, b) => {
+ return a < b ? 1 : a > b ? -1 : 0;
+};
+
+sort([3, 1, 5, 2], fn); // => [ 1, 2, 3, 5 ]
+```
+
+#### `sortBy(Array arr, Number order, String property)`
+
+Sort the array by specific property and direction.
+
+```js
+import { sortBy } from "@ndaidong/bellajs";
+
+const players = [
+ {
+ name: "Jerome Nash",
+ age: 24,
+ },
+ {
+ name: "Jackson Valdez",
+ age: 21,
+ },
+ {
+ name: "Benjamin Cole",
+ age: 23,
+ },
+ {
+ name: "Manuel Delgado",
+ age: 33,
+ },
+ {
+ name: "Caleb McKinney",
+ age: 28,
+ },
+];
+
+const result = sortBy(players, -1, "age");
+console.log(result);
+```
+
+#### `shuffle(Array arr)`
+
+Shuffle the positions of elements in an array.
+
+```js
+import { shuffle } from "@ndaidong/bellajs";
+
+shuffle([1, 3, 8, 2, 5, 7]);
+```
+
+#### `unique(Array arr)`
+
+Remove all duplicate elements from an array.
+
+```js
+import { unique } from "@ndaidong/bellajs";
+
+unique([1, 2, 3, 2, 3, 1, 5]); // => [ 1, 2, 3, 5 ]
+```
+
+### Functional utils
+
+#### `curry(fn)`
+
+Make a curried function.
+
+```js
+import { curry } from "@ndaidong/bellajs";
+
+const sum = curry((a, b, c) => {
+ return a + b + c;
+});
+
+sum(3)(2)(1); // => 6
+sum(1)(2)(3); // => 6
+sum(1, 2)(3); // => 6
+sum(1)(2, 3); // => 6
+sum(1, 2, 3); // => 6
+```
+
+#### `compose(f1, f2, ...fN)`
+
+Performs right-to-left function composition.
+
+```js
+import { compose } from "@ndaidong/bellajs";
+
+const f1 = (name) => {
+ return `f1 ${name}`;
+};
+const f2 = (name) => {
+ return `f2 ${name}`;
+};
+const f3 = (name) => {
+ return `f3 ${name}`;
+};
+
+const addF = compose(f1, f2, f3);
+
+addF("Hello"); // => 'f1 f2 f3 Hello'
+
+const add1 = (num) => {
+ return num + 1;
+};
+
+const mult2 = (num) => {
+ return num * 2;
+};
+
+const add1AndMult2 = compose(add1, mult2);
+add1AndMult2(3); // => 7
+// because multiple to 2 first, then add 1 late => 3 * 2 + 1
+```
+
+#### `pipe(f1, f2, ...fN)`
+
+Performs left-to-right function composition.
+
+```js
+import { pipe } from "@ndaidong/bellajs";
+
+const f1 = (name) => {
+ return `f1 ${name}`;
+};
+const f2 = (name) => {
+ return `f2 ${name}`;
+};
+const f3 = (name) => {
+ return `f3 ${name}`;
+};
+
+const addF = pipe(f1, f2, f3);
+
+addF("Hello"); // => 'f3 f2 f1 Hello'
+
+const add1 = (num) => {
+ return num + 1;
+};
+
+const mult2 = (num) => {
+ return num * 2;
+};
+
+const add1AndMult2 = pipe(add1, mult2);
+add1AndMult2(3); // => 8
+// because add 1 first, then multiple to 2 late => (3 + 1) * 2
+```
+
+#### `maybe(Anything val)`
+
+Return a static variant of `Maybe` monad.
+
+```js
+import { maybe } from "@ndaidong/bellajs";
+
+const plus5 = (x) => x + 5;
+const minus2 = (x) => x - 2;
+const isNumber = (x) => Number(x) === x;
+const toString = (x) => "The value is " + String(x);
+const getDefault = () => "This is default value";
+
+maybe(5)
+ .map(plus5)
+ .map(minus2)
+ .value(); // 8
+
+maybe("noop")
+ .map(plus5)
+ .map(minus2)
+ .value(); // null
+
+maybe(5)
+ .if(isNumber)
+ .map(plus5)
+ .map(minus2)
+ .else(getDefault)
+ .map(toString)
+ .value(); // 'The value is 8'
+
+maybe()
+ .if(isNumber)
+ .map(plus5)
+ .map(minus2)
+ .map(toString)
+ .value(); // null
+
+maybe()
+ .if(isNumber)
+ .map(plus5)
+ .map(minus2)
+ .else(getDefault)
+ .map(toString)
+ .value(); // 'This is default value'
+```
+
+### Date utils
+
+#### `formatDateString(Date | Timestamp [, String locale [, Object options]])`
+
+```js
+import { formatDateString } from "@ndaidong/bellajs";
+
+const today = new Date();
+
+formatDateString(today); // => Jan 3, 2022, 8:34:28 PM GMT+7
+
+// custom format
+formatDateString(today, {
+ dateStyle: "short",
+ timeStyle: "short",
+ hour12: true,
+}); // => 1/3/22, 8:34 PM
+
+// custom locale
+formatDateString(today, "zh"); // => 2022年1月3日 GMT+7 下午8:34:28
+
+// custom lang and format
+formatDateString(today, "zh", {
+ dateStyle: "short",
+ timeStyle: "long",
+ hour12: true,
+}); // => 2022/1/3 GMT+7 下午8:34:28
+
+formatDateString(today, "vi"); // => 20:34:28 GMT+7, 3 thg 1, 2022
+formatDateString(today, "vi", {
+ dateStyle: "full",
+ timeStyle: "full",
+}); // => 20:34:28 Giờ Đông Dương Thứ Hai, 3 tháng 1, 2022
+```
+
+#### `formatTimeAgo(Date | Timestamp [, String locale [, String justnow]])`
+
+```js
+import { formatTimeAgo } from "@ndaidong/bellajs";
+
+const today = new Date();
+
+const yesterday = today.setDate(today.getDate() - 1);
+formatTimeAgo(yesterday); // => 1 day ago
+
+const current = new Date();
+const aLittleWhile = current.setHours(current.getHours() - 3);
+formatTimeAgo(aLittleWhile); // => 3 hours ago
+
+// change locale
+formatTimeAgo(aLittleWhile, "zh"); // => 3小时前
+formatTimeAgo(aLittleWhile, "vi"); // => 3 giờ trước
+```
+
+The last param `justnow` can be used to display a custom 'just now' message,
+when the distance is lesser than 1s.
+
+```js
+const now = new Date();
+const aJiff = now.setTime(now.getTime() - 100);
+formatTimeAgo(aJiff); // => 'just now'
+formatTimeAgo(aJiff, "fr", "à l'instant"); // => à l'instant
+formatTimeAgo(aJiff, "ja", "すこし前"); // => すこし前
+```
+
+These two functions based on recent features of built-in object `Intl`.
+
+Please refer the following resources for more info:
+
+- [Intl.DateTimeFormat() constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat)
+- [Intl.RelativeTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat)
+- [Intl.Locale](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale)
+
+### Random utils
+
+#### `randint([Number min [, Number max]])`
+
+Returns a number between `min` and `max`
+
+```js
+import { randint } from "@ndaidong/bellajs";
+
+randint(); // => a random integer
+randint(1, 5); // => a random integer between 3 and 5, including 1 and 5
+```
+
+#### `genid([Number length [, String prefix]])`
+
+Create random ID string.
+
+```js
+import { genid } from "@ndaidong/bellajs";
+
+genid(); // => random 32 chars
+genid(16); // => random 16 chars
+genid(5); // => random 5 chars
+genid(5, "X_"); // => X_{random 3 chars}
+```
+
+## Development
+
+Since v12.x.x, we switched to [Deno](https://docs.deno.com/runtime/manual/)
+platform, and use [DNT](https://github.com/denoland/dnt) to build Node.js
+packages.
+
+```bash
+git clone https://github.com/ndaidong/bellajs.git
+cd bellajs
+
+# test
+deno test
+
+# build npm packages
+deno task build
+
+cd npm
+node test_runner.js
+```
+
+## License
+
+The MIT License (MIT)
+
+---
diff --git a/deno.json b/deno.json
new file mode 100644
index 0000000..ef529f3
--- /dev/null
+++ b/deno.json
@@ -0,0 +1,33 @@
+{
+ "name": "@ndaidong/bellajs",
+ "version": "12.0.0-rc1",
+ "description": "A useful helper for any javascript program",
+ "homepage": "https://github.com/ndaidong/bellajs",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/ndaidong/bellajs.git"
+ },
+ "author": "@ndaidong",
+ "license": "MIT",
+ "tasks": {
+ "build": "deno run -A ./scripts/build_npm.ts"
+ },
+ "imports": {
+ "assert": "https://deno.land/std@0.224.0/assert/mod.ts",
+ "@deno/dnt": "jsr:@deno/dnt@^0.41.2"
+ },
+ "exports": "./mod.ts",
+ "test": {
+ "include": ["tests"],
+ "exclude": []
+ },
+ "publish": {
+ "include": [
+ "LICENSE",
+ "README.md",
+ "mod.ts",
+ "utils/*.ts",
+ "tests/*.ts"
+ ]
+ }
+}
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index 1bef40a..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,133 +0,0 @@
-// eslint.config.js
-
-import eslintjs from '@eslint/js'
-import globals from 'globals'
-
-const ignores = [
- 'node_modules/*',
- 'dist/*',
-]
-
-export default [
- {
- ignores,
- ...eslintjs.configs.recommended,
- },
- {
- languageOptions: {
- ecmaVersion: 'latest',
- sourceType: 'module',
- globals: {
- ...globals.node,
- ...globals.browser,
- ...globals.jest,
- Intl: 'readonly',
- },
- },
- ignores,
- rules: {
- 'arrow-spacing': ['error', { 'before': true, 'after': true }],
- 'block-spacing': ['error', 'always'],
- 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
- 'camelcase': ['error', {
- 'allow': ['^UNSAFE_'],
- 'properties': 'never',
- 'ignoreGlobals': true,
- }],
- 'comma-dangle': ['error', {
- 'arrays': 'always-multiline',
- 'objects': 'always-multiline',
- 'imports': 'never',
- 'exports': 'never',
- 'functions': 'never',
- }],
- 'comma-spacing': ['error', { 'before': false, 'after': true }],
- 'eol-last': 'error',
- 'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
- 'func-call-spacing': ['error', 'never'],
- 'indent': [
- 'error',
- 2,
- {
- 'MemberExpression': 1,
- 'FunctionDeclaration': {
- 'body': 1,
- 'parameters': 2,
- },
- 'SwitchCase': 1,
- 'ignoredNodes': ['TemplateLiteral > *'],
- },
- ],
- 'key-spacing': ['error', { 'beforeColon': false, 'afterColon': true }],
- 'keyword-spacing': ['error', { 'before': true, 'after': true }],
- 'lines-between-class-members': ['error', 'always', { 'exceptAfterSingleLine': true }],
- 'max-len': [
- 'error',
- {
- 'code': 120,
- 'ignoreTrailingComments': true,
- 'ignoreComments': true,
- 'ignoreUrls': true,
- },
- ],
- 'max-lines': [
- 'error',
- {
- 'max': 450,
- 'skipBlankLines': true,
- 'skipComments': false,
- },
- ],
- 'max-lines-per-function': [
- 'error',
- {
- 'max': 150,
- 'skipBlankLines': true,
- },
- ],
- 'max-params': ['error', 4],
- 'no-array-constructor': 'error',
- 'no-mixed-spaces-and-tabs': 'error',
- 'no-multi-spaces': 'error',
- 'no-multi-str': 'error',
- 'no-multiple-empty-lines': [
- 'error',
- {
- 'max': 1,
- 'maxEOF': 0,
- },
- ],
- 'no-restricted-syntax': [
- 'error',
- 'WithStatement',
- 'BinaryExpression[operator=\'in\']',
- ],
- 'no-trailing-spaces': 'error',
- 'no-use-before-define': [
- 'error',
- {
- 'functions': true,
- 'classes': true,
- 'variables': false,
- },
- ],
- 'no-var': 'warn',
- 'object-curly-spacing': ['error', 'always'],
- 'padded-blocks': [
- 'error',
- {
- 'blocks': 'never',
- 'switches': 'never',
- 'classes': 'never',
- },
- ],
- 'quotes': ['error', 'single'],
- 'space-before-blocks': ['error', 'always'],
- 'space-before-function-paren': ['error', 'always'],
- 'space-infix-ops': 'error',
- 'space-unary-ops': ['error', { 'words': true, 'nonwords': false }],
- 'space-in-parens': ['error', 'never'],
- 'semi': ['error', 'never'],
- },
- },
-]
diff --git a/mod.ts b/mod.ts
new file mode 100644
index 0000000..0a10b0b
--- /dev/null
+++ b/mod.ts
@@ -0,0 +1,97 @@
+// mod.ts
+
+import {
+ hasProperty,
+ isArray,
+ isDate,
+ isObject,
+ isString,
+} from "./utils/detection.ts";
+
+export const clone = (val: any): any => {
+ return structuredClone(val);
+};
+
+export const copies = (
+ source: object,
+ dest: object,
+ matched: boolean = false,
+ excepts: string[] = [],
+): object => {
+ const xdest = clone(dest)
+ for (const k in source) {
+ if (excepts.length > 0 && excepts.includes(k)) {
+ continue;
+ }
+ if (!matched || (matched && hasProperty(dest, k))) {
+ const oa: any = source[k as keyof typeof source];
+ const ob: any = dest[k as keyof typeof dest];
+ if ((isObject(ob) && isObject(oa)) || (isArray(ob) && isArray(oa))) {
+ xdest[k] = copies(oa, dest[k as keyof typeof dest], matched, excepts);
+ } else {
+ xdest[k] = clone(oa);
+ }
+ }
+ }
+ return xdest;
+};
+
+export const unique = (arr: any[] = []): any[] => {
+ return [...new Set(arr)];
+};
+
+const fnSort = (a: any, b: any): number => {
+ return a > b ? 1 : (a < b ? -1 : 0);
+};
+
+export const sort = (
+ arr: any[] = [],
+ sorting: ((a: any, b: any) => number) | null = null,
+): any[] => {
+ const tmp: any[] = [...arr];
+ const fn: (a: any, b: any) => number = sorting || fnSort;
+ tmp.sort(fn);
+ return tmp;
+};
+
+export const sortBy = (
+ arr: any[] = [],
+ order: number = 1,
+ key: string = "",
+): any[] => {
+ if (!isString(key) || !hasProperty(arr[0], key)) {
+ return arr;
+ }
+ return sort(arr, (m, n) => {
+ return m[key] > n[key] ? order : (m[key] < n[key] ? (-1 * order) : 0);
+ });
+};
+
+export const shuffle = (arr: any[] = []): any[] => {
+ const input: any[] = [...arr];
+ const output: any[] = [];
+ let inputLen: number = input.length;
+ while (inputLen > 0) {
+ const index: number = Math.floor(Math.random() * inputLen);
+ output.push(input.splice(index, 1)[0]);
+ inputLen--;
+ }
+ return output;
+};
+
+export const pick = (arr: any[] = [], count: number = 1): any[] => {
+ const a: any[] = shuffle(arr);
+ const mc: number = Math.max(1, count);
+ const c: number = Math.min(mc, a.length - 1);
+ return a.splice(0, c);
+};
+
+export * from "./utils/detection.ts";
+export * from "./utils/string.ts";
+export * from "./utils/random.ts";
+export * from "./utils/date.ts";
+
+export * from "./utils/curry.ts";
+export * from "./utils/compose.ts";
+export * from "./utils/pipe.ts";
+export * from "./utils/maybe.ts";
diff --git a/package.json b/package.json
deleted file mode 100755
index 5f23078..0000000
--- a/package.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "version": "11.2.0",
- "name": "bellajs",
- "description": "A useful helper for any javascript program",
- "homepage": "https://www.npmjs.com/package/bellajs",
- "repository": {
- "type": "git",
- "url": "https://github.com/ndaidong/bellajs"
- },
- "author": "@ndaidong",
- "main": "./src/main.js",
- "type": "module",
- "engines": {
- "node": ">= 18.4"
- },
- "scripts": {
- "lint": "eslint .",
- "lint:fix": "eslint --fix .",
- "pretest": "npm run lint",
- "test": "NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --verbose --coverage=true --env=jsdom",
- "reset": "node reset"
- },
- "devDependencies": {
- "eslint": "^9.2.0",
- "globals": "^15.2.0",
- "jest": "^29.7.0",
- "jest-environment-jsdom": "^29.7.0"
- },
- "keywords": [
- "detection",
- "manipulation",
- "templating",
- "utilities"
- ],
- "license": "MIT"
-}
diff --git a/reset.js b/reset.js
deleted file mode 100755
index 8e31507..0000000
--- a/reset.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/**
- * reset.js
- * @ndaidong
-**/
-
-import {
- existsSync,
- unlinkSync
-} from 'fs'
-
-import { execSync } from 'child_process'
-
-const dirs = [
- 'dist',
- 'docs',
- '.nyc_output',
- 'coverage',
- 'node_modules',
- '.nuxt',
-]
-
-const files = [
- 'yarn.lock',
- 'pnpm-lock.yaml',
- 'package-lock.json',
- 'coverage.lcov',
-]
-
-dirs.forEach((d) => {
- execSync(`rm -rf ${d}`)
-})
-
-files.forEach((f) => {
- if (existsSync(f)) {
- unlinkSync(f)
- }
-})
diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts
new file mode 100644
index 0000000..2e969c6
--- /dev/null
+++ b/scripts/build_npm.ts
@@ -0,0 +1,32 @@
+// scripts/build_npm.ts
+import { build, emptyDir } from "@deno/dnt";
+
+import pkg from "../deno.json" with { type: "json" };
+
+const outputDir = "./npm";
+
+await emptyDir(outputDir);
+
+await build({
+ importMap: "deno.json",
+ entryPoints: ["./mod.ts"],
+ outDir: outputDir,
+ shims: {
+ deno: true,
+ },
+ package: {
+ name: pkg.name,
+ version: pkg.version,
+ description: pkg.description,
+ author: pkg.author,
+ repository: pkg.repository,
+ bugs: {
+ url: `${pkg.homepage}/issues`,
+ },
+ license: pkg.license,
+ },
+ postBuild() {
+ Deno.copyFileSync("LICENSE", "npm/LICENSE");
+ Deno.copyFileSync("README.md", "npm/README.md");
+ },
+});
diff --git a/src/main.js b/src/main.js
deleted file mode 100755
index f400bf3..0000000
--- a/src/main.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/**
- * bellajs
- * @ndaidong
-**/
-
-import {
- isObject,
- isArray,
- isDate,
- isString,
- hasProperty
-} from './utils/detection.js'
-
-export const clone = (val, history = null) => {
- const stack = history || new Set()
-
- if (stack.has(val)) {
- return val
- }
-
- stack.add(val)
-
- if (isDate(val)) {
- return new Date(val.valueOf())
- }
-
- const copyObject = (o) => {
- const oo = Object.create({})
- for (const k in o) {
- if (hasProperty(o, k)) {
- oo[k] = clone(o[k], stack)
- }
- }
- return oo
- }
-
- const copyArray = (a) => {
- return [...a].map((e) => {
- if (isArray(e)) {
- return copyArray(e)
- } else if (isObject(e)) {
- return copyObject(e)
- }
- return clone(e, stack)
- })
- }
-
- if (isArray(val)) {
- return copyArray(val)
- }
-
- if (isObject(val)) {
- return copyObject(val)
- }
-
- return val
-}
-
-export const copies = (source, dest, matched = false, excepts = []) => {
- for (const k in source) {
- if (excepts.length > 0 && excepts.includes(k)) {
- continue
- }
- if (!matched || (matched && hasProperty(dest, k))) {
- const oa = source[k]
- const ob = dest[k]
- if ((isObject(ob) && isObject(oa)) || (isArray(ob) && isArray(oa))) {
- dest[k] = copies(oa, dest[k], matched, excepts)
- } else {
- dest[k] = clone(oa)
- }
- }
- }
- return dest
-}
-
-export const unique = (arr = []) => {
- return [...new Set(arr)]
-}
-
-const fnSort = (a, b) => {
- return a > b ? 1 : (a < b ? -1 : 0)
-}
-
-export const sort = (arr = [], sorting = null) => {
- const tmp = [...arr]
- const fn = sorting || fnSort
- tmp.sort(fn)
- return tmp
-}
-
-export const sortBy = (arr = [], order = 1, key = '') => {
- if (!isString(key) || !hasProperty(arr[0], key)) {
- return arr
- }
- return sort(arr, (m, n) => {
- return m[key] > n[key] ? order : (m[key] < n[key] ? (-1 * order) : 0)
- })
-}
-
-export const shuffle = (arr = []) => {
- const input = [...arr]
- const output = []
- let inputLen = input.length
- while (inputLen > 0) {
- const index = Math.floor(Math.random() * inputLen)
- output.push(input.splice(index, 1)[0])
- inputLen--
- }
- return output
-}
-
-export const pick = (arr = [], count = 1) => {
- const a = shuffle(arr)
- const mc = Math.max(1, count)
- const c = Math.min(mc, a.length - 1)
- return a.splice(0, c)
-}
-
-export * from './utils/detection.js'
-export * from './utils/string.js'
-export * from './utils/random.js'
-export * from './utils/date.js'
-
-export * from './utils/curry.js'
-export * from './utils/compose.js'
-export * from './utils/pipe.js'
-export * from './utils/maybe.js'
diff --git a/src/main.test.js b/src/main.test.js
deleted file mode 100755
index 6de4927..0000000
--- a/src/main.test.js
+++ /dev/null
@@ -1,192 +0,0 @@
-// main.test
-
-/* eslint-env jest */
-
-import {
- hasProperty,
- clone,
- copies,
- unique,
- sort,
- sortBy,
- pick
-} from './main.js'
-
-describe('test .clone() method:', () => {
- test(' check if .clone(object) works correctly', () => {
- const x = {
- level: 4,
- IQ: 140,
- epouse: {
- name: 'Alice',
- age: 27,
- },
- birthday: new Date(),
- a: 0,
- clone: false,
- reg: /^\w+@\s([a-z])$/gi,
- }
- const y = clone(x)
- Object.keys(x).forEach((k) => {
- expect(hasProperty(y, k)).toBeTruthy()
- })
- Object.keys(x.epouse).forEach((k) => {
- expect(hasProperty(y.epouse, k)).toBeTruthy()
- expect(y.epouse[k]).toEqual(x.epouse[k])
- })
-
- // check immutability
- y.epouse.age = 25
- expect(y.epouse.age).toEqual(25)
- expect(x.epouse.age).toEqual(27)
- })
-
- test(' check if .clone(array) works correctly', () => {
- const x = [
- 1,
- 5,
- 0,
- 'a',
- -10,
- '-10',
- '',
- {
- a: 1,
- b: 'Awesome',
- },
- [
- 5,
- 6,
- 8,
- {
- name: 'Lys',
- age: 11,
- },
- ],
- ]
- const y = clone(x)
- expect(y).toHaveLength(x.length)
- for (let i = 0; i < x.length; i++) {
- expect(x[i]).toEqual(y[i])
- }
-
- // check immutability
- y[8][3].age = 10
- expect(y[8][3].age).toEqual(10)
- expect(x[8][3].age).toEqual(11)
- })
-})
-
-describe('test .copies() method:', () => {
- test(' check if .copies(source, dest) works correctly', () => {
- const source = {
- name: 'Toto',
- age: 30,
- level: 8,
- nationality: {
- name: 'America',
- },
- groups: [
- 'admin',
- 'accountant',
- ],
- }
- const dest = {
- level: 4,
- IQ: 140,
- epouse: {
- name: 'Alice',
- age: 27,
- },
- nationality: {
- name: 'Congo',
- long: '18123.123123.12312',
- lat: '98984771.134231.1234',
- },
- groups: [
- 'finance',
- 'manager',
- ],
- }
- copies(source, dest)
- Object.keys(source).forEach((k) => {
- expect(hasProperty(dest, k)).toBeTruthy()
- })
- expect(dest.nationality.name).toEqual(source.nationality.name)
- })
-
- test(' check if .copies(source, dest, matched, excepts) works correctly', () => {
- const source = {
- name: 'Kiwi',
- age: 16,
- gender: 'male',
- }
- const dest = {
- name: 'Aline',
- age: 20,
- }
- copies(source, dest, true, ['age'])
- expect(hasProperty(dest, 'gender')).toBeFalsy()
- expect(dest.name).toEqual(source.name)
- expect(dest.age === source.age).toBeFalsy()
- })
-})
-
-describe('test .unique() method:', () => {
- test(' check if .unique(array) works correctly', () => {
- const arr = [1, 1, 2, 2, 3, 4, 5, 5, 6, 3, 5, 4]
- const uniqArr = unique(arr)
- expect(uniqArr).toHaveLength(6)
- })
-})
-
-describe('test .sort() method:', () => {
- test(' check if .sort(array) works correctly', () => {
- const arr = [6, 4, 8, 2]
- const sortedArr = sort(arr)
- expect(sortedArr.join('')).toEqual('2468')
- })
-})
-
-describe('test .sortBy() method:', () => {
- test(' check if .sortBy(array) works correctly', () => {
- const arr = [
- { age: 5, name: 'E' },
- { age: 9, name: 'B' },
- { age: 3, name: 'A' },
- { age: 12, name: 'D' },
- { age: 7, name: 'C' },
- ]
- const sortedByAge = [
- { age: 3, name: 'A' },
- { age: 5, name: 'E' },
- { age: 7, name: 'C' },
- { age: 9, name: 'B' },
- { age: 12, name: 'D' },
- ]
- const sortedArr = sortBy(arr, 1, 'age')
- expect(JSON.stringify(sortedArr) === JSON.stringify(sortedByAge)).toBeTruthy()
-
- const sortedByNonStringKey = sortBy(arr, 1, 99)
- expect(JSON.stringify(sortedByNonStringKey) === JSON.stringify(arr)).toBeTruthy()
-
- const sortedByNonExistKey = sortBy(arr, 1, 'balance')
- expect(JSON.stringify(sortedByNonExistKey) === JSON.stringify(arr)).toBeTruthy()
- })
-})
-
-describe('test .pick() method:', () => {
- test(' check if .pick(array) works correctly', () => {
- const str = 'abcdefghijklmnopqrstuvwxyz'
- const arr = str.split('')
- const uniqChar = pick(arr)[0]
- expect(str.includes(uniqChar)).toBeTruthy()
- })
-
- test(' check if .pick(array, count) works correctly', () => {
- const str = 'abcdefghijklmnopqrstuvwxyz'
- const arr = str.split('')
- const picked = pick(arr, 10)
- expect(picked).toHaveLength(10)
- })
-})
diff --git a/src/utils/compose.js b/src/utils/compose.js
deleted file mode 100755
index c061193..0000000
--- a/src/utils/compose.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// utils / compose
-
-export const compose = (...fns) => {
- return fns.reduce((f, g) => (x) => f(g(x)))
-}
diff --git a/src/utils/compose.test.js b/src/utils/compose.test.js
deleted file mode 100755
index 642e1a2..0000000
--- a/src/utils/compose.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// compose.test
-
-/* eslint-env jest */
-
-import {
- compose
-} from './compose.js'
-
-describe('test .compose() method:', () => {
- const f1 = (name) => {
- return `f1 ${name}`
- }
- const f2 = (name) => {
- return `f2 ${name}`
- }
- const f3 = (name) => {
- return `f3 ${name}`
- }
-
- const addDashes = compose(f1, f2, f3)
-
- const add3 = (num) => {
- return num + 3
- }
-
- const mul6 = (num) => {
- return num * 6
- }
-
- const div2 = (num) => {
- return num / 2
- }
-
- const sub5 = (num) => {
- return num - 5
- }
-
- const calculate = compose(sub5, div2, mul6, add3)
-
- test(' check if .compose() works correctly', () => {
- expect(addDashes('Alice')).toEqual('f1 f2 f3 Alice')
- expect(calculate(5)).toEqual(19)
- })
-})
diff --git a/src/utils/curry.js b/src/utils/curry.js
deleted file mode 100755
index b4f42db..0000000
--- a/src/utils/curry.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// utils / curry
-
-export const curry = (fn) => {
- const totalArguments = fn.length
- const next = (argumentLength, rest) => {
- if (argumentLength > 0) {
- return (...args) => {
- return next(argumentLength - args.length, [...rest, ...args])
- }
- }
- return fn(...rest)
- }
- return next(totalArguments, [])
-}
diff --git a/src/utils/curry.test.js b/src/utils/curry.test.js
deleted file mode 100755
index d6612fc..0000000
--- a/src/utils/curry.test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-// curry.test
-
-/* eslint-env jest */
-
-import {
- curry
-} from './curry.js'
-
-describe('test .curry() method:', () => {
- const sum = curry((a, b, c) => {
- return a + b + c
- })
- test(' check if .curry() works correctly', () => {
- expect(sum(3)(2)(1)).toEqual(6)
- expect(sum(1)(2)(3)).toEqual(6)
- expect(sum(1, 2)(3)).toEqual(6)
- expect(sum(1)(2, 3)).toEqual(6)
- expect(sum(1, 2, 3)).toEqual(6)
- })
-})
diff --git a/src/utils/date.test.js b/src/utils/date.test.js
deleted file mode 100755
index 7881189..0000000
--- a/src/utils/date.test.js
+++ /dev/null
@@ -1,62 +0,0 @@
-// date.test
-
-/* eslint-env jest */
-
-import { jest } from '@jest/globals'
-
-import {
- formatDateString,
- formatTimeAgo
-} from './date.js'
-
-describe('test .formatDateString() method', () => {
- const d = new Date()
-
- test(' check .formatDateString() with default options', () => {
- const result = formatDateString(d)
- const reg = /^\w+\s\d+,\s+\d{4},\s\d+:\d+:\d+\s(AM|PM)\s(GMT)\+\d+$/
- expect(result.match(reg) !== null).toBeTruthy()
- })
-
- test(' check .formatDateString() with custom options', () => {
- const result = formatDateString(d, {
- dateStyle: 'full',
- timeStyle: 'medium',
- hour12: true,
- })
- const reg = /^\w+,\s\w+\s\d+,\s+\d{4}\sat\s\d+:\d+:\d+\s(AM|PM)$/
- expect(result.match(reg) !== null).toBeTruthy()
- })
-
- test(' check .formatDateString() with custom language and options', () => {
- const result = formatDateString(d, 'en', {
- dateStyle: 'full',
- timeStyle: 'medium',
- hour12: true,
- })
- const reg = /^\w+,\s\w+\s\d+,\s+\d{4}\sat\s\d+:\d+:\d+\s(AM|PM)$/
- expect(result.match(reg) !== null).toBeTruthy()
- })
-})
-
-describe('test .formatTimeAgo() method:', () => {
- jest.useFakeTimers()
- jest.spyOn(global, 'setTimeout')
-
- const d = new Date()
-
- test(' check if .formatTimeAgo() return "just now"', () => {
- const result = formatTimeAgo(d)
- expect(result === 'just now').toBeTruthy()
- const justnowCustomMessage = formatTimeAgo(d, 'vi', 'vừa mới xong')
- expect(justnowCustomMessage === 'vừa mới xong').toBeTruthy()
- })
-
- test(' check .formatTimeAgo() after 5s', () => {
- setTimeout(() => {
- const result = formatTimeAgo(d)
- expect(result === '5 seconds ago').toBeTruthy()
- }, 5000)
- jest.advanceTimersByTime(5000)
- })
-})
diff --git a/src/utils/detection.js b/src/utils/detection.js
deleted file mode 100755
index 80c019d..0000000
--- a/src/utils/detection.js
+++ /dev/null
@@ -1,77 +0,0 @@
-// utils / detection
-
-const ob2Str = (val) => {
- return {}.toString.call(val)
-}
-
-export const isInteger = (val) => {
- return Number.isInteger(val)
-}
-
-export const isArray = (val) => {
- return Array.isArray(val)
-}
-
-export const isString = (val) => {
- return String(val) === val
-}
-
-export const isNumber = (val) => {
- return Number(val) === val
-}
-
-export const isBoolean = (val) => {
- return Boolean(val) === val
-}
-
-export const isNull = (val) => {
- return ob2Str(val) === '[object Null]'
-}
-
-export const isUndefined = (val) => {
- return ob2Str(val) === '[object Undefined]'
-}
-
-export const isNil = (val) => {
- return isUndefined(val) || isNull(val)
-}
-
-export const isFunction = (val) => {
- return ob2Str(val) === '[object Function]'
-}
-
-export const isObject = (val) => {
- return ob2Str(val) === '[object Object]' && !isArray(val)
-}
-
-export const isDate = (val) => {
- return val instanceof Date && !isNaN(val.valueOf())
-}
-
-export const isElement = (v) => {
- return ob2Str(v).match(/^\[object HTML\w*Element]$/) !== null
-}
-
-export const isLetter = (val) => {
- const re = /^[a-z]+$/i
- return isString(val) && re.test(val)
-}
-
-export const isEmail = (val) => {
- const re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i
- return isString(val) && re.test(val)
-}
-
-export const isEmpty = (val) => {
- return !val || isNil(val) ||
- (isString(val) && val === '') ||
- (isArray(val) && val.length === 0) ||
- (isObject(val) && Object.keys(val).length === 0)
-}
-
-export const hasProperty = (ob, k) => {
- if (!ob || !k) {
- return false
- }
- return Object.prototype.hasOwnProperty.call(ob, k)
-}
diff --git a/src/utils/detection.test.js b/src/utils/detection.test.js
deleted file mode 100755
index e7e030a..0000000
--- a/src/utils/detection.test.js
+++ /dev/null
@@ -1,290 +0,0 @@
-// detection.test
-
-/* eslint-env jest */
-
-import {
- isInteger,
- isArray,
- isString,
- isNumber,
- isBoolean,
- isNull,
- isUndefined,
- isNil,
- isFunction,
- isObject,
- isDate,
- isElement,
- isLetter,
- isEmail,
- isEmpty,
- hasProperty
-} from './detection.js'
-
-describe('test .isInteger() method:', () => {
- const positives = [1, 1000, 9999, 0, -3]
- positives.forEach((val) => {
- test(`test .isInteger(${val}) --> true`, () => {
- expect(isInteger(val)).toBe(true)
- })
- })
-
- const negatives = [1.5, -3.2, '', undefined]
- negatives.forEach((val) => {
- test(`test .isInteger(${val}) --> false`, () => {
- expect(isInteger(val)).toBe(false)
- })
- })
-})
-
-describe('test .isArray() method:', () => {
- const positives = [[], [1, 2, 3]]
- positives.forEach((val) => {
- test(`test .isArray(${val}) --> true`, () => {
- expect(isArray(val)).toBe(true)
- })
- })
-
- const negatives = [1.5, '', undefined]
- negatives.forEach((val) => {
- test(`test .isArray(${val}) --> false`, () => {
- expect(isArray(val)).toBe(false)
- })
- })
-})
-
-describe('test .isString() method:', () => {
- const positives = ['', 'abc xyz', '10000']
- positives.forEach((val) => {
- test(`test .isString(${val}) --> true`, () => {
- expect(isString(val)).toBe(true)
- })
- })
-
- const negatives = [{}, 30, [], 1.5, null, undefined]
- negatives.forEach((val) => {
- test(`test .isString(${val}) --> false`, () => {
- expect(isString(val)).toBe(false)
- })
- })
-})
-
-describe('test .isNumber() method:', () => {
- const positives = [1, 1.5, 0, 9999, -2]
- positives.forEach((val) => {
- test(`test .isNumber(${val}) --> true`, () => {
- expect(isNumber(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', null, undefined]
- negatives.forEach((val) => {
- test(`test .isNumber(${val}) --> false`, () => {
- expect(isNumber(val)).toBe(false)
- })
- })
-})
-
-describe('test .isBoolean() method:', () => {
- const positives = [true, false, 3 !== 2, 3 === 2]
- positives.forEach((val) => {
- test(`test .isBoolean(${val}) --> true`, () => {
- expect(isBoolean(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 1, 0, null, undefined]
- negatives.forEach((val) => {
- test(`test .isBoolean(${val}) --> false`, () => {
- expect(isBoolean(val)).toBe(false)
- })
- })
-})
-
-describe('test .isNull() method:', () => {
- const positives = [null]
- positives.forEach((val) => {
- test(`test .isNull(${val}) --> true`, () => {
- expect(isNull(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 0, undefined]
- negatives.forEach((val) => {
- test(`test .isNull(${val}) --> false`, () => {
- expect(isNull(val)).toBe(false)
- })
- })
-})
-
-describe('test .isUndefined() method:', () => {
- let v
- const positives = [undefined, v]
- positives.forEach((val) => {
- test(`test .isUndefined(${val}) --> true`, () => {
- expect(isUndefined(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 0, null]
- negatives.forEach((val) => {
- test(`test .isUndefined(${val}) --> false`, () => {
- expect(isUndefined(val)).toBe(false)
- })
- })
-})
-
-describe('test .isNil() method:', () => {
- let v
- const positives = [undefined, v, null]
- positives.forEach((val) => {
- test(`test .isNil(${val}) --> true`, () => {
- expect(isNil(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 0]
- negatives.forEach((val) => {
- test(`test .isNil(${val}) --> false`, () => {
- expect(isNil(val)).toBe(false)
- })
- })
-})
-
-describe('test .isFunction() method:', () => {
- const positives = [function () {}, () => {}]
- positives.forEach((val) => {
- test(`test .isFunction(${val}) --> true`, () => {
- expect(isFunction(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 0, null]
- negatives.forEach((val) => {
- test(`test .isFunction(${val}) --> false`, () => {
- expect(isFunction(val)).toBe(false)
- })
- })
-})
-
-describe('test .isObject() method:', () => {
- const ob = new Object() // eslint-disable-line
- const positives = [{}, ob, Object.create({})]
- positives.forEach((val) => {
- test(`test .isObject(${val}) --> true`, () => {
- expect(isObject(val)).toBe(true)
- })
- })
-
- const negatives = [17, [], '', 0, null, () => {}, true]
- negatives.forEach((val) => {
- test(`test .isObject(${val}) --> false`, () => {
- expect(isObject(val)).toBe(false)
- })
- })
-})
-
-describe('test .isDate() method:', () => {
- const dt = new Date()
- const positives = [dt]
- positives.forEach((val) => {
- test(`test .isDate(${val}) --> true`, () => {
- expect(isDate(val)).toBe(true)
- })
- })
-
- const negatives = [17, [], '', 0, null, () => {}, true, {}, dt.toUTCString()]
- negatives.forEach((val) => {
- test(`test .isDate(${val}) --> false`, () => {
- expect(isDate(val)).toBe(false)
- })
- })
-})
-
-describe('test .isElement() method:', () => {
- const el = document.createElement('DIV')
- const positives = [el]
- positives.forEach((val) => {
- test(`test .isElement(${val}) --> true`, () => {
- expect(isElement(val)).toBe(true)
- })
- })
-
- const negatives = [17, [], '', 0, null, () => {}, true, {}]
- negatives.forEach((val) => {
- test(`test .isElement(${val}) --> false`, () => {
- expect(isElement(val)).toBe(false)
- })
- })
-})
-
-describe('test .isLetter() method:', () => {
- const positives = ['a', 'A', 'sigma']
- positives.forEach((val) => {
- test(`test .isLetter(${val}) --> true`, () => {
- expect(isLetter(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 0, undefined, 'a23b']
- negatives.forEach((val) => {
- test(`test .isLetter(${val}) --> false`, () => {
- expect(isLetter(val)).toBe(false)
- })
- })
-})
-
-describe('test .isEmail() method:', () => {
- const positives = ['admin@pwshub.com', 'abc@qtest.com']
- positives.forEach((val) => {
- test(`test .isEmail(${val}) --> true`, () => {
- expect(isEmail(val)).toBe(true)
- })
- })
-
- const negatives = [{}, [], '', 0, undefined, 'a23b@qtest@com']
- negatives.forEach((val) => {
- test(`test .isEmail(${val}) --> false`, () => {
- expect(isEmail(val)).toBe(false)
- })
- })
-})
-
-describe('test .isEmpty() method:', () => {
- const positives = ['', 0, {}, [], undefined, null]
- positives.forEach((val) => {
- test(`test .isEmpty(${val}) --> true`, () => {
- expect(isEmpty(val)).toBe(true)
- })
- })
-
- const negatives = [{ a: 1 }, '12', 9, [7, 1]]
- negatives.forEach((val) => {
- test(`test .isEmpty(${val}) --> false`, () => {
- expect(isEmpty(val)).toBe(false)
- })
- })
-})
-
-describe('test .hasProperty() method:', () => {
- const obj = {
- name: 'alice',
- age: 17,
- }
- const positives = ['name', 'age']
- positives.forEach((val) => {
- test(`test .hasProperty(${val}) --> true`, () => {
- expect(hasProperty(obj, val)).toBe(true)
- })
- })
-
- const negatives = [{ a: 1 }, 'email', 9, '__proto__']
- negatives.forEach((val) => {
- test(`test .hasProperty(${val}) --> false`, () => {
- expect(hasProperty(obj, val)).toBe(false)
- })
- })
- test('test .hasProperty(null) --> false', () => {
- expect(hasProperty(null)).toBe(false)
- })
-})
diff --git a/src/utils/maybe.js b/src/utils/maybe.js
deleted file mode 100755
index 203e0d0..0000000
--- a/src/utils/maybe.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// utils / maybe
-
-import {
- defineProp
-} from './defineProp.js'
-
-export const maybe = (val) => {
- const __val = val
- const isNil = () => {
- return __val === null || __val === undefined
- }
- const value = () => {
- return __val
- }
- const getElse = (fn) => {
- return maybe(__val || fn())
- }
- const filter = (fn) => {
- return maybe(fn(__val) === true ? __val : null)
- }
- const map = (fn) => {
- return maybe(isNil() ? null : fn(__val))
- }
- const output = Object.create({})
- defineProp(output, '__value__', __val, { enumerable: true })
- defineProp(output, '__type__', 'Maybe', { enumerable: true })
- defineProp(output, 'isNil', isNil)
- defineProp(output, 'value', value)
- defineProp(output, 'map', map)
- defineProp(output, 'if', filter)
- defineProp(output, 'else', getElse)
- return output
-}
diff --git a/src/utils/maybe.test.js b/src/utils/maybe.test.js
deleted file mode 100755
index 5c721eb..0000000
--- a/src/utils/maybe.test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// maybe.test
-
-/* eslint-env jest */
-
-import {
- maybe
-} from './maybe.js'
-
-describe('test .maybe() method:', () => {
- const plus5 = (x) => x + 5
- const minus2 = (x) => x - 2
- const isNumber = (x) => Number(x) === x
- const toString = (x) => 'The value is ' + String(x)
- const getDefault = () => 'This is default value'
-
- test(' check if .maybe() works correctly', () => {
- const x1 = maybe(5)
- .if(isNumber)
- .map(plus5)
- .map(minus2)
- .map(toString)
- .else(getDefault)
- .value()
- expect(x1).toEqual('The value is 8')
-
- const x2 = maybe('nothing')
- .if(isNumber)
- .map(plus5)
- .map(minus2)
- .map(toString)
- .else(getDefault)
- .value()
- expect(x2).toEqual('This is default value')
- })
-})
diff --git a/src/utils/pipe.js b/src/utils/pipe.js
deleted file mode 100755
index 3ee2b54..0000000
--- a/src/utils/pipe.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// utils / pipe
-
-export const pipe = (...fns) => {
- return fns.reduce((f, g) => (x) => g(f(x)))
-}
diff --git a/src/utils/pipe.test.js b/src/utils/pipe.test.js
deleted file mode 100755
index 9ce5904..0000000
--- a/src/utils/pipe.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// pipe.test
-
-/* eslint-env jest */
-
-import {
- pipe
-} from './pipe.js'
-
-describe('test .pipe() method:', () => {
- const f1 = (name) => {
- return `f1 ${name}`
- }
- const f2 = (name) => {
- return `f2 ${name}`
- }
- const f3 = (name) => {
- return `f3 ${name}`
- }
-
- const addDashes = pipe(f1, f2, f3)
-
- const add3 = (num) => {
- return num + 3
- }
-
- const mul6 = (num) => {
- return num * 6
- }
-
- const div2 = (num) => {
- return num / 2
- }
-
- const sub5 = (num) => {
- return num - 5
- }
-
- const calculate = pipe(add3, mul6, div2, sub5)
-
- test(' check if .compose() works correctly', () => {
- expect(addDashes('Alice')).toEqual('f3 f2 f1 Alice')
- expect(calculate(5)).toEqual(19)
- })
-})
diff --git a/src/utils/random.js b/src/utils/random.js
deleted file mode 100755
index fc7cbf1..0000000
--- a/src/utils/random.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// utils / random
-
-const crypto = globalThis.crypto
-
-export const genid = (len = 32, prefix = '') => {
- let s = prefix
- const nums = crypto.getRandomValues(new Uint32Array(len))
- for (let i = 0; i < nums.length; i++) {
- const n = nums[i].toString(36)
- const r = Math.random()
- const c = n.charAt(Math.floor(r * n.length))
- s += (r > 0.3 && r < 0.7) ? c.toUpperCase() : c
- }
- return s.substring(0, len)
-}
-
-export const randint = (min = 0, max = 1e6) => {
- const byteArray = new Uint8Array(1)
- crypto.getRandomValues(byteArray)
- const floatNum = '0.' + byteArray[0].toString()
- return Math.floor(floatNum * (max - min + 1)) + min
-}
diff --git a/src/utils/random.test.js b/src/utils/random.test.js
deleted file mode 100755
index aca423a..0000000
--- a/src/utils/random.test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// random.test
-
-/* eslint-env jest */
-
-import { randint, genid } from './random.js'
-
-describe('test .randint() method:', () => {
- const randArr = []
- while (randArr.length < 20) {
- randArr.push(randint())
- }
- test(`test .randint() after ${randArr.length} times`, () => {
- expect(randArr).toHaveLength(20)
- const uniqVal = Array.from(new Set(randArr))
- expect(uniqVal.length).toBeGreaterThan(10)
- })
-
- test('test .randint() with same min/max', () => {
- const q = randint(10, 10)
- expect(q).toEqual(10)
- })
-
- const min = 50
- const max = 80
- test(`test .randint() between ${min} - ${max}`, () => {
- for (let i = 0; i < 100; i++) {
- const q = randint(min, max)
- expect(q).toBeGreaterThanOrEqual(min)
- expect(q).toBeLessThanOrEqual(max)
- }
- })
-})
-
-describe('test .genid() method:', () => {
- test('check .genid() default param', () => {
- const actual = genid()
- expect(actual).toHaveLength(32)
- })
-
- test('check .genid(512)', () => {
- const actual = genid(512)
- expect(actual).toHaveLength(512)
- })
-
- const len = 100
- const ids = []
- while (ids.length < len) {
- ids.push(genid())
- }
- const uniques = Array.from(new Set(ids))
- test('check .genid() always return unique string', () => {
- expect(ids).toHaveLength(len)
- expect(uniques).toHaveLength(ids.length)
- })
-})
diff --git a/src/utils/string.js b/src/utils/string.js
deleted file mode 100755
index 0438690..0000000
--- a/src/utils/string.js
+++ /dev/null
@@ -1,143 +0,0 @@
-// utils / string
-
-import {
- isArray,
- isString,
- isNumber,
- hasProperty
-} from './detection.js'
-
-const toString = (input) => {
- const s = isNumber(input) ? String(input) : input
- if (!isString(s)) {
- throw new Error('InvalidInput: String required.')
- }
- return s
-}
-
-export const truncate = (s, len = 140) => {
- const txt = toString(s)
- const txtlen = txt.length
- if (txtlen <= len) {
- return txt
- }
- const subtxt = txt.substring(0, len).trim()
- const subtxtArr = subtxt.split(' ')
- const subtxtLen = subtxtArr.length
- if (subtxtLen > 1) {
- subtxtArr.pop()
- return subtxtArr.map(word => word.trim()).join(' ') + '...'
- }
- return subtxt.substring(0, len - 3) + '...'
-}
-
-export const stripTags = (s) => {
- return toString(s).replace(/(<([^>]+)>)/ig, '').trim()
-}
-
-export const escapeHTML = (s) => {
- return toString(s)
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
-}
-
-export const unescapeHTML = (s) => {
- return toString(s)
- .replace(/"/g, '"')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/&/g, '&')
-}
-
-export const ucfirst = (s) => {
- const x = toString(s).toLowerCase()
- return x.length > 1 ? x.charAt(0).toUpperCase() + x.slice(1) : x.toUpperCase()
-}
-
-export const ucwords = (s) => {
- return toString(s).split(' ').map((w) => {
- return ucfirst(w)
- }).join(' ')
-}
-
-export const replaceAll = (s, alpha, beta) => {
- let x = toString(s)
- const a = isNumber(alpha) ? String(alpha) : alpha
- const b = isNumber(beta) ? String(beta) : beta
-
- if (isString(a) && isString(b)) {
- const aa = x.split(a)
- x = aa.join(b)
- } else if (isArray(a) && isString(b)) {
- a.forEach((v) => {
- x = replaceAll(x, v, b)
- })
- } else if (isArray(a) && isArray(b) && a.length === b.length) {
- const k = a.length
- if (k > 0) {
- for (let i = 0; i < k; i++) {
- const aaa = a[i]
- const bb = b[i]
- x = replaceAll(x, aaa, bb)
- }
- }
- }
- return x
-}
-
-const getCharMap = () => {
- const lmap = {
- a: 'á|à|ả|ã|ạ|ă|ắ|ặ|ằ|ẳ|ẵ|â|ấ|ầ|ẩ|ẫ|ậ|ä|æ',
- c: 'ç',
- d: 'đ|ð',
- e: 'é|è|ẻ|ẽ|ẹ|ê|ế|ề|ể|ễ|ệ|ë',
- i: 'í|ì|ỉ|ĩ|ị|ï|î',
- n: 'ñ',
- o: 'ó|ò|ỏ|õ|ọ|ô|ố|ồ|ổ|ỗ|ộ|ơ|ớ|ờ|ở|ỡ|ợ|ö|ø',
- s: 'ß',
- u: 'ú|ù|ủ|ũ|ụ|ư|ứ|ừ|ử|ữ|ự|û',
- y: 'ý|ỳ|ỷ|ỹ|ỵ|ÿ',
- }
-
- const map = {
- ...lmap,
- }
- Object.keys(lmap).forEach((k) => {
- const K = k.toUpperCase()
- map[K] = lmap[k].toUpperCase()
- })
-
- return map
-}
-
-export const stripAccent = (s) => {
- let x = toString(s)
-
- const updateS = (ai, key) => {
- x = replaceAll(x, ai, key)
- }
-
- const map = getCharMap()
- for (const key in map) {
- if (hasProperty(map, key)) {
- const a = map[key].split('|')
- a.forEach((item) => {
- return updateS(item, key)
- })
- }
- }
- return x
-}
-
-export const slugify = (s, delimiter = '-') => {
- return stripAccent(s)
- .normalize('NFKD')
- .replace(/[\u0300-\u036f]/g, '')
- .trim()
- .toLowerCase()
- .replace(/[^a-z0-9 -]/g, '')
- .replace(/\s+/g, delimiter)
- .replace(new RegExp(`${delimiter}+`, 'g'), delimiter)
-}
diff --git a/src/utils/string.test.js b/src/utils/string.test.js
deleted file mode 100755
index 27202a0..0000000
--- a/src/utils/string.test.js
+++ /dev/null
@@ -1,254 +0,0 @@
-// string.test
-
-/* eslint-env jest */
-
-import {
- truncate,
- stripTags,
- escapeHTML,
- unescapeHTML,
- ucfirst,
- ucwords,
- replaceAll,
- stripAccent,
- slugify
-} from './string.js'
-
-describe('test .truncate() method:', () => {
- const inputs = [
- {
- text: 'If a property is non-configurable, its writable attribute can only be changed to false.',
- limit: 60,
- expectation: 'If a property is non-configurable, its writable attribute...',
- },
- {
- text: 'this string is less than limit',
- limit: 100,
- expectation: 'this string is less than limit',
- },
- {
- text: 'uyyiyirwqyiyiyrihklhkjhskdjfhkahfiusayiyfiudyiyqwiyriuqyiouroiuyi',
- limit: 20,
- expectation: 'uyyiyirwqyiyiyrih...',
- },
- ]
-
- inputs.forEach(({ text, limit, expectation }, k) => {
- test(` check .truncate(text, ${k})`, () => {
- const actual = truncate(text, limit)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .stripTags() method:', () => {
- const inputs = [
- {
- text: 'Hello world',
- expectation: 'Hello world',
- },
- ]
-
- inputs.forEach(({ text, expectation }, k) => {
- test(` check .stripTags(text, ${k})`, () => {
- const actual = stripTags(text)
- expect(actual).toEqual(expectation)
- })
- })
-
- test(' check .stripTags(non-text)', () => {
- expect(() => {
- stripTags({})
- }).toThrow()
- })
-})
-
-describe('test .escapeHTML() method:', () => {
- const inputs = [
- {
- text: 'Hello world',
- expectation: '<a>Hello <b>world</b></a>',
- },
- ]
-
- inputs.forEach(({ text, expectation }) => {
- test(` check .escapeHTML(${text})`, () => {
- const actual = escapeHTML(text)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .unescapeHTML() method:', () => {
- const inputs = [
- {
- text: '<a>Hello <b>world</b></a>',
- expectation: 'Hello world',
- },
- ]
-
- inputs.forEach(({ text, expectation }) => {
- test(` check .unescapeHTML(${text})`, () => {
- const actual = unescapeHTML(text)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .ucfirst() method:', () => {
- const inputs = [
- {
- text: 'HElLo wOrLd',
- expectation: 'Hello world',
- },
- {
- text: 'h',
- expectation: 'H',
- },
- ]
-
- inputs.forEach(({ text, expectation }) => {
- test(` check .ucfirst(${text})`, () => {
- const actual = ucfirst(text)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .ucwords() method:', () => {
- const inputs = [
- {
- text: 'HElLo wOrLd',
- expectation: 'Hello World',
- },
- ]
-
- inputs.forEach(({ text, expectation }) => {
- test(` check .ucwords(${text})`, () => {
- const actual = ucwords(text)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .replaceAll() method:', () => {
- const inputs = [
- {
- input: {
- a: 'Hello world',
- b: 'l',
- c: '2',
- },
- expectation: 'He22o wor2d',
- },
- {
- input: {
- a: 'Hello world',
- b: 'l',
- c: 2,
- },
- expectation: 'He22o wor2d',
- },
- {
- input: {
- a: 798078967,
- b: 7,
- c: 1,
- },
- expectation: '198018961',
- },
- {
- input: {
- a: 'Hello world',
- b: ['l', 'o'],
- c: ['2', '0'],
- },
- expectation: 'He220 w0r2d',
- },
- {
- input: {
- a: 'Hello world',
- b: ['l', 'o'],
- c: '2',
- },
- expectation: 'He222 w2r2d',
- },
- {
- input: {
- a: 'Hello world',
- b: ['l'],
- c: ['2', '0'],
- },
- expectation: 'Hello world',
- },
- {
- input: {
- a: 'Hello world',
- b: 'l',
- },
- expectation: 'Hello world',
- },
- {
- input: {
- a: 'Hello world',
- },
- expectation: 'Hello world',
- },
- {
- input: {
- a: 10000,
- },
- expectation: '10000',
- },
- {
- input: {
- a: 0,
- },
- expectation: '0',
- },
- ]
-
- inputs.forEach(({ input, expectation }) => {
- const { a, b, c } = input
- test(` check .replaceAll(${a}, ${b}, ${c})`, () => {
- const actual = replaceAll(a, b, c)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .stripAccent() method:', () => {
- const inputs = [
- {
- text: 'Sur l\'année 2015 - ủ Ù ỹ Ỹ',
- expectation: 'Sur l\'annee 2015 - u U y Y',
- },
- ]
-
- inputs.forEach(({ text, expectation }) => {
- test(` check .stripAccent(${text})`, () => {
- const actual = stripAccent(text)
- expect(actual).toEqual(expectation)
- })
- })
-})
-
-describe('test .slugify() method:', () => {
- const inputs = [
- {
- text: 'Sur l\'année 2015',
- expectation: 'sur-lannee-2015',
- },
- {
- text: 'Nghị luận tác phẩm "Đường kách mệnh" của Hồ Chí Minh?!',
- expectation: 'nghi-luan-tac-pham-duong-kach-menh-cua-ho-chi-minh',
- },
- ]
-
- inputs.forEach(({ text, expectation }) => {
- test(` check .slugify(${text})`, () => {
- const actual = slugify(text)
- expect(actual).toEqual(expectation)
- })
- })
-})
diff --git a/tests/compose_test.ts b/tests/compose_test.ts
new file mode 100644
index 0000000..2cfa02f
--- /dev/null
+++ b/tests/compose_test.ts
@@ -0,0 +1,37 @@
+import { assertEquals } from "assert";
+
+import { compose } from "../utils/compose.ts";
+
+Deno.test("check if .compose() works correctly", () => {
+ const f1 = (name: string): string => {
+ return `f1 ${name}`;
+ };
+ const f2 = (name: string): string => {
+ return `f2 ${name}`;
+ };
+ const f3 = (name: string): string => {
+ return `f3 ${name}`;
+ };
+
+ const addDashes = compose(f1, f2, f3);
+ assertEquals(addDashes("Alice"), "f1 f2 f3 Alice");
+
+ const add3 = (num: number): number => {
+ return num + 3;
+ };
+
+ const mul6 = (num: number): number => {
+ return num * 6;
+ };
+
+ const div2 = (num: number): number => {
+ return num / 2;
+ };
+
+ const sub5 = (num: number): number => {
+ return num - 5;
+ };
+
+ const calculate = compose(sub5, div2, mul6, add3);
+ assertEquals(calculate(5), 19);
+});
diff --git a/tests/curry_test.ts b/tests/curry_test.ts
new file mode 100644
index 0000000..e21255b
--- /dev/null
+++ b/tests/curry_test.ts
@@ -0,0 +1,14 @@
+import { assertEquals } from "assert";
+
+import { curry } from "../utils/curry.ts";
+
+Deno.test("check if .curry() works correctly", () => {
+ const sum = curry((a: number, b: number, c: number): number => {
+ return a + b + c;
+ });
+ assertEquals(sum(3)(2)(1), 6);
+ assertEquals(sum(1)(2)(3), 6);
+ assertEquals(sum(1, 2)(3), 6);
+ assertEquals(sum(1)(2, 3), 6);
+ assertEquals(sum(1, 2, 3), 6);
+});
diff --git a/tests/date_test.ts b/tests/date_test.ts
new file mode 100644
index 0000000..bb5bf1a
--- /dev/null
+++ b/tests/date_test.ts
@@ -0,0 +1,61 @@
+import { assertEquals } from "assert";
+
+import {
+ formatDateString,
+ formatTimeAgo
+} from "../utils/date.ts";
+
+Deno.test("check if .formatDateString() works correctly", async (t) => {
+ const d = new Date();
+
+ await t.step(' check .formatDateString() with default options', () => {
+ const result = formatDateString(d)
+ const reg = /^\w+\s\d+,\s+\d{4},\s\d+:\d+:\d+\s(AM|PM)\s(GMT)\+\d+$/
+ assertEquals(result.match(reg) !== null, true)
+ });
+
+ await t.step(' check .formatDateString() with custom options', () => {
+ const result = formatDateString(d, {
+ dateStyle: 'full',
+ timeStyle: 'medium',
+ hour12: true,
+ })
+ const reg = /^\w+,\s\w+\s\d+,\s+\d{4}\sat\s\d+:\d+:\d+\s(AM|PM)$/
+ assertEquals(result.match(reg) !== null, true)
+ });
+
+ await t.step(' check .formatDateString() with custom language and options', () => {
+ const result = formatDateString(d, 'en', {
+ dateStyle: 'full',
+ timeStyle: 'medium',
+ hour12: true,
+ })
+ const reg = /^\w+,\s\w+\s\d+,\s+\d{4}\sat\s\d+:\d+:\d+\s(AM|PM)$/
+ assertEquals(result.match(reg) !== null, true)
+ });
+});
+
+Deno.test("check if .formatTimeAgo() works correctly", async (t) => {
+ const d = new Date();
+
+ await t.step(' check if .formatTimeAgo() return "just now"', () => {
+ const result = formatTimeAgo(d)
+ assertEquals(result, 'just now')
+ const justnowCustomMessage = formatTimeAgo(d, 'vi', 'vừa mới xong')
+ assertEquals(justnowCustomMessage, 'vừa mới xong')
+ });
+
+ await t.step(' check if .formatTimeAgo() after 5s', () => {
+ const t = d.getSeconds()
+ d.setSeconds(t - 5)
+ const result = formatTimeAgo(d)
+ assertEquals(result, '5 seconds ago')
+ });
+
+ await t.step(' check if .formatTimeAgo() after 2 days', () => {
+ const t = d.getDate()
+ d.setDate(t - 2)
+ const result = formatTimeAgo(d)
+ assertEquals(result, '2 days ago')
+ });
+});
diff --git a/tests/detection_test.ts b/tests/detection_test.ts
new file mode 100644
index 0000000..29f8aea
--- /dev/null
+++ b/tests/detection_test.ts
@@ -0,0 +1,250 @@
+import { assertEquals } from "assert";
+
+import {
+ isNumber,
+ isInteger,
+ isArray,
+ isString,
+ isBoolean,
+ isNull,
+ isUndefined,
+ isNil,
+ isFunction,
+ isObject,
+ isDate,
+ isEmail,
+ isEmpty,
+ hasProperty
+} from "../utils/detection.ts";
+
+Deno.test("check if .isNumber() works correctly", async (t) => {
+ const positives = [1, 1.5, 0, 9999, -2]
+ for (const val of positives) {
+ await t.step(`test .isNumber(${val}) --> true`, () => {
+ assertEquals(isNumber(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', null, undefined]
+ for (const val of negatives) {
+ await t.step(`test .isNumber(${val}) --> true`, () => {
+ assertEquals(isNumber(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isInteger() works correctly", async (t) => {
+ const positives = [1, 1000, 9999, 0, -3]
+ for (const val of positives) {
+ await t.step(`test .isInteger(${val}) --> true`, () => {
+ assertEquals(isInteger(val), true)
+ })
+ }
+
+ const negatives = [1.5, -3.2, '', undefined]
+ for (const val of negatives) {
+ await t.step(`test .isInteger(${val}) --> true`, () => {
+ assertEquals(isInteger(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isArray() works correctly", async (t) => {
+ const positives = [[], [1, 2, 3], new Array(), Array.from(new Set())]
+ for (const val of positives) {
+ await t.step(`test .isArray(${val}) --> true`, () => {
+ assertEquals(isArray(val), true)
+ })
+ }
+
+ const negatives = [1.5, '', undefined]
+ for (const val of negatives) {
+ await t.step(`test .isArray(${val}) --> true`, () => {
+ assertEquals(isArray(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isString() works correctly", async (t) => {
+ const positives = ['', 'abc xyz', '10000']
+ for (const val of positives) {
+ await t.step(`test .isString(${val}) --> true`, () => {
+ assertEquals(isString(val), true)
+ })
+ }
+
+ const negatives = [{}, 30, [], 1.5, null, undefined]
+ for (const val of negatives) {
+ await t.step(`test .isString(${val}) --> true`, () => {
+ assertEquals(isString(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isBoolean() works correctly", async (t) => {
+ const positives = [true, false]
+ for (const val of positives) {
+ await t.step(`test .isBoolean(${val}) --> true`, () => {
+ assertEquals(isBoolean(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', 1, 0, null, undefined]
+ for (const val of negatives) {
+ await t.step(`test .isBoolean(${val}) --> true`, () => {
+ assertEquals(isBoolean(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isNull() works correctly", async (t) => {
+ const positives = [null]
+ for (const val of positives) {
+ await t.step(`test .isNull(${val}) --> true`, () => {
+ assertEquals(isNull(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', 0, undefined]
+ for (const val of negatives) {
+ await t.step(`test .isNull(${val}) --> true`, () => {
+ assertEquals(isNull(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isUndefined() works correctly", async (t) => {
+ let v
+ const positives = [undefined, v]
+ for (const val of positives) {
+ await t.step(`test .isUndefined(${val}) --> true`, () => {
+ assertEquals(isUndefined(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', 0, null]
+ for (const val of negatives) {
+ await t.step(`test .isUndefined(${val}) --> true`, () => {
+ assertEquals(isUndefined(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isNil() works correctly", async (t) => {
+ let v
+ const positives = [undefined, v, null]
+ for (const val of positives) {
+ await t.step(`test .isNil(${val}) --> true`, () => {
+ assertEquals(isNil(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', 0]
+ for (const val of negatives) {
+ await t.step(`test .isNil(${val}) --> true`, () => {
+ assertEquals(isNil(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isFunction() works correctly", async (t) => {
+ const positives = [function () {}, () => {}]
+ for (const val of positives) {
+ await t.step(`test .isFunction(${val}) --> true`, () => {
+ assertEquals(isFunction(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', 0, null]
+ for (const val of negatives) {
+ await t.step(`test .isFunction(${val}) --> true`, () => {
+ assertEquals(isFunction(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isObject() works correctly", async (t) => {
+ const ob = new Object()
+ const positives = [{}, ob, Object.create({})]
+ for (const val of positives) {
+ await t.step(`test .isObject(${val}) --> true`, () => {
+ assertEquals(isObject(val), true)
+ })
+ }
+
+ const negatives = [17, [], '', 0, null, () => {}, true]
+ for (const val of negatives) {
+ await t.step(`test .isObject(${val}) --> true`, () => {
+ assertEquals(isObject(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isDate() works correctly", async (t) => {
+ const dt = new Date()
+ const positives = [dt]
+ for (const val of positives) {
+ await t.step(`test .isDate(${val}) --> true`, () => {
+ assertEquals(isDate(val), true)
+ })
+ }
+
+ const negatives = [17, [], '', 0, null, () => {}, true, {}, dt.toUTCString()]
+ for (const val of negatives) {
+ await t.step(`test .isDate(${val}) --> true`, () => {
+ assertEquals(isDate(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isEmail() works correctly", async (t) => {
+ const positives = ['admin@pwshub.com', 'abc@qtest.com']
+ for (const val of positives) {
+ await t.step(`test .isEmail(${val}) --> true`, () => {
+ assertEquals(isEmail(val), true)
+ })
+ }
+
+ const negatives = [{}, [], '', 0, undefined, 'a23b@qtest@com']
+ for (const val of negatives) {
+ await t.step(`test .isEmail(${val}) --> true`, () => {
+ assertEquals(isEmail(val), false)
+ })
+ }
+});
+
+Deno.test("check if .isEmpty() works correctly", async (t) => {
+ const positives = ['', 0, {}, [], undefined, null]
+ for (const val of positives) {
+ await t.step(`test .isEmpty(${val}) --> true`, () => {
+ assertEquals(isEmpty(val), true)
+ })
+ }
+
+ const negatives = [{ a: 1 }, '12', 9, [7, 1]]
+ for (const val of negatives) {
+ await t.step(`test .isEmpty(${val}) --> true`, () => {
+ assertEquals(isEmpty(val), false)
+ })
+ }
+});
+
+Deno.test("check if .hasProperty() works correctly", async (t) => {
+ const obj = {
+ name: 'alice',
+ age: 17,
+ }
+ const positives = ['name', 'age']
+ for (const val of positives) {
+ await t.step(`test .hasProperty(${val}) --> true`, () => {
+ assertEquals(hasProperty(obj, val), true)
+ })
+ }
+
+ const negatives = [{ a: 1 }, 'email', 9, '__proto__']
+ for (const val of negatives) {
+ await t.step(`test .hasProperty(${val}) --> true`, () => {
+ assertEquals(hasProperty(obj, val), false)
+ })
+ }
+});
diff --git a/tests/maybe_test.ts b/tests/maybe_test.ts
new file mode 100644
index 0000000..f9b2ab8
--- /dev/null
+++ b/tests/maybe_test.ts
@@ -0,0 +1,29 @@
+import { assertEquals } from "assert";
+
+import { maybe } from "../utils/maybe.ts";
+
+Deno.test("check if .maybe() works correctly", () => {
+ const plus5 = (x: number): number => x + 5;
+ const minus2 = (x: number): number => x - 2;
+ const isNumber = (x: any): boolean => Number(x) === x;
+ const toString = (x: string): string => "The value is " + String(x);
+ const getDefault = (): string => "This is default value";
+
+ const x1 = maybe(5)
+ .if(isNumber)
+ .map(plus5)
+ .map(minus2)
+ .map(toString)
+ .else(getDefault)
+ .value();
+ assertEquals(x1, "The value is 8");
+
+ const x2 = maybe("nothing")
+ .if(isNumber)
+ .map(plus5)
+ .map(minus2)
+ .map(toString)
+ .else(getDefault)
+ .value();
+ assertEquals(x2, "This is default value");
+});
diff --git a/tests/mod_test.ts b/tests/mod_test.ts
new file mode 100644
index 0000000..ef63fd1
--- /dev/null
+++ b/tests/mod_test.ts
@@ -0,0 +1,151 @@
+import { assertEquals, assertStringIncludes } from "assert";
+
+import {
+ clone,
+ copies,
+ unique,
+ sort,
+ sortBy,
+ pick,
+ hasProperty
+} from "../mod.ts";
+
+Deno.test("check if .clone() works correctly", async (t) => {
+ await t.step(`check if .clone(object) works correctly`, () => {
+ const x: any = {
+ level: 4,
+ IQ: 140,
+ epouse: {
+ name: 'Alice',
+ age: 27,
+ },
+ birthday: new Date(),
+ a: 0,
+ clone: false,
+ reg: /^\w+@\s([a-z])$/gi,
+ }
+ const y = clone(x)
+ Object.keys(x).forEach((k) => {
+ assertEquals(hasProperty(y, k), true)
+ })
+ Object.keys(x.epouse).forEach((k) => {
+ assertEquals(hasProperty(y.epouse, k), true)
+ assertEquals(y.epouse[k], x.epouse[k as keyof typeof x.epouse])
+ })
+
+ // check immutability
+ y.epouse.age = 25
+ assertEquals(y.epouse.age, 25)
+ assertEquals(x.epouse.age, 27)
+ })
+
+ await t.step(`check if .clone(array) works correctly`, () => {
+ const x: any[] = [
+ 1,
+ 5,
+ 0,
+ 'a',
+ -10,
+ '-10',
+ '',
+ {
+ a: 1,
+ b: 'Awesome',
+ },
+ [
+ 5,
+ 6,
+ 8,
+ {
+ name: 'Lys',
+ age: 11,
+ },
+ ],
+ ]
+ const y = clone(x)
+ assertEquals(y.length, x.length)
+ for (let i = 0; i < x.length; i++) {
+ assertEquals(x[i], y[i])
+ }
+
+ // check immutability
+ y[8][3].age = 10
+ assertEquals(y[8][3].age, 10)
+ assertEquals(x[8][3].age, 11)
+ })
+});
+
+Deno.test("check if .copies() works correctly", async (t) => {
+ // await t.step(`check if .copies(source, dest) works correctly`, () => {
+ // const str = 'abcdefghijklmnopqrstuvwxyz'
+ // const arr = str.split('')
+ // const uniqChar = pick(arr)[0]
+ // assertStringIncludes(str, uniqChar)
+ // })
+
+ await t.step(`check if .copies(source, dest, matched, excepts) works correctly`, () => {
+ const source = {
+ name: 'Kiwi',
+ age: 16,
+ gender: 'male',
+ }
+ const dest = {
+ name: 'Aline',
+ age: 20,
+ }
+ copies(source, dest, true, ['age'])
+ assertEquals(hasProperty(dest, 'gender'), false)
+ assertEquals(dest.name, source.name)
+ assertEquals(dest.age, source.age)
+ })
+});
+
+Deno.test("check if .unique(array) works correctly", () => {
+ const arr = [1, 1, 2, 2, 3, 4, 5, 5, 6, 3, 5, 4]
+ const uniqArr = unique(arr)
+ assertEquals(uniqArr.length, 6)
+});
+
+Deno.test("check if .sort(array) works correctly", () => {
+ const arr = [6, 4, 8, 2]
+ const sortedArr = sort(arr)
+ assertEquals(sortedArr.join(''), '2468')
+});
+
+Deno.test("check if .sortBy(array) works correctly", () => {
+ const arr = [
+ { age: 5, name: 'E' },
+ { age: 9, name: 'B' },
+ { age: 3, name: 'A' },
+ { age: 12, name: 'D' },
+ { age: 7, name: 'C' },
+ ]
+ const sortedByAge = [
+ { age: 3, name: 'A' },
+ { age: 5, name: 'E' },
+ { age: 7, name: 'C' },
+ { age: 9, name: 'B' },
+ { age: 12, name: 'D' },
+ ]
+ const sortedArr = sortBy(arr, 1, 'age')
+ assertEquals(JSON.stringify(sortedArr), JSON.stringify(sortedByAge))
+
+ const sortedByNonExistKey = sortBy(arr, 1, 'balance')
+ assertEquals(JSON.stringify(sortedByNonExistKey), JSON.stringify(arr))
+});
+
+Deno.test("check if .pick() works correctly", async (t) => {
+ await t.step(`check if .pick(array) works correctly`, () => {
+ const str = 'abcdefghijklmnopqrstuvwxyz'
+ const arr = str.split('')
+ const uniqChar = pick(arr)[0]
+ assertStringIncludes(str, uniqChar)
+ })
+
+ await t.step(`check if .pick(array, count) works correctly`, () => {
+ const str = 'abcdefghijklmnopqrstuvwxyz'
+ const arr = str.split('')
+ const picked = pick(arr, 10)
+ assertEquals(picked.length, 10)
+ })
+});
diff --git a/tests/pipe_test.ts b/tests/pipe_test.ts
new file mode 100644
index 0000000..7f89bda
--- /dev/null
+++ b/tests/pipe_test.ts
@@ -0,0 +1,37 @@
+import { assertEquals } from "assert";
+
+import { pipe } from "../utils/pipe.ts";
+
+Deno.test("check if .pipe() works correctly", () => {
+ const f1 = (name: string): string => {
+ return `f1 ${name}`;
+ };
+ const f2 = (name: string): string => {
+ return `f2 ${name}`;
+ };
+ const f3 = (name: string): string => {
+ return `f3 ${name}`;
+ };
+
+ const addDashes = pipe(f1, f2, f3);
+ assertEquals(addDashes("Alice"), "f3 f2 f1 Alice");
+
+ const add3 = (num: number): number => {
+ return num + 3;
+ };
+
+ const mul6 = (num: number): number => {
+ return num * 6;
+ };
+
+ const div2 = (num: number): number => {
+ return num / 2;
+ };
+
+ const sub5 = (num: number): number => {
+ return num - 5;
+ };
+
+ const calculate = pipe(add3, mul6, div2, sub5);
+ assertEquals(calculate(5), 19);
+});
diff --git a/tests/random_test.ts b/tests/random_test.ts
new file mode 100644
index 0000000..2e0797b
--- /dev/null
+++ b/tests/random_test.ts
@@ -0,0 +1,60 @@
+import { assertEquals } from "assert";
+
+import { genid, randint } from "../utils/random.ts";
+
+Deno.test("check if .randint() works correctly", async (t) => {
+ const randArr: number[] = [];
+ while (randArr.length < 20) {
+ randArr.push(randint());
+ }
+
+ await t.step(`.randint() after ${randArr.length} times`, async () => {
+ assertEquals(randArr.length, 20);
+ const uniqVal = Array.from(new Set(randArr));
+ assertEquals(uniqVal.length > 10, true);
+ });
+
+ await t.step(".randint() with min = max", async () => {
+ const q = randint(10, 10);
+ assertEquals(q, 10);
+ });
+
+ const min = 50;
+ const max = 80;
+ await t.step(`.randint() between ${min} - ${max}`, async () => {
+ for (let i = 0; i < 100; i++) {
+ const q = randint(min, max);
+ assertEquals(q >= min, true);
+ assertEquals(q <= max, true);
+ }
+ });
+});
+
+Deno.test("check if .genid() works correctly", async (t) => {
+ const randArr: number[] = [];
+ while (randArr.length < 20) {
+ randArr.push(randint());
+ }
+
+ await t.step(".genid() default param", async () => {
+ const actual = genid();
+ assertEquals(actual.length, 32);
+ });
+
+ await t.step(".genid(512) default param", async () => {
+ const actual = genid(512);
+ assertEquals(actual.length, 512);
+ });
+
+ const len = 100;
+ const ids = [];
+ while (ids.length < len) {
+ ids.push(genid());
+ }
+ const uniques = Array.from(new Set(ids));
+
+ await t.step(`.genid() always return unique string`, async () => {
+ assertEquals(ids.length, len);
+ assertEquals(uniques.length, len);
+ });
+});
diff --git a/tests/string_test.ts b/tests/string_test.ts
new file mode 100644
index 0000000..123a85e
--- /dev/null
+++ b/tests/string_test.ts
@@ -0,0 +1,77 @@
+import { assertEquals } from "assert";
+
+import {
+ truncate,
+ stripTags,
+ escapeHTML,
+ unescapeHTML,
+ ucfirst,
+ ucwords,
+ slugify
+} from "../utils/string.ts";
+
+Deno.test("check if .truncate() works correctly", async (t) => {
+ const inputs = [
+ {
+ text: 'If a property is non-configurable, its writable attribute can only be changed to false.',
+ limit: 60,
+ expectation: 'If a property is non-configurable, its writable attribute...',
+ },
+ {
+ text: 'this string is less than limit',
+ limit: 100,
+ expectation: 'this string is less than limit',
+ },
+ {
+ text: 'uyyiyirwqyiyiyrihklhkjhskdjfhkahfiusayiyfiudyiyqwiyriuqyiouroiuyi',
+ limit: 20,
+ expectation: 'uyyiyirwqyiyiyrih...',
+ },
+ ]
+
+ let k = 1
+ for (const input of inputs) {
+ const { text, limit, expectation } = input
+ await t.step(` check .truncate($${k})`, () => {
+ const actual = truncate(text, limit)
+ assertEquals(actual, expectation)
+ })
+ k++
+ }
+});
+
+Deno.test("check if .stripTags() works correctly", () => {
+ const actual = stripTags('Hello world')
+ assertEquals(actual, 'Hello world')
+});
+
+Deno.test("check if .escapeHTML() works correctly", () => {
+ const actual = escapeHTML('Hello world')
+ assertEquals(actual, '<a>Hello <b>world</b></a>')
+});
+
+Deno.test("check if .unescapeHTML() works correctly", () => {
+ const actual = unescapeHTML('<a>Hello <b>world</b></a>')
+ assertEquals(actual, 'Hello world')
+});
+
+Deno.test("check if .ucfirst() works correctly", () => {
+ const actual = ucfirst('HElLo wOrLd')
+ assertEquals(actual, 'Hello world')
+});
+
+Deno.test("check if .ucwords() works correctly", () => {
+ const actual = ucwords('HElLo wOrLd')
+ assertEquals(actual, 'Hello World')
+});
+
+Deno.test("check if .slugify() works correctly", () => {
+ assertEquals(
+ slugify('Sur l\'année 2015'),
+ 'sur-lannee-2015'
+ )
+ assertEquals(
+ slugify('Nghị luận tác phẩm "Đường kách mệnh" của Hồ Chí Minh?!'),
+ 'nghi-luan-tac-pham-duong-kach-menh-cua-ho-chi-minh'
+ )
+});
diff --git a/utils/compose.ts b/utils/compose.ts
new file mode 100755
index 0000000..9a06893
--- /dev/null
+++ b/utils/compose.ts
@@ -0,0 +1,5 @@
+// utils / compose
+
+export const compose = (...fns: ((arg: T) => T)[]): (arg: T) => T => {
+ return fns.reduce((f, g) => (x) => f(g(x)));
+};
diff --git a/utils/curry.ts b/utils/curry.ts
new file mode 100755
index 0000000..3bb349f
--- /dev/null
+++ b/utils/curry.ts
@@ -0,0 +1,14 @@
+// utils / curry
+
+export const curry = any>(fn: T) => {
+ const totalArguments: number = fn.length;
+ const next = (argumentLength: number, rest: any[]) => {
+ if (argumentLength > 0) {
+ return (...args: any[]) => {
+ return next(argumentLength - args.length, [...rest, ...args]);
+ };
+ }
+ return fn(...rest);
+ };
+ return next(totalArguments, []);
+};
diff --git a/src/utils/date.js b/utils/date.ts
similarity index 51%
rename from src/utils/date.js
rename to utils/date.ts
index 52aa31f..1a53cb6 100755
--- a/src/utils/date.js
+++ b/utils/date.ts
@@ -1,17 +1,30 @@
// utils / date
-import {
- isObject
-} from './detection.js'
+import { isObject } from "./detection.ts";
-const getDateFormat = () => {
- return {
- dateStyle: 'medium',
- timeStyle: 'long',
- }
+interface DateFormat {
+ dateStyle: string;
+ timeStyle: string;
+}
+
+interface TimeConversions {
+ second: number;
+ minute: number;
+ hour: number;
+ day: number;
+ week: number;
+ month: number;
+ year: number;
}
-const getTimeConvers = () => {
+const getDateFormat = (): DateFormat => {
+ return {
+ dateStyle: "medium",
+ timeStyle: "long",
+ };
+};
+
+const getTimeConvers = (): TimeConversions => {
return {
second: 1000,
minute: 60,
@@ -20,10 +33,10 @@ const getTimeConvers = () => {
week: 7,
month: 4,
year: 12,
- }
-}
+ };
+};
-const isValidLocal = (hl) => {
+const isValidLocal = (hl: string): boolean => {
try {
const locale = new Intl.Locale(hl)
return locale.language !== ''
@@ -32,7 +45,7 @@ const isValidLocal = (hl) => {
}
}
-export const formatDateString = (...args) => {
+export const formatDateString = (...args: any[]): string => {
const input = args[0]
const lang = isValidLocal(args[1]) ? args[1] : 'en'
const dfmt = getDateFormat()
@@ -47,20 +60,20 @@ export const formatDateString = (...args) => {
return dtf.format(new Date(input))
}
-export const formatTimeAgo = (input, lang = 'en', justnow = 'just now') => {
- const t = new Date(input)
- let delta = Date.now() - t
+export const formatTimeAgo = (input: any, lang: string = 'en', justnow: string = 'just now'): string => {
+ const t: number = (new Date(input)).getTime()
+ let delta: number = Date.now() - t
const tcv = getTimeConvers()
if (delta <= tcv.second) {
return justnow
}
- let unit = 'second'
+ let unit: any = 'second'
for (const key in tcv) {
- if (delta < tcv[key]) {
+ if (delta < tcv[key as keyof typeof tcv]) {
break
} else {
unit = key
- delta /= tcv[key]
+ delta /= tcv[key as keyof typeof tcv]
}
}
delta = Math.floor(delta)
diff --git a/src/utils/defineProp.js b/utils/defineProp.ts
similarity index 51%
rename from src/utils/defineProp.js
rename to utils/defineProp.ts
index 44c17b5..4ccd1eb 100755
--- a/src/utils/defineProp.js
+++ b/utils/defineProp.ts
@@ -1,15 +1,21 @@
// utils / defineProp
-export const defineProp = (ob, key, val, config = {}) => {
+export const defineProp = (
+ ob: object,
+ key: string,
+ val: any,
+ config: { writable?: boolean; configurable?: boolean; enumerable?: boolean } =
+ {},
+): void => {
const {
writable = false,
configurable = false,
enumerable = false,
- } = config
+ } = config;
Object.defineProperty(ob, key, {
value: val,
writable,
configurable,
enumerable,
- })
-}
+ });
+};
diff --git a/utils/detection.ts b/utils/detection.ts
new file mode 100755
index 0000000..42b0e7c
--- /dev/null
+++ b/utils/detection.ts
@@ -0,0 +1,69 @@
+// utils / detection
+
+const ob2Str = (val: any): string => {
+ return {}.toString.call(val);
+};
+
+export const isNumber = (val: any): boolean => {
+ return Number(val) === val;
+};
+
+export const isInteger = (val: any): boolean => {
+ return Number.isInteger(val);
+};
+
+export const isArray = (val: any): boolean => {
+ return Array.isArray(val);
+};
+
+export const isString = (val: any): boolean => {
+ return String(val) === val;
+};
+
+export const isBoolean = (val: any): boolean => {
+ return Boolean(val) === val;
+};
+
+export const isNull = (val: any): boolean => {
+ return ob2Str(val) === "[object Null]";
+};
+
+export const isUndefined = (val: any): boolean => {
+ return ob2Str(val) === "[object Undefined]";
+};
+
+export const isNil = (val: any): boolean => {
+ return isUndefined(val) || isNull(val);
+};
+
+export const isFunction = (val: any): boolean => {
+ return ob2Str(val) === "[object Function]";
+};
+
+export const isObject = (val: any): boolean => {
+ return ob2Str(val) === "[object Object]" && !isArray(val);
+};
+
+export const isDate = (val: any): boolean => {
+ return val instanceof Date && !isNaN(val.valueOf());
+};
+
+export const isEmail = (val: any): boolean => {
+ const re =
+ /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
+ return isString(val) && re.test(val);
+};
+
+export const isEmpty = (val: any): boolean => {
+ return !val || isNil(val) ||
+ (isString(val) && val === "") ||
+ (isArray(val) && val.length === 0) ||
+ (isObject(val) && Object.keys(val).length === 0);
+};
+
+export const hasProperty = (ob: any, k: any): boolean => {
+ if (!ob || !k) {
+ return false;
+ }
+ return Object.prototype.hasOwnProperty.call(ob, k);
+};
diff --git a/utils/maybe.ts b/utils/maybe.ts
new file mode 100755
index 0000000..5465f58
--- /dev/null
+++ b/utils/maybe.ts
@@ -0,0 +1,47 @@
+// utils / maybe
+
+import { defineProp } from "./defineProp.ts";
+
+export const maybe = (val: T) => {
+ const __val = val;
+ const isNil = (): boolean => {
+ return __val === null || __val === undefined;
+ };
+ const value = (): T => {
+ return __val;
+ };
+ const getElse = (fn: () => T): typeof maybe => {
+ return maybe(__val || fn());
+ };
+ const filter = (fn: (val: T) => boolean): typeof maybe => {
+ return maybe(fn(__val) === true ? __val : null);
+ };
+ const map = (fn: (val: T) => U): typeof maybe => {
+ return maybe(isNil() ? null : fn(__val));
+ };
+ const output = Object.create({});
+ Object.defineProperty(output, "__value__", {
+ value: __val,
+ enumerable: true,
+ });
+ Object.defineProperty(output, "__type__", {
+ value: "Maybe",
+ enumerable: true,
+ });
+ Object.defineProperty(output, "isNil", {
+ value: isNil,
+ });
+ Object.defineProperty(output, "value", {
+ value: value,
+ });
+ Object.defineProperty(output, "map", {
+ value: map,
+ });
+ Object.defineProperty(output, "if", {
+ value: filter,
+ });
+ Object.defineProperty(output, "else", {
+ value: getElse,
+ });
+ return output;
+};
diff --git a/utils/pipe.ts b/utils/pipe.ts
new file mode 100755
index 0000000..dcbe084
--- /dev/null
+++ b/utils/pipe.ts
@@ -0,0 +1,5 @@
+// utils / pipe
+
+export const pipe = (...fns: ((arg: T) => T)[]): (arg: T) => T => {
+ return fns.reduce((f, g) => (x) => g(f(x)));
+};
diff --git a/utils/random.ts b/utils/random.ts
new file mode 100755
index 0000000..b10f26c
--- /dev/null
+++ b/utils/random.ts
@@ -0,0 +1,23 @@
+// utils / random
+
+const crypto = globalThis.crypto;
+
+export const genid = (len: number = 32, prefix: string = ""): string => {
+ let s: string = prefix;
+ const nums: Uint32Array = new Uint32Array(len);
+ crypto.getRandomValues(nums);
+ for (let i: number = 0; i < nums.length; i++) {
+ const n: string = nums[i].toString(36);
+ const r: number = Math.random();
+ const c: string = n.charAt(Math.floor(r * n.length));
+ s += r > 0.3 && r < 0.7 ? c.toUpperCase() : c;
+ }
+ return s.substring(0, len);
+};
+
+export const randint = (min: number = 0, max: number = 1e6): number => {
+ const byteArray = new Uint8Array(1);
+ crypto.getRandomValues(byteArray);
+ const floatNum = Number("0." + byteArray[0].toString());
+ return Math.floor(floatNum * (max - min + 1)) + min;
+};
diff --git a/utils/string.ts b/utils/string.ts
new file mode 100755
index 0000000..45178ed
--- /dev/null
+++ b/utils/string.ts
@@ -0,0 +1,115 @@
+// utils / string
+
+import { hasProperty, isArray, isNumber, isString } from "./detection.ts";
+
+const toString = (input: any): string => {
+ return !isString(input) ? String(input) : input;
+};
+
+export const truncate = (s: string, len: number = 140): string => {
+ const txt = toString(s);
+ const txtlen = txt.length;
+ if (txtlen <= len) {
+ return txt;
+ }
+ const subtxt = txt.substring(0, len).trim();
+ const subtxtArr = subtxt.split(" ");
+ const subtxtLen = subtxtArr.length;
+ if (subtxtLen > 1) {
+ subtxtArr.pop();
+ return subtxtArr.map((word: string) => word.trim()).join(" ") + "...";
+ }
+ return subtxt.substring(0, len - 3) + "...";
+};
+
+export const stripTags = (s: string): string => {
+ return toString(s).replace(/(<([^>]+)>)/ig, "").trim();
+};
+
+export const escapeHTML = (s: string): string => {
+ return toString(s)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+};
+
+export const unescapeHTML = (s: string): string => {
+ return toString(s)
+ .replace(/"/g, '"')
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+ .replace(/&/g, "&");
+};
+
+export const ucfirst = (s: string): string => {
+ const x = toString(s).toLowerCase();
+ return x.length > 1
+ ? x.charAt(0).toUpperCase() + x.slice(1)
+ : x.toUpperCase();
+};
+
+export const ucwords = (s: string): string => {
+ return toString(s).split(" ").map((w: string) => {
+ return ucfirst(w);
+ }).join(" ");
+};
+
+export const replaceAll = (s: string, a: string, b: string): string => {
+ return toString(s).replaceAll(a, b)
+};
+
+const getCharMap = (): { [key: string]: string } => {
+ const lmap: { [key: string]: string } = {
+ a: "á|à|ả|ã|ạ|ă|ắ|ặ|ằ|ẳ|ẵ|â|ấ|ầ|ẩ|ẫ|ậ|ä|æ",
+ c: "ç",
+ d: "đ|ð",
+ e: "é|è|ẻ|ẽ|ẹ|ê|ế|ề|ể|ễ|ệ|ë",
+ i: "í|ì|ỉ|ĩ|ị|ï|î",
+ n: "ñ",
+ o: "ó|ò|ỏ|õ|ọ|ô|ố|ồ|ổ|ỗ|ộ|ơ|ớ|ờ|ở|ỡ|ợ|ö|ø",
+ s: "ß",
+ u: "ú|ù|ủ|ũ|ụ|ư|ứ|ừ|ử|ữ|ự|û",
+ y: "ý|ỳ|ỷ|ỹ|ỵ|ÿ",
+ };
+
+ const map: { [key: string]: string } = {
+ ...lmap,
+ };
+ Object.keys(lmap).forEach((k) => {
+ const K = k.toUpperCase();
+ map[K] = lmap[k].toUpperCase();
+ });
+
+ return map;
+};
+
+export const stripAccent = (s: string): string => {
+ let x = toString(s);
+
+ const updateS = (ai: string, key: string) => {
+ x = replaceAll(x, ai, key);
+ };
+
+ const map = getCharMap();
+ for (const key in map) {
+ if (hasProperty(map, key)) {
+ const a = map[key].split("|");
+ a.forEach((item) => {
+ return updateS(item, key);
+ });
+ }
+ }
+ return x;
+};
+
+export const slugify = (s: string, delimiter: string = "-"): string => {
+ return stripAccent(s)
+ .normalize("NFKD")
+ .replace(/[\u0300-\u036f]/g, "")
+ .trim()
+ .toLowerCase()
+ .replace(/[^a-z0-9 -]/g, "")
+ .replace(/\s+/g, delimiter)
+ .replace(new RegExp(`${delimiter}+`, "g"), delimiter);
+};