@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.
Files changed (104) hide show
  1. package/README.md +6 -0
  2. package/package.json +43 -0
  3. package/src/__snapshots__/index.test.js.snap +88 -0
  4. package/src/array/flatten.js +14 -0
  5. package/src/array/flattten.test.js +29 -0
  6. package/src/array/index.js +2 -0
  7. package/src/array/shuffle.js +11 -0
  8. package/src/array/shuffle.test.js +25 -0
  9. package/src/base/base.js +118 -0
  10. package/src/base/base.test.js +590 -0
  11. package/src/base/index.js +1 -0
  12. package/src/class/index.js +1 -0
  13. package/src/class/mixin.js +29 -0
  14. package/src/class/mixin.test.js +70 -0
  15. package/src/dataPath/getEntriesAtDataPath.js +67 -0
  16. package/src/dataPath/getEntriesAtDataPath.test.js +204 -0
  17. package/src/dataPath/getValueAtDataPath.js +45 -0
  18. package/src/dataPath/getValueAtDataPath.test.js +140 -0
  19. package/src/dataPath/index.js +6 -0
  20. package/src/dataPath/normalizeDataPath.js +27 -0
  21. package/src/dataPath/normalizeDataPath.test.js +36 -0
  22. package/src/dataPath/parseDataPath.js +16 -0
  23. package/src/dataPath/parseDataPath.test.js +67 -0
  24. package/src/dataPath/setDataPathEntries.js +8 -0
  25. package/src/dataPath/setDataPathEntries.test.js +36 -0
  26. package/src/dataPath/setValueAtDataPath.js +13 -0
  27. package/src/dataPath/setValueAtDataPath.test.js +34 -0
  28. package/src/function/deprecate.js +9 -0
  29. package/src/function/index.js +4 -0
  30. package/src/function/toAsync.js +9 -0
  31. package/src/function/toAsync.test.js +31 -0
  32. package/src/function/toCallback.js +11 -0
  33. package/src/function/toCallback.test.js +29 -0
  34. package/src/function/toPromiseCallback.js +9 -0
  35. package/src/function/toPromiseCallback.test.js +24 -0
  36. package/src/html/escapeHtml.js +11 -0
  37. package/src/html/escapeHtml.test.js +19 -0
  38. package/src/html/index.js +2 -0
  39. package/src/html/stripHtml.js +23 -0
  40. package/src/html/stripHtml.test.js +37 -0
  41. package/src/index.js +10 -0
  42. package/src/index.test.js +7 -0
  43. package/src/object/asCallback.js +9 -0
  44. package/src/object/clone.js +75 -0
  45. package/src/object/clone.test.js +131 -0
  46. package/src/object/equals.js +36 -0
  47. package/src/object/equals.test.js +269 -0
  48. package/src/object/groupBy.js +15 -0
  49. package/src/object/groupBy.test.js +70 -0
  50. package/src/object/index.js +8 -0
  51. package/src/object/mapKeys.js +7 -0
  52. package/src/object/mapKeys.test.js +16 -0
  53. package/src/object/mapValues.js +9 -0
  54. package/src/object/mapValues.test.js +38 -0
  55. package/src/object/mergeDeeply.js +47 -0
  56. package/src/object/mergeDeeply.test.js +152 -0
  57. package/src/object/pick.js +11 -0
  58. package/src/object/pick.test.js +23 -0
  59. package/src/object/pickBy.js +11 -0
  60. package/src/object/pickBy.test.js +48 -0
  61. package/src/promise/index.js +2 -0
  62. package/src/promise/mapConcurrently.js +33 -0
  63. package/src/promise/mapSequentially.js +9 -0
  64. package/src/string/camelize.js +14 -0
  65. package/src/string/camelize.test.js +37 -0
  66. package/src/string/capitalize.js +7 -0
  67. package/src/string/capitalize.test.js +33 -0
  68. package/src/string/decamelize.js +27 -0
  69. package/src/string/decamelize.test.js +83 -0
  70. package/src/string/deindent.js +69 -0
  71. package/src/string/deindent.test.js +181 -0
  72. package/src/string/escapeRegexp.js +3 -0
  73. package/src/string/format.js +109 -0
  74. package/src/string/format.test.js +196 -0
  75. package/src/string/formatDate.js +13 -0
  76. package/src/string/formatDate.test.js +28 -0
  77. package/src/string/getCommonPrefix.js +35 -0
  78. package/src/string/getCommonPrefix.test.js +23 -0
  79. package/src/string/index.js +15 -0
  80. package/src/string/isAbsoluteUrl.js +7 -0
  81. package/src/string/isAbsoluteUrl.test.js +15 -0
  82. package/src/string/isCreditCard.js +21 -0
  83. package/src/string/isCreditCard.test.js +50 -0
  84. package/src/string/isDomain.js +9 -0
  85. package/src/string/isDomain.test.js +15 -0
  86. package/src/string/isEmail.js +6 -0
  87. package/src/string/isEmail.test.js +37 -0
  88. package/src/string/isHostname.js +8 -0
  89. package/src/string/isHostname.test.js +12 -0
  90. package/src/string/isUrl.js +23 -0
  91. package/src/string/isUrl.test.js +1595 -0
  92. package/src/string/labelize.js +17 -0
  93. package/src/string/labelize.test.js +39 -0
  94. package/src/timer/debounce.js +34 -0
  95. package/src/timer/debounce.test.js +101 -0
  96. package/src/timer/debounceAsync.js +60 -0
  97. package/src/timer/debounceAsync.test.js +143 -0
  98. package/src/timer/index.js +2 -0
  99. package/types/index.d.ts +939 -0
  100. package/types/tests/base.test-d.ts +172 -0
  101. package/types/tests/datapath.test-d.ts +75 -0
  102. package/types/tests/function.test-d.ts +137 -0
  103. package/types/tests/object.test-d.ts +190 -0
  104. package/types/tests/promise.test-d.ts +66 -0
