@radio-garden/ditojs-utils 2.85.0-0.5067ad799
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/package.json +43 -0
- package/src/__snapshots__/index.test.js.snap +88 -0
- package/src/array/flatten.js +14 -0
- package/src/array/flattten.test.js +29 -0
- package/src/array/index.js +2 -0
- package/src/array/shuffle.js +11 -0
- package/src/array/shuffle.test.js +25 -0
- package/src/base/base.js +118 -0
- package/src/base/base.test.js +590 -0
- package/src/base/index.js +1 -0
- package/src/class/index.js +1 -0
- package/src/class/mixin.js +29 -0
- package/src/class/mixin.test.js +70 -0
- package/src/dataPath/getEntriesAtDataPath.js +67 -0
- package/src/dataPath/getEntriesAtDataPath.test.js +204 -0
- package/src/dataPath/getValueAtDataPath.js +45 -0
- package/src/dataPath/getValueAtDataPath.test.js +140 -0
- package/src/dataPath/index.js +6 -0
- package/src/dataPath/normalizeDataPath.js +27 -0
- package/src/dataPath/normalizeDataPath.test.js +36 -0
- package/src/dataPath/parseDataPath.js +16 -0
- package/src/dataPath/parseDataPath.test.js +67 -0
- package/src/dataPath/setDataPathEntries.js +8 -0
- package/src/dataPath/setDataPathEntries.test.js +36 -0
- package/src/dataPath/setValueAtDataPath.js +13 -0
- package/src/dataPath/setValueAtDataPath.test.js +34 -0
- package/src/function/deprecate.js +9 -0
- package/src/function/index.js +4 -0
- package/src/function/toAsync.js +9 -0
- package/src/function/toAsync.test.js +31 -0
- package/src/function/toCallback.js +11 -0
- package/src/function/toCallback.test.js +29 -0
- package/src/function/toPromiseCallback.js +9 -0
- package/src/function/toPromiseCallback.test.js +24 -0
- package/src/html/escapeHtml.js +11 -0
- package/src/html/escapeHtml.test.js +19 -0
- package/src/html/index.js +2 -0
- package/src/html/stripHtml.js +23 -0
- package/src/html/stripHtml.test.js +37 -0
- package/src/index.js +10 -0
- package/src/index.test.js +7 -0
- package/src/object/asCallback.js +9 -0
- package/src/object/clone.js +75 -0
- package/src/object/clone.test.js +131 -0
- package/src/object/equals.js +36 -0
- package/src/object/equals.test.js +269 -0
- package/src/object/groupBy.js +15 -0
- package/src/object/groupBy.test.js +70 -0
- package/src/object/index.js +8 -0
- package/src/object/mapKeys.js +7 -0
- package/src/object/mapKeys.test.js +16 -0
- package/src/object/mapValues.js +9 -0
- package/src/object/mapValues.test.js +38 -0
- package/src/object/mergeDeeply.js +47 -0
- package/src/object/mergeDeeply.test.js +152 -0
- package/src/object/pick.js +11 -0
- package/src/object/pick.test.js +23 -0
- package/src/object/pickBy.js +11 -0
- package/src/object/pickBy.test.js +48 -0
- package/src/promise/index.js +2 -0
- package/src/promise/mapConcurrently.js +33 -0
- package/src/promise/mapSequentially.js +9 -0
- package/src/string/camelize.js +14 -0
- package/src/string/camelize.test.js +37 -0
- package/src/string/capitalize.js +7 -0
- package/src/string/capitalize.test.js +33 -0
- package/src/string/decamelize.js +27 -0
- package/src/string/decamelize.test.js +83 -0
- package/src/string/deindent.js +69 -0
- package/src/string/deindent.test.js +181 -0
- package/src/string/escapeRegexp.js +3 -0
- package/src/string/format.js +109 -0
- package/src/string/format.test.js +196 -0
- package/src/string/formatDate.js +13 -0
- package/src/string/formatDate.test.js +28 -0
- package/src/string/getCommonPrefix.js +35 -0
- package/src/string/getCommonPrefix.test.js +23 -0
- package/src/string/index.js +15 -0
- package/src/string/isAbsoluteUrl.js +7 -0
- package/src/string/isAbsoluteUrl.test.js +15 -0
- package/src/string/isCreditCard.js +21 -0
- package/src/string/isCreditCard.test.js +50 -0
- package/src/string/isDomain.js +9 -0
- package/src/string/isDomain.test.js +15 -0
- package/src/string/isEmail.js +6 -0
- package/src/string/isEmail.test.js +37 -0
- package/src/string/isHostname.js +8 -0
- package/src/string/isHostname.test.js +12 -0
- package/src/string/isUrl.js +23 -0
- package/src/string/isUrl.test.js +1595 -0
- package/src/string/labelize.js +17 -0
- package/src/string/labelize.test.js +39 -0
- package/src/timer/debounce.js +34 -0
- package/src/timer/debounce.test.js +101 -0
- package/src/timer/debounceAsync.js +60 -0
- package/src/timer/debounceAsync.test.js +143 -0
- package/src/timer/index.js +2 -0
- package/types/index.d.ts +939 -0
- package/types/tests/base.test-d.ts +172 -0
- package/types/tests/datapath.test-d.ts +75 -0
- package/types/tests/function.test-d.ts +137 -0
- package/types/tests/object.test-d.ts +190 -0
- package/types/tests/promise.test-d.ts +66 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { pickBy } from './pickBy.js'
|
|
2
|
+
|
|
3
|
+
describe('pickBy()', () => {
|
|
4
|
+
it('should work with a callback argument', () => {
|
|
5
|
+
const object = { a: 1, b: 2, c: 3, d: 4 }
|
|
6
|
+
const actual = pickBy(object, n => n === 1 || n === 3)
|
|
7
|
+
expect(actual).toStrictEqual({ a: 1, c: 3 })
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should support string property accessor for truthy values', () => {
|
|
11
|
+
const users = {
|
|
12
|
+
user1: { name: 'Alice', active: true },
|
|
13
|
+
user2: { name: 'Bob', active: false },
|
|
14
|
+
user3: { name: 'Charlie', active: true }
|
|
15
|
+
}
|
|
16
|
+
const actual = pickBy(users, 'active')
|
|
17
|
+
expect(actual).toStrictEqual({
|
|
18
|
+
user1: { name: 'Alice', active: true },
|
|
19
|
+
user3: { name: 'Charlie', active: true }
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should filter by numeric property with string accessor', () => {
|
|
24
|
+
const items = {
|
|
25
|
+
a: { score: 0 },
|
|
26
|
+
b: { score: 5 },
|
|
27
|
+
c: { score: 10 }
|
|
28
|
+
}
|
|
29
|
+
const actual = pickBy(items, 'score')
|
|
30
|
+
expect(actual).toStrictEqual({
|
|
31
|
+
b: { score: 5 },
|
|
32
|
+
c: { score: 10 }
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should filter by string property with string accessor', () => {
|
|
37
|
+
const data = {
|
|
38
|
+
a: { label: '' },
|
|
39
|
+
b: { label: 'test' },
|
|
40
|
+
c: { label: 'value' }
|
|
41
|
+
}
|
|
42
|
+
const actual = pickBy(data, 'label')
|
|
43
|
+
expect(actual).toStrictEqual({
|
|
44
|
+
b: { label: 'test' },
|
|
45
|
+
c: { label: 'value' }
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export async function mapConcurrently(
|
|
2
|
+
input,
|
|
3
|
+
callback,
|
|
4
|
+
{ concurrency = 0 } = {}
|
|
5
|
+
) {
|
|
6
|
+
const array = await input
|
|
7
|
+
if (!concurrency) {
|
|
8
|
+
return Promise.all(array.map(callback))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const promises = []
|
|
12
|
+
const results = []
|
|
13
|
+
let index = 0
|
|
14
|
+
|
|
15
|
+
function next() {
|
|
16
|
+
if (index < array.length) {
|
|
17
|
+
const i = index++
|
|
18
|
+
return Promise.resolve(callback(array[i], i, array)).then(result => {
|
|
19
|
+
results[i] = result
|
|
20
|
+
return next()
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
while (concurrency-- > 0) {
|
|
27
|
+
const promise = next()
|
|
28
|
+
if (!promise) break
|
|
29
|
+
promises.push(promise)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Promise.all(promises).then(() => results)
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function camelize(str, pascalCase = false) {
|
|
2
|
+
return str
|
|
3
|
+
? str
|
|
4
|
+
// Trim beginnings and ends
|
|
5
|
+
.replace(/^[-_\s]+|[-_\s]+$/g, '')
|
|
6
|
+
.replace(
|
|
7
|
+
/(^|[-_\s]+)(\w)([A-Z]*)([a-z]*)/g,
|
|
8
|
+
(all, sep, first, upperRest, lowerRest) =>
|
|
9
|
+
`${
|
|
10
|
+
pascalCase || sep ? first.toUpperCase() : first.toLowerCase()
|
|
11
|
+
}${upperRest.toLowerCase()}${lowerRest}`
|
|
12
|
+
)
|
|
13
|
+
: ''
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { camelize } from './camelize.js'
|
|
2
|
+
|
|
3
|
+
const strings = [
|
|
4
|
+
'foo bar', 'Foo bar', 'foo Bar', 'Foo Bar', 'FOO BAR', 'FooBar', 'fooBar'
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
describe('camelize()', () => {
|
|
8
|
+
describe.each([
|
|
9
|
+
[undefined, 'fooBar'],
|
|
10
|
+
[false, 'fooBar'],
|
|
11
|
+
[true, 'FooBar']
|
|
12
|
+
])(
|
|
13
|
+
'camelize(value, %o)',
|
|
14
|
+
(pascalCase, expected) => {
|
|
15
|
+
describe.each([
|
|
16
|
+
...strings,
|
|
17
|
+
'foo-bar',
|
|
18
|
+
'foo_bar',
|
|
19
|
+
'--foo-bar--',
|
|
20
|
+
'__foo_bar__'
|
|
21
|
+
])(
|
|
22
|
+
`camelize(%o, ${pascalCase})`,
|
|
23
|
+
value => {
|
|
24
|
+
it(`returns ${expected}`, () => {
|
|
25
|
+
expect(camelize(value, pascalCase)).toBe(expected)
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
it('should return an empty string if nothing can be processed', () => {
|
|
33
|
+
expect(camelize()).toBe('')
|
|
34
|
+
expect(camelize(null)).toBe('')
|
|
35
|
+
expect(camelize('')).toBe('')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { capitalize } from './capitalize.js'
|
|
2
|
+
|
|
3
|
+
describe('capitalize()', () => {
|
|
4
|
+
it('should capitalize hyphenated text', () => {
|
|
5
|
+
expect(capitalize('some-hyphenated-text')).toBe('Some-Hyphenated-Text')
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
it('should capitalize underscored text', () => {
|
|
9
|
+
expect(capitalize('some_underscored_text')).toBe('Some_Underscored_Text')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should capitalize a camel-cased text', () => {
|
|
13
|
+
expect(capitalize('someCamelCasedText')).toBe('SomeCamelCasedText')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should capitalize normal text', () => {
|
|
17
|
+
expect(capitalize('some normal text')).toBe('Some Normal Text')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should consider numbers as word-breaks', () => {
|
|
21
|
+
expect(capitalize('one234five')).toBe('One234Five')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should support umlauts', () => {
|
|
25
|
+
expect(capitalize('mäuse häuschen')).toBe('Mäuse Häuschen')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should return an empty string if nothing can be processed', () => {
|
|
29
|
+
expect(capitalize()).toBe('')
|
|
30
|
+
expect(capitalize(null)).toBe('')
|
|
31
|
+
expect(capitalize('')).toBe('')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function decamelize(str, sep = ' ') {
|
|
2
|
+
return str
|
|
3
|
+
? str
|
|
4
|
+
.replace(
|
|
5
|
+
// TODO: Once JavaScript supports Unicode property escapes in regexps,
|
|
6
|
+
// switch to better parsing that matches non-ASCII uppercase letters:
|
|
7
|
+
// /([\p{Ll}\d])(\p{Lu})/gu
|
|
8
|
+
// See: https://caniuse.com/?search=Unicode%20property%20escape
|
|
9
|
+
/(^\s+)|(\s+$)|([a-z\d])([A-Z])|(?<=\S)\s+(?=\S)/g,
|
|
10
|
+
(all, leading, trailing, lower, upper) =>
|
|
11
|
+
lower
|
|
12
|
+
? `${lower}${sep}${upper}`
|
|
13
|
+
: leading || trailing
|
|
14
|
+
? ''
|
|
15
|
+
: sep
|
|
16
|
+
)
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
: ''
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function hyphenate(str) {
|
|
22
|
+
return decamelize(str, '-')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function underscore(str) {
|
|
26
|
+
return decamelize(str, '_')
|
|
27
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { decamelize, hyphenate, underscore } from './decamelize.js'
|
|
2
|
+
|
|
3
|
+
const strings = [
|
|
4
|
+
'foo bar', 'Foo bar', 'foo Bar', 'Foo Bar', 'FOO BAR', 'FooBar', 'fooBar'
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
describe('decamelize()', () => {
|
|
8
|
+
const expected = 'foo bar'
|
|
9
|
+
describe.each(strings)(
|
|
10
|
+
'decamelize(%o)',
|
|
11
|
+
value => {
|
|
12
|
+
it(`returns ${expected}`, () => {
|
|
13
|
+
expect(decamelize(value)).toBe(expected)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
it('should return an empty string if nothing can be processed', () => {
|
|
19
|
+
expect(decamelize()).toBe('')
|
|
20
|
+
expect(decamelize(null)).toBe('')
|
|
21
|
+
expect(decamelize('')).toBe('')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('hyphenate()', () => {
|
|
26
|
+
const expected = 'foo-bar'
|
|
27
|
+
describe.each(strings)(
|
|
28
|
+
'hyphenate(%o)',
|
|
29
|
+
value => {
|
|
30
|
+
it(`returns ${expected}`, () => {
|
|
31
|
+
expect(hyphenate(value)).toBe(expected)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
it('should hyphenate single space between words', () => {
|
|
37
|
+
expect(hyphenate('Foo Bar')).toBe('foo-bar')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should hyphenate multiple spaces', () => {
|
|
41
|
+
expect(hyphenate('Foo Bar')).toBe('foo-bar')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should trim leading whitespace', () => {
|
|
45
|
+
expect(hyphenate(' Foo Bar')).toBe('foo-bar')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should trim trailing whitespace', () => {
|
|
49
|
+
expect(hyphenate('Foo Bar ')).toBe('foo-bar')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should hyphenate special characters correctly', () => {
|
|
53
|
+
expect(hyphenate('Foo & Bar')).toBe('foo-&-bar')
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('underscore()', () => {
|
|
58
|
+
const expected = 'foo_bar'
|
|
59
|
+
describe.each(strings)(
|
|
60
|
+
'underscore(%o)',
|
|
61
|
+
value => {
|
|
62
|
+
it(`returns ${expected}`, () => {
|
|
63
|
+
expect(underscore(value)).toBe(expected)
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
it('should underscore multiple spaces', () => {
|
|
69
|
+
expect(underscore('Foo Bar')).toBe('foo_bar')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should trim leading whitespace', () => {
|
|
73
|
+
expect(underscore(' Foo Bar')).toBe('foo_bar')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should trim trailing whitespace', () => {
|
|
77
|
+
expect(underscore('Foo Bar ')).toBe('foo_bar')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should underscore special characters correctly', () => {
|
|
81
|
+
expect(underscore('Foo & Bar')).toBe('foo_&_bar')
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { asArray } from '../base/index.js'
|
|
2
|
+
|
|
3
|
+
// ES6 string tag that strips indentation from multi-line strings
|
|
4
|
+
// Based on, and further improved from https://github.com/dmnd/dedent
|
|
5
|
+
export function deindent(strings, ...values) {
|
|
6
|
+
// Small helper to determine the actual line-break used in the strings, based
|
|
7
|
+
// on the line-breaks encountered.
|
|
8
|
+
const getLineBreak = line => line.match(/^(\n|\r\n|\r)/)?.[1] || '\n'
|
|
9
|
+
|
|
10
|
+
strings = asArray(strings)
|
|
11
|
+
// First, perform interpolation
|
|
12
|
+
let parts = []
|
|
13
|
+
for (let i = 0; i < strings.length; i++) {
|
|
14
|
+
parts.push(
|
|
15
|
+
strings[i]
|
|
16
|
+
// Join lines when there is a line-break suppressed by a preceding '\\'
|
|
17
|
+
.replace(/\\\n\s*/g, '')
|
|
18
|
+
// Handle escaped back-ticks
|
|
19
|
+
.replace(/\\`/g, '`')
|
|
20
|
+
)
|
|
21
|
+
if (i < values.length) {
|
|
22
|
+
// See if the value itself contains multiple lines, and if so, indent
|
|
23
|
+
// each of them by the whitespace that precedes it except the first.
|
|
24
|
+
const value = values[i].toString()
|
|
25
|
+
const lines = value.split(/\n|\r\n|\r/)
|
|
26
|
+
if (lines.length > 1) {
|
|
27
|
+
// Determine the indent by finding the immediately preceding white-space
|
|
28
|
+
// up to the previous line-break or the beginning of the string.
|
|
29
|
+
// (?:^[\n\r]*|[\n\r]) # Start at either the beginning or the previous
|
|
30
|
+
// # line break.
|
|
31
|
+
// (?:[\n\r]*) # The line break
|
|
32
|
+
// (\s+) # Collect the indenting white-space...
|
|
33
|
+
// (?:[^\n\r]*)$ # ...up to the end or the next word, but in last
|
|
34
|
+
// # line, by making sure no line breaks follow
|
|
35
|
+
// # until the end.
|
|
36
|
+
const str = parts.join('')
|
|
37
|
+
const match = str.match(
|
|
38
|
+
/((?:^[\n\r]*|[\n\r])(?:[\n\r]*))(\s+)(?:[^\n\r]*)$/
|
|
39
|
+
)
|
|
40
|
+
const [, lineBreaks = '', indent = ''] = match || []
|
|
41
|
+
parts = [str]
|
|
42
|
+
parts.push(lines.join(`${getLineBreak(lineBreaks)}${indent}`))
|
|
43
|
+
} else {
|
|
44
|
+
parts.push(value)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const str = parts.join('')
|
|
49
|
+
const lines = str.split(/\n|\r\n|\r/)
|
|
50
|
+
// Remove the first line-break at the beginning of the result, to allow this:
|
|
51
|
+
// const value = `
|
|
52
|
+
// content...
|
|
53
|
+
// ` // line break at the end remains
|
|
54
|
+
let offset = 0
|
|
55
|
+
if (!lines[0]) {
|
|
56
|
+
lines.shift()
|
|
57
|
+
offset++
|
|
58
|
+
}
|
|
59
|
+
let indent = Infinity
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const match = line.match(/^(\s*)\S+/)
|
|
62
|
+
if (match) {
|
|
63
|
+
indent = Math.min(indent, match[1].length)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const index = lines[0].length + offset
|
|
67
|
+
const lineBreak = getLineBreak(str.slice(index, index + 2))
|
|
68
|
+
return lines.map(line => line.slice(indent)).join(lineBreak)
|
|
69
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { deindent } from './deindent.js'
|
|
2
|
+
|
|
3
|
+
describe('deindent()', () => {
|
|
4
|
+
it('should deindent indented multi-line strings', () => {
|
|
5
|
+
expect(
|
|
6
|
+
deindent`
|
|
7
|
+
some
|
|
8
|
+
indented
|
|
9
|
+
text`
|
|
10
|
+
).toBe(
|
|
11
|
+
`some
|
|
12
|
+
indented
|
|
13
|
+
text`
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should preserve the last line-break', () => {
|
|
18
|
+
expect(
|
|
19
|
+
deindent`
|
|
20
|
+
some
|
|
21
|
+
indented
|
|
22
|
+
text
|
|
23
|
+
`
|
|
24
|
+
).toBe(
|
|
25
|
+
`some
|
|
26
|
+
indented
|
|
27
|
+
text
|
|
28
|
+
`
|
|
29
|
+
)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should only swallow the first line-break, not those following', () => {
|
|
33
|
+
expect(
|
|
34
|
+
deindent`
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
some
|
|
38
|
+
indented
|
|
39
|
+
text
|
|
40
|
+
`
|
|
41
|
+
).toBe(
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
some
|
|
45
|
+
indented
|
|
46
|
+
text
|
|
47
|
+
`
|
|
48
|
+
)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should not swallow the empty line-breaks at the end', () => {
|
|
52
|
+
expect(
|
|
53
|
+
deindent`
|
|
54
|
+
some
|
|
55
|
+
indented
|
|
56
|
+
text
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
`
|
|
60
|
+
).toBe(
|
|
61
|
+
`some
|
|
62
|
+
indented
|
|
63
|
+
text
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
`
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should handle multi-line strings without first or last line', () => {
|
|
71
|
+
expect(
|
|
72
|
+
deindent`some
|
|
73
|
+
indented
|
|
74
|
+
text
|
|
75
|
+
some
|
|
76
|
+
indented
|
|
77
|
+
text`
|
|
78
|
+
).toBe(
|
|
79
|
+
`some
|
|
80
|
+
indented
|
|
81
|
+
text
|
|
82
|
+
some
|
|
83
|
+
indented
|
|
84
|
+
text`
|
|
85
|
+
)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should handle multi-line strings without first or last line', () => {
|
|
89
|
+
expect(
|
|
90
|
+
deindent` some
|
|
91
|
+
indented
|
|
92
|
+
text`
|
|
93
|
+
).toBe(
|
|
94
|
+
`some
|
|
95
|
+
indented
|
|
96
|
+
text`
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should correctly indent nested single-line string values', () => {
|
|
101
|
+
const singleLineText = 'single-line text'
|
|
102
|
+
expect(
|
|
103
|
+
deindent`
|
|
104
|
+
some
|
|
105
|
+
indented
|
|
106
|
+
${singleLineText}
|
|
107
|
+
`
|
|
108
|
+
).toBe(
|
|
109
|
+
`some
|
|
110
|
+
indented
|
|
111
|
+
single-line text
|
|
112
|
+
`
|
|
113
|
+
)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('should correctly indent unnested multi-line string values', () => {
|
|
117
|
+
const multiLineText = 'multi-\nline\ntext'
|
|
118
|
+
expect(
|
|
119
|
+
deindent`
|
|
120
|
+
some
|
|
121
|
+
indented
|
|
122
|
+
${multiLineText}
|
|
123
|
+
`
|
|
124
|
+
).toBe(
|
|
125
|
+
`some
|
|
126
|
+
indented
|
|
127
|
+
multi-
|
|
128
|
+
line
|
|
129
|
+
text
|
|
130
|
+
`
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should correctly indent nested multi-line string values', () => {
|
|
135
|
+
const multiLineText = 'multi-\nline\ntext'
|
|
136
|
+
expect(
|
|
137
|
+
deindent`
|
|
138
|
+
some
|
|
139
|
+
indented
|
|
140
|
+
${multiLineText}
|
|
141
|
+
`
|
|
142
|
+
).toBe(
|
|
143
|
+
`some
|
|
144
|
+
indented
|
|
145
|
+
multi-
|
|
146
|
+
line
|
|
147
|
+
text
|
|
148
|
+
`
|
|
149
|
+
)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('should maintain the indent of prefixed multi-line string values', () => {
|
|
153
|
+
const multiLineText = 'multi-\nline\ntext'
|
|
154
|
+
expect(
|
|
155
|
+
deindent`
|
|
156
|
+
some
|
|
157
|
+
indented
|
|
158
|
+
content: ${multiLineText}
|
|
159
|
+
`
|
|
160
|
+
).toBe(
|
|
161
|
+
`some
|
|
162
|
+
indented
|
|
163
|
+
content: multi-
|
|
164
|
+
line
|
|
165
|
+
text
|
|
166
|
+
`
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should not deindent if the first line starts with text', () => {
|
|
171
|
+
expect(
|
|
172
|
+
deindent`some
|
|
173
|
+
indented
|
|
174
|
+
text`
|
|
175
|
+
).toBe(
|
|
176
|
+
`some
|
|
177
|
+
indented
|
|
178
|
+
text`
|
|
179
|
+
)
|
|
180
|
+
})
|
|
181
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { isNumber, isDate, isObject } from '../base/index.js'
|
|
2
|
+
|
|
3
|
+
export const defaultFormats = {
|
|
4
|
+
number: {
|
|
5
|
+
style: 'decimal'
|
|
6
|
+
},
|
|
7
|
+
date: {
|
|
8
|
+
day: 'numeric',
|
|
9
|
+
month: 'long',
|
|
10
|
+
year: 'numeric'
|
|
11
|
+
},
|
|
12
|
+
time: {
|
|
13
|
+
hour: '2-digit',
|
|
14
|
+
minute: '2-digit',
|
|
15
|
+
second: '2-digit'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function format(value, {
|
|
20
|
+
locale = 'en-US',
|
|
21
|
+
number,
|
|
22
|
+
date,
|
|
23
|
+
time,
|
|
24
|
+
defaults = defaultFormats
|
|
25
|
+
} = {}) {
|
|
26
|
+
const formats = { number, date, time }
|
|
27
|
+
const defaultValue = number || date || time ? undefined : true
|
|
28
|
+
const getOptions = name => {
|
|
29
|
+
const defaultOption = defaults[name]
|
|
30
|
+
const option = formats[name] ?? defaultValue
|
|
31
|
+
return option === true
|
|
32
|
+
? defaultOption
|
|
33
|
+
: isObject(option)
|
|
34
|
+
? Object.entries({
|
|
35
|
+
...defaultOption,
|
|
36
|
+
...option
|
|
37
|
+
}).reduce((opt, [key, value]) => {
|
|
38
|
+
if (value !== false) {
|
|
39
|
+
opt[key] = value
|
|
40
|
+
}
|
|
41
|
+
return opt
|
|
42
|
+
}, {})
|
|
43
|
+
: {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (value != null) {
|
|
47
|
+
if (number) {
|
|
48
|
+
value = isNumber(value) ? value : parseFloat(value)
|
|
49
|
+
} else if (date || time) {
|
|
50
|
+
value = isDate(value) ? value : new Date(value)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let options
|
|
55
|
+
if (isNumber(value)) {
|
|
56
|
+
if (number === false) {
|
|
57
|
+
return ''
|
|
58
|
+
} else {
|
|
59
|
+
options = getOptions('number')
|
|
60
|
+
if (options.format) {
|
|
61
|
+
// Support custom post-formatting of number formats,
|
|
62
|
+
// e.g. to replace decimals and such:
|
|
63
|
+
const parts = new Intl.NumberFormat(locale, options)
|
|
64
|
+
.formatToParts(value)
|
|
65
|
+
return parts
|
|
66
|
+
.map(
|
|
67
|
+
({ type, value }) => options.format(value, type, options) ?? value
|
|
68
|
+
)
|
|
69
|
+
.join('')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else if (isDate(value)) {
|
|
73
|
+
if (date === false && time === false) {
|
|
74
|
+
return ''
|
|
75
|
+
} else {
|
|
76
|
+
// Flatten the separate date / time options down to one
|
|
77
|
+
const opts = {
|
|
78
|
+
date: getOptions('date'),
|
|
79
|
+
time: getOptions('time')
|
|
80
|
+
}
|
|
81
|
+
options = {
|
|
82
|
+
...opts.date,
|
|
83
|
+
...opts.time
|
|
84
|
+
}
|
|
85
|
+
if (options.format) {
|
|
86
|
+
// Support custom post-formatting of both time and date formats,
|
|
87
|
+
// e.g. to replace separators and such:
|
|
88
|
+
const parts = new Intl.DateTimeFormat(locale, options)
|
|
89
|
+
.formatToParts(value)
|
|
90
|
+
let modeOpts = null
|
|
91
|
+
return parts
|
|
92
|
+
.map(({ type, value }) => {
|
|
93
|
+
if (type !== 'literal') {
|
|
94
|
+
// Switch mode between date/time to pick the right format method:
|
|
95
|
+
const mode = ['weekday', 'day', 'month', 'year'].includes(type)
|
|
96
|
+
? 'date'
|
|
97
|
+
: 'time'
|
|
98
|
+
modeOpts = opts[mode]
|
|
99
|
+
}
|
|
100
|
+
return modeOpts?.format?.(value, type, modeOpts) ?? value
|
|
101
|
+
})
|
|
102
|
+
.join('')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return value != null
|
|
107
|
+
? value.toLocaleString(locale, options)
|
|
108
|
+
: value
|
|
109
|
+
}
|