@@ -0,0 +1,196 @@
1
+ import { format } from './format.js'
2
+
3
+ describe('format()', () => {
4
+ const integer = 123456789
5
+ const float = 123456.789
6
+ const date = new Date(2012, 5, 9, 22, 45, 30)
7
+
8
+ it('should return undefined when no value is given', () => {
9
+ expect(format()).toBe(undefined)
10
+ })
11
+
12
+ it('should return null when value is null', () => {
13
+ expect(format(null)).toBe(null)
14
+ expect(format(null, { number: true })).toBe(null)
15
+ expect(format(null, { date: true })).toBe(null)
16
+ expect(format(null, { time: true })).toBe(null)
17
+ })
18
+
19
+ it('should use the en-US locale by default', () => {
20
+ expect(format(integer)).toBe('123,456,789')
21
+ expect(format(float)).toBe('123,456.789')
22
+ expect(format(date)).toMatch(/^June 9, 2012 at 10:45:30\sPM$/)
23
+ })
24
+
25
+ it('should format numbers with different locale and default options', () => {
26
+ expect(format(integer, { locale: 'de-DE' })).toBe('123.456.789')
27
+ expect(format(float, { locale: 'de-DE' })).toBe('123.456,789')
28
+ })
29
+
30
+ it('should format dates with different locale and default options', () => {
31
+ expect(format(date, { locale: 'de-DE' })).toBe('9. Juni 2012 um 22:45:30')
32
+ })
33
+
34
+ it('should format string as numbers if told so', () => {
35
+ expect(
36
+ format('123456789', { locale: 'de-DE', number: true })
37
+ ).toBe('123.456.789')
38
+ })
39
+
40
+ it('should format numbers as dates if told so', () => {
41
+ expect(
42
+ format(integer, { locale: 'de-DE', date: false, time: true })
43
+ ).toBe('11:17:36')
44
+ expect(
45
+ format(integer, { locale: 'de-DE', date: true, time: false })
46
+ ).toBe('2. Januar 1970')
47
+ expect(
48
+ format(integer, { locale: 'de-DE', date: true, time: true })
49
+ ).toBe('2. Januar 1970 um 11:17:36')
50
+ })
51
+
52
+ it('should omit time when `options.date = true`', () => {
53
+ expect(
54
+ format(date, { locale: 'de-DE', date: true })
55
+ ).toBe('9. Juni 2012')
56
+ })
57
+
58
+ it('should omit date when `options.time = true`', () => {
59
+ expect(
60
+ format(date, { locale: 'de-DE', time: true })
61
+ ).toBe('22:45:30')
62
+ })
63
+
64
+ it('should omit time when `options.time = false`', () => {
65
+ expect(
66
+ format(date, { locale: 'de-DE', time: false })
67
+ ).toBe('9. Juni 2012')
68
+ })
69
+
70
+ it('should omit date when `options.date = false`', () => {
71
+ expect(
72
+ format(date, { locale: 'de-DE', date: false })
73
+ ).toBe('22:45:30')
74
+ })
75
+
76
+ it(`should return an empty string when \`options.date = false\` and \`options.time = false\``, () => {
77
+ expect(
78
+ format(date, { locale: 'de-DE', date: false, time: false })
79
+ ).toBe('')
80
+ })
81
+
82
+ it('should support fine-grained control of `options.number`', () => {
83
+ expect(
84
+ format(integer, {
85
+ locale: 'de-DE',
86
+ number: {
87
+ style: 'currency',
88
+ currency: 'EUR'
89
+ }
90
+ })
91
+ ).toMatch(/^123\.456\.789,00\s€$/)
92
+ expect(
93
+ format(integer, {
94
+ locale: 'de-CH',
95
+ number: {
96
+ style: 'currency',
97
+ currency: 'CHF'
98
+ }
99
+ })
100
+ ).toMatch(/^CHF\s123’456’789\.00$/)
101
+ expect(
102
+ format(float, {
103
+ locale: 'de-DE',
104
+ number: {
105
+ style: 'currency',
106
+ currency: 'EUR'
107
+ }
108
+ })
109
+ ).toMatch(/^123\.456,79\s€$/)
110
+ expect(
111
+ format(float, {
112
+ locale: 'de-CH',
113
+ number: {
114
+ style: 'currency',
115
+ currency: 'CHF'
116
+ }
117
+ })
118
+ ).toMatch(/^CHF\s123’456\.79$/)
119
+ })
120
+
121
+ it('should support fine-grained control of `options.date`', () => {
122
+ expect(
123
+ format(date, {
124
+ locale: 'de-DE',
125
+ date: {
126
+ month: 'short',
127
+ day: false
128
+ },
129
+ time: false
130
+ })
131
+ ).toBe('Juni 2012')
132
+ expect(
133
+ format(date, {
134
+ locale: 'de-DE',
135
+ date: {
136
+ year: false,
137
+ day: '2-digit'
138
+ },
139
+ time: false
140
+ })
141
+ ).toBe('09. Juni')
142
+ })
143
+
144
+ it('should support custom `options.number.format()` overrides', () => {
145
+ expect(
146
+ format(integer, {
147
+ locale: 'en-GB',
148
+ number: {
149
+ style: 'currency',
150
+ currency: 'GBP',
151
+ format: (value, type) =>
152
+ type === 'group'
153
+ ? `’`
154
+ : type === 'decimal'
155
+ ? ','
156
+ : undefined
157
+ }
158
+ })
159
+ ).toBe(`£123’456’789,00`)
160
+ })
161
+
162
+ it(`should support custom \`options.date.format()\` and \`options.time.format()\` overrides`, () => {
163
+ expect(
164
+ format(date, {
165
+ locale: 'en-GB',
166
+ date: {
167
+ day: 'numeric',
168
+ month: 'numeric',
169
+ year: 'numeric',
170
+ format: (value, type, options) =>
171
+ type === 'literal' && options.month === 'numeric'
172
+ ? value.replace(/\//, '.').replace(/,/, '')
173
+ : undefined
174
+ },
175
+ time: {
176
+ hour: '2-digit',
177
+ minute: '2-digit',
178
+ second: '2-digit',
179
+ format: value => value.replace(/:/, '_')
180
+ }
181
+ })
182
+ ).toBe('09.06.2012 22_45_30')
183
+ })
184
+
185
+ it('should handle string values', () => {
186
+ expect(
187
+ format('2016-05-24T15:54:14.876Z', { date: true })
188
+ ).toBe('May 24, 2016')
189
+ expect(
190
+ format('2016-05-24T15:54:14.876Z', { date: true, time: true })
191
+ ).toMatch(/^May 24, 2016 at 05:54:14\sPM$/)
192
+ expect(format('123456789', { number: true })).toBe('123,456,789')
193
+ expect(format('123456.789', { number: true })).toBe('123,456.789')
194
+ expect(format('Hello World')).toBe('Hello World')
195
+ })
196
+ })
@@ -0,0 +1,13 @@
1
+ import { isDate } from '../base/index.js'
2
+ import { format } from './format.js'
3
+
4
+ export function formatDate(value, {
5
+ locale = 'en-US',
6
+ date = true,
7
+ time = true
8
+ } = {}) {
9
+ return format(
10
+ isDate(value) || value == null ? value : new Date(value),
11
+ { locale, date, time }
12
+ )
13
+ }
@@ -0,0 +1,28 @@
1
+ import { formatDate } from './formatDate.js'
2
+
3
+ describe('formatDate()', () => {
4
+ const date = new Date(2012, 5, 9, 22, 45, 30)
5
+
6
+ it('should not format null', () => {
7
+ expect(formatDate(null, { locale: 'de-DE' })).toBe(null)
8
+ })
9
+
10
+ it('should use the en-US locale by default', () => {
11
+ expect(formatDate(date)).toMatch(/^June 9, 2012 at 10:45:30\sPM$/)
12
+ expect(formatDate(date, {})).toMatch(/^June 9, 2012 at 10:45:30\sPM$/)
13
+ })
14
+
15
+ it('should format dates with different locale and default options', () => {
16
+ expect(formatDate(date, { locale: 'de-DE' }))
17
+ .toBe('9. Juni 2012 um 22:45:30')
18
+ })
19
+
20
+ it('should format numbers or strings as dates', () => {
21
+ expect(formatDate(0, { locale: 'de-DE' }))
22
+ .toBe('1. Januar 1970 um 01:00:00')
23
+ expect(formatDate(1000000000000, { locale: 'de-DE' }))
24
+ .toBe('9. September 2001 um 03:46:40')
25
+ expect(formatDate('04 Dec 1995 00:12:00 GMT', { locale: 'de-DE' }))
26
+ .toBe('4. Dezember 1995 um 01:12:00')
27
+ })
28
+ })
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Computes the index of the end of the longest common prefix from an array of
3
+ * strings.
4
+ * @param {string[]} strings
5
+ * @returns {number}
6
+ */
7
+ export function getCommonOffset(...strings) {
8
+ if (strings.length === 0) return 0
9
+ let offset = 0
10
+ const string1 = strings[0]
11
+ const length1 = string1.length
12
+ for (let i = 0; i < length1; i++) {
13
+ const char = string1.charCodeAt(i)
14
+ for (let j = 1; j < strings.length; j++) {
15
+ const string2 = strings[j]
16
+ if (i >= string2.length || char !== string2.charCodeAt(i)) {
17
+ return offset
18
+ }
19
+ }
20
+ offset++
21
+ }
22
+ return offset
23
+ }
24
+
25
+ /**
26
+ * Computes the longest common prefix from an array of strings.
27
+ *
28
+ * @param {string[]} strings
29
+ * @returns {string}
30
+ */
31
+ export function getCommonPrefix(...strings) {
32
+ return strings.length === 0
33
+ ? ''
34
+ : strings[0].slice(0, getCommonOffset(...strings))
35
+ }
@@ -0,0 +1,23 @@
1
+ import { getCommonPrefix, getCommonOffset } from './getCommonPrefix.js'
2
+
3
+ describe('getCommonPrefix()', () => {
4
+ it('should return the longest common prefix', () => {
5
+ expect(getCommonPrefix('interstate', 'intersection')).toBe('inters')
6
+ expect(getCommonPrefix('intersection', 'interstate')).toBe('inters')
7
+ })
8
+ it('should compare case-sensitively', () => {
9
+ expect(getCommonPrefix('interstate', 'Intersection')).toBe('')
10
+ expect(getCommonPrefix('InterState', 'Intersection')).toBe('Inter')
11
+ })
12
+ })
13
+
14
+ describe('getCommonOffset()', () => {
15
+ it('should return the longest common prefix', () => {
16
+ expect(getCommonOffset('interstate', 'intersection')).toBe(6)
17
+ expect(getCommonOffset('intersection', 'interstate')).toBe(6)
18
+ })
19
+ it('should compare case-sensitively', () => {
20
+ expect(getCommonOffset('interstate', 'Intersection')).toBe(0)
21
+ expect(getCommonOffset('InterState', 'Intersection')).toBe(5)
22
+ })
23
+ })
@@ -0,0 +1,15 @@
1
+ export * from './camelize.js'
2
+ export * from './capitalize.js'
3
+ export * from './decamelize.js'
4
+ export * from './deindent.js'
5
+ export * from './escapeRegexp.js'
6
+ export * from './format.js'
7
+ export * from './formatDate.js'
8
+ export * from './getCommonPrefix.js'
9
+ export * from './isAbsoluteUrl.js'
10
+ export * from './isCreditCard.js'
11
+ export * from './isDomain.js'
12
+ export * from './isEmail.js'
13
+ export * from './isHostname.js'
14
+ export * from './isUrl.js'
15
+ export * from './labelize.js'
@@ -0,0 +1,7 @@
1
+ export function isAbsoluteUrl(str) {
2
+ // A URL is considered absolute if it begins with "<scheme>:" or "//"
3
+ // (protocol-relative URL). RFC 3986 defines scheme name as a sequence of
4
+ // characters beginning with a letter and followed by any combination of
5
+ // letters, digits, plus, period, or hyphen.
6
+ return !!(str && /^([a-z][a-z\d+-.]*:)|^\/\//i.test(str))
7
+ }
@@ -0,0 +1,15 @@
1
+ import { isAbsoluteUrl } from './isAbsoluteUrl.js'
2
+
3
+ describe('isAbsoluteUrl()', () => {
4
+ it('should return true for absolute URLs starting with schemas', () => {
5
+ expect(isAbsoluteUrl('http://lineto.com')).toBe(true)
6
+ expect(isAbsoluteUrl('https://lineto.com')).toBe(true)
7
+ expect(isAbsoluteUrl('ftp://lineto.com')).toBe(true)
8
+ expect(isAbsoluteUrl('file:///Users/lineto')).toBe(true)
9
+ expect(isAbsoluteUrl('mailto:user@lineto.com')).toBe(true)
10
+ expect(isAbsoluteUrl('//lineto.com')).toBe(true)
11
+ expect(isAbsoluteUrl('/static/index.html')).toBe(false)
12
+ expect(isAbsoluteUrl('../index.html')).toBe(false)
13
+ expect(isAbsoluteUrl('../static//index.html')).toBe(false)
14
+ })
15
+ })
@@ -0,0 +1,21 @@
1
+ const creditCardRegExp =
2
+ /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})|62[0-9]{14}$/
3
+
4
+ export function isCreditCard(str) {
5
+ str = str && str.replace(/[\s-]+/g, '')
6
+ if (!str || str.length > 16 || !creditCardRegExp.test(str)) {
7
+ return false
8
+ }
9
+ // The Luhn-Algorithm:
10
+ let check = 0
11
+ let even = false
12
+ for (let i = str.length - 1; i >= 0; i--) {
13
+ let digit = +str[i]
14
+ if (even && (digit *= 2) > 9) {
15
+ digit -= 9
16
+ }
17
+ check += digit
18
+ even = !even
19
+ }
20
+ return (check % 10) === 0
21
+ }
@@ -0,0 +1,50 @@
1
+ import { isCreditCard } from './isCreditCard.js'
2
+
3
+ describe('isCreditCard()', () => {
4
+ describe.each([
5
+ '375556917985515',
6
+ '36050234196908',
7
+ '4716461583322103',
8
+ '4716-2210-5188-5662',
9
+ '4929 7226 5379 7141',
10
+ '5398228707871527',
11
+ '6283875070985593',
12
+ '6263892624162870',
13
+ '6234917882863855',
14
+ '6234698580215388',
15
+ '6226050967750613',
16
+ '6246281879460688',
17
+ '2222155765072228',
18
+ '2225855203075256',
19
+ '2720428011723762',
20
+ '2718760626256570'
21
+ ])(
22
+ 'isCreditCard(%o)',
23
+ str => {
24
+ it('returns true', () => {
25
+ expect(isCreditCard(str)).toBe(true)
26
+ })
27
+ }
28
+ )
29
+ describe.each([
30
+ false,
31
+ 'foo',
32
+ 'foo',
33
+ '5398228707871528',
34
+ '2718760626256571',
35
+ '2721465526338453',
36
+ '2220175103860763',
37
+ '375556917985515999999993',
38
+ '899999996234917882863855',
39
+ 'prefix6234917882863855',
40
+ '623491788middle2863855',
41
+ '6234917882863855suffix'
42
+ ])(
43
+ 'isCreditCard(%o)',
44
+ str => {
45
+ it('returns false', () => {
46
+ expect(isCreditCard(str)).toBe(false)
47
+ })
48
+ }
49
+ )
50
+ })
@@ -0,0 +1,9 @@
1
+ import punycode from 'punycode/punycode.js'
2
+
3
+ // Best effort approach, allowing Internationalized domain name (with punycode)
4
+ const domainRegExp =
5
+ /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i
6
+
7
+ export function isDomain(str) {
8
+ return !!(str && domainRegExp.test(punycode.toASCII(str)))
9
+ }
@@ -0,0 +1,15 @@
1
+ import { isDomain } from './isDomain.js'
2
+
3
+ describe('isDomain()', () => {
4
+ it('should return true for domains', () => {
5
+ expect(isDomain('lineto.com')).toBe(true)
6
+ expect(isDomain('www.lineto.com')).toBe(true)
7
+ expect(isDomain('www.lineto.ch')).toBe(true)
8
+ expect(isDomain('sub2.sub1.lineto.com')).toBe(true)
9
+ expect(isDomain('www.lineto.c')).toBe(false)
10
+ expect(isDomain('lineto_com')).toBe(false)
11
+ expect(isDomain('line-to.com')).toBe(true)
12
+ expect(isDomain('line_to.com')).toBe(false)
13
+ expect(isDomain('lünéto.com')).toBe(true)
14
+ })
15
+ })
@@ -0,0 +1,6 @@
1
+ const emailRegExp =
2
+ /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
3
+
4
+ export function isEmail(str) {
5
+ return !!(str && emailRegExp.test(str))
6
+ }
@@ -0,0 +1,37 @@
1
+ import { isEmail } from './isEmail.js'
2
+
3
+ describe('isEmail()', () => {
4
+ describe.each([
5
+ 'foo@bar.com',
6
+ 'x@x.au',
7
+ 'foo@bar.com.au',
8
+ 'foo+bar@bar.com',
9
+ 'hans.m端ller@test.com',
10
+ 'hans@m端ller.com',
11
+ 'test|123@m端ller.com',
12
+ 'test+ext@gmail.com',
13
+ 'some.name.midd.leNa.me.+extension@GoogleMail.com'
14
+ ])(
15
+ 'isEmail(%o)',
16
+ str => {
17
+ it('returns true', () => {
18
+ expect(isEmail(str)).toBe(true)
19
+ })
20
+ }
21
+ )
22
+ describe.each([
23
+ 'invalidemail@',
24
+ 'invalid.com',
25
+ '@invalid.com',
26
+ 'foo@bar.com.',
27
+ 'foo@bar.co.uk.',
28
+ 'Name foo@bar.co.uk'
29
+ ])(
30
+ 'isEmail(%o)',
31
+ str => {
32
+ it('returns false', () => {
33
+ expect(isEmail(str)).toBe(false)
34
+ })
35
+ }
36
+ )
37
+ })
@@ -0,0 +1,8 @@
1
+ // https://tools.ietf.org/html/rfc1034#section-3.5
2
+ // https://tools.ietf.org/html/rfc1123#section-2
3
+ const hostnameRegExp =
4
+ /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*$/i
5
+
6
+ export function isHostname(str) {
7
+ return !!(str && hostnameRegExp.test(str))
8
+ }
@@ -0,0 +1,12 @@
1
+ import { isHostname } from './isHostname.js'
2
+
3
+ describe('isHostname()', () => {
4
+ it('should return true for hostnames', () => {
5
+ expect(isHostname('lineto.com')).toBe(true)
6
+ expect(isHostname('www.lineto.com')).toBe(true)
7
+ expect(isHostname('lineto_com')).toBe(false)
8
+ expect(isHostname('line-to.com')).toBe(true)
9
+ expect(isHostname('line_to.com')).toBe(false)
10
+ expect(isHostname('linéto.com')).toBe(false)
11
+ })
12
+ })
@@ -0,0 +1,23 @@
1
+ // TODO: There's a contradiction between isUrl() and isAbsoluteUrl(), where
2
+ // isUrl() returns `true` for 'google.com', and isAbsoluteUrl() returns `false`!
3
+
4
+ const isUrlRegExp = new RegExp(
5
+ '^((https?|ftps?|mailto|rtsp|mms)?://)?' + // user:pass@
6
+ "(([0-9a-z_!~*'().&=+$%-]+:)?[0-9a-z_!~*'().&=+$%-]*@)?" +
7
+ '(' +
8
+ '(\\d{1,3}\\.){3}\\d{1,3}' + // ip
9
+ '|' +
10
+ '([0-9a-z_-]+\\.)*' + // sub-domain: www.
11
+ '[0-9a-z](?:[0-9a-z_-]{0,61}[0-9a-z])?\\.' + // domain-name
12
+ // tld - https://tools.ietf.org/id/draft-liman-tld-names-00.html#rfc.section.2
13
+ '([a-z]{2,61}|xn--[0-9a-z][0-9a-z-]{0,61}[0-9a-z])' +
14
+ ')' + // top level domain.
15
+ '(:[0-9]{1,5})?' + // port
16
+ '((/?)|' + // allow ending in a slash
17
+ "([/?#][0-9a-z_!~*'().;:@&=+$,%/?#-]+)+/?)$", // path
18
+ 'i'
19
+ )
20
+
21
+ export function isUrl(str) {
22
+ return !!(str && isUrlRegExp.test(str))
23
+ }