@pyreon/ui-core 0.11.5 → 0.11.7
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 +15 -13
- package/lib/index.d.ts +19 -12
- package/lib/index.js +29 -19
- package/package.json +24 -24
- package/src/PyreonUI.tsx +28 -34
- package/src/__tests__/PyreonUI.test.tsx +40 -37
- package/src/__tests__/compose.test.ts +8 -8
- package/src/__tests__/config.test.ts +37 -37
- package/src/__tests__/context.test.tsx +28 -27
- package/src/__tests__/hoistNonReactStatics.test.tsx +44 -44
- package/src/__tests__/isEmpty.test.ts +15 -15
- package/src/__tests__/isEqual.test.ts +28 -28
- package/src/__tests__/render.test.tsx +28 -28
- package/src/__tests__/useStableValue.test.ts +23 -23
- package/src/__tests__/utils.test.ts +149 -149
- package/src/config.ts +7 -7
- package/src/context.tsx +43 -8
- package/src/hoistNonReactStatics.ts +1 -1
- package/src/html/htmlTags.ts +142 -142
- package/src/html/index.ts +3 -3
- package/src/index.ts +19 -17
- package/src/isEmpty.ts +1 -1
- package/src/isEqual.ts +1 -1
- package/src/render.tsx +6 -6
- package/src/useStableValue.ts +2 -2
- package/src/utils.ts +3 -3
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from
|
|
2
|
-
import config, { init } from
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import config, { init } from '../config'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
6
|
-
expect(config.component).toBe(
|
|
4
|
+
describe('Configuration', () => {
|
|
5
|
+
it('has default component as div', () => {
|
|
6
|
+
expect(config.component).toBe('div')
|
|
7
7
|
})
|
|
8
8
|
|
|
9
|
-
it(
|
|
10
|
-
expect(config.textComponent).toBe(
|
|
9
|
+
it('has default textComponent as span', () => {
|
|
10
|
+
expect(config.textComponent).toBe('span')
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
it(
|
|
13
|
+
it('has css function', () => {
|
|
14
14
|
expect(config.css).toBeDefined()
|
|
15
|
-
expect(typeof config.css).toBe(
|
|
15
|
+
expect(typeof config.css).toBe('function')
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it('has styled function', () => {
|
|
19
19
|
expect(config.styled).toBeDefined()
|
|
20
|
-
expect(typeof config.styled).toBe(
|
|
20
|
+
expect(typeof config.styled).toBe('function')
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
it(
|
|
23
|
+
it('has keyframes function', () => {
|
|
24
24
|
expect(config.keyframes).toBeDefined()
|
|
25
|
-
expect(typeof config.keyframes).toBe(
|
|
25
|
+
expect(typeof config.keyframes).toBe('function')
|
|
26
26
|
})
|
|
27
27
|
|
|
28
|
-
describe(
|
|
28
|
+
describe('init', () => {
|
|
29
29
|
const originalCss = config.css
|
|
30
30
|
const originalStyled = config.styled
|
|
31
31
|
const originalKeyframes = config.keyframes
|
|
@@ -43,56 +43,56 @@ describe("Configuration", () => {
|
|
|
43
43
|
config.createMediaQueries = originalCreateMediaQueries
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
it(
|
|
47
|
-
const mockCss = (() =>
|
|
46
|
+
it('updates css engine', () => {
|
|
47
|
+
const mockCss = (() => '') as any
|
|
48
48
|
init({ css: mockCss })
|
|
49
49
|
expect(config.css).toBe(mockCss)
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
it(
|
|
53
|
-
const mockStyled = (() =>
|
|
52
|
+
it('updates styled engine', () => {
|
|
53
|
+
const mockStyled = (() => '') as any
|
|
54
54
|
init({ styled: mockStyled })
|
|
55
55
|
expect(config.styled).toBe(mockStyled)
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
it(
|
|
59
|
-
init({ component:
|
|
60
|
-
expect(config.component).toBe(
|
|
58
|
+
it('updates component', () => {
|
|
59
|
+
init({ component: 'section' })
|
|
60
|
+
expect(config.component).toBe('section')
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
-
it(
|
|
64
|
-
init({ textComponent:
|
|
65
|
-
expect(config.textComponent).toBe(
|
|
63
|
+
it('updates textComponent', () => {
|
|
64
|
+
init({ textComponent: 'p' })
|
|
65
|
+
expect(config.textComponent).toBe('p')
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
it(
|
|
69
|
-
const mockKeyframes = (() =>
|
|
68
|
+
it('updates keyframes', () => {
|
|
69
|
+
const mockKeyframes = (() => 'anim') as any
|
|
70
70
|
init({ keyframes: mockKeyframes })
|
|
71
71
|
expect(config.keyframes).toBe(mockKeyframes)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
-
it(
|
|
74
|
+
it('updates createMediaQueries', () => {
|
|
75
75
|
const mockCreateMQ = (() => ({})) as any
|
|
76
76
|
init({ createMediaQueries: mockCreateMQ })
|
|
77
77
|
expect(config.createMediaQueries).toBe(mockCreateMQ)
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
-
it(
|
|
81
|
-
init({ component:
|
|
82
|
-
expect(config.component).toBe(
|
|
83
|
-
expect(config.textComponent).toBe(
|
|
80
|
+
it('only updates provided fields', () => {
|
|
81
|
+
init({ component: 'article' })
|
|
82
|
+
expect(config.component).toBe('article')
|
|
83
|
+
expect(config.textComponent).toBe('span')
|
|
84
84
|
expect(config.css).toBe(originalCss)
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
-
it(
|
|
87
|
+
it('does nothing with empty object', () => {
|
|
88
88
|
init({})
|
|
89
|
-
expect(config.component).toBe(
|
|
90
|
-
expect(config.textComponent).toBe(
|
|
89
|
+
expect(config.component).toBe('div')
|
|
90
|
+
expect(config.textComponent).toBe('span')
|
|
91
91
|
})
|
|
92
92
|
|
|
93
|
-
it(
|
|
94
|
-
const first = (() =>
|
|
95
|
-
const second = (() =>
|
|
93
|
+
it('can be called multiple times to swap engine', () => {
|
|
94
|
+
const first = (() => 'first') as any
|
|
95
|
+
const second = (() => 'second') as any
|
|
96
96
|
init({ css: first })
|
|
97
97
|
expect(config.css).toBe(first)
|
|
98
98
|
init({ css: second })
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { popContext, useContext } from
|
|
2
|
-
import { afterEach, describe, expect, it } from
|
|
3
|
-
import Provider, { context } from
|
|
1
|
+
import { popContext, useContext } from '@pyreon/core'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import Provider, { context } from '../context'
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe('Provider', () => {
|
|
6
6
|
afterEach(() => {
|
|
7
7
|
// Clean up any pushed context frames
|
|
8
8
|
try {
|
|
@@ -12,59 +12,60 @@ describe("Provider", () => {
|
|
|
12
12
|
}
|
|
13
13
|
})
|
|
14
14
|
|
|
15
|
-
it(
|
|
16
|
-
const children =
|
|
15
|
+
it('returns children when no theme is provided', () => {
|
|
16
|
+
const children = 'Hello'
|
|
17
17
|
const result = Provider({ children })
|
|
18
|
-
expect(result).toBe(
|
|
18
|
+
expect(result).toBe('Hello')
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
it(
|
|
22
|
-
const children =
|
|
21
|
+
it('returns children with empty theme', () => {
|
|
22
|
+
const children = 'Hello'
|
|
23
23
|
const result = Provider({ theme: {}, children })
|
|
24
|
-
expect(result).toBe(
|
|
24
|
+
expect(result).toBe('Hello')
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
it(
|
|
28
|
-
const children =
|
|
27
|
+
it('returns children with null theme', () => {
|
|
28
|
+
const children = 'Hello'
|
|
29
29
|
// @ts-expect-error testing null theme
|
|
30
30
|
const result = Provider({ theme: null, children })
|
|
31
|
-
expect(result).toBe(
|
|
31
|
+
expect(result).toBe('Hello')
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
it(
|
|
34
|
+
it('returns children when theme is provided and pushes context', () => {
|
|
35
35
|
const theme = { rootSize: 16, breakpoints: { xs: 0 } }
|
|
36
|
-
const children =
|
|
36
|
+
const children = 'Styled'
|
|
37
37
|
const result = Provider({ theme, children })
|
|
38
|
-
expect(result).toBe(
|
|
38
|
+
expect(result).toBe('Styled')
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
it(
|
|
41
|
+
it('pushes context with theme and extra props', () => {
|
|
42
42
|
const theme = { rootSize: 16 }
|
|
43
|
-
const children =
|
|
44
|
-
Provider({ theme, children, custom:
|
|
43
|
+
const children = 'Content'
|
|
44
|
+
Provider({ theme, children, custom: 'value' })
|
|
45
45
|
// After Provider runs, context should have been pushed
|
|
46
|
-
//
|
|
47
|
-
const
|
|
46
|
+
// Context is ReactiveContext — useContext returns () => value
|
|
47
|
+
const getCtx = useContext(context)
|
|
48
|
+
const ctx = getCtx() as any
|
|
48
49
|
expect(ctx.theme).toEqual({ rootSize: 16 })
|
|
49
|
-
expect(ctx.custom).toBe(
|
|
50
|
+
expect(ctx.custom).toBe('value')
|
|
50
51
|
})
|
|
51
52
|
|
|
52
|
-
it(
|
|
53
|
+
it('returns null when no children and no theme', () => {
|
|
53
54
|
const result = Provider({})
|
|
54
55
|
expect(result).toBeNull()
|
|
55
56
|
})
|
|
56
57
|
|
|
57
|
-
it(
|
|
58
|
+
it('returns null when theme is provided but no children', () => {
|
|
58
59
|
const theme = { rootSize: 16 }
|
|
59
60
|
const result = Provider({ theme })
|
|
60
61
|
expect(result).toBeNull()
|
|
61
62
|
})
|
|
62
63
|
})
|
|
63
64
|
|
|
64
|
-
describe(
|
|
65
|
-
it(
|
|
65
|
+
describe('context', () => {
|
|
66
|
+
it('exports context object with an id', () => {
|
|
66
67
|
expect(context).toBeDefined()
|
|
67
68
|
expect(context.id).toBeDefined()
|
|
68
|
-
expect(typeof context.id).toBe(
|
|
69
|
+
expect(typeof context.id).toBe('symbol')
|
|
69
70
|
})
|
|
70
71
|
})
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import hoistNonReactStatics from
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import hoistNonReactStatics from '../hoistNonReactStatics'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
4
|
+
describe('hoistNonReactStatics', () => {
|
|
5
|
+
it('copies custom static properties from source to target', () => {
|
|
6
6
|
const Source = () => null
|
|
7
|
-
;(Source as any).customStatic =
|
|
7
|
+
;(Source as any).customStatic = 'hello'
|
|
8
8
|
;(Source as any).anotherStatic = 42
|
|
9
9
|
|
|
10
10
|
const Target = () => null
|
|
11
11
|
|
|
12
12
|
hoistNonReactStatics(Target, Source)
|
|
13
13
|
|
|
14
|
-
expect((Target as any).customStatic).toBe(
|
|
14
|
+
expect((Target as any).customStatic).toBe('hello')
|
|
15
15
|
expect((Target as any).anotherStatic).toBe(42)
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it('does not copy component statics (displayName, defaultProps)', () => {
|
|
19
19
|
const Source = () => null
|
|
20
|
-
Source.displayName =
|
|
20
|
+
Source.displayName = 'SourceComponent'
|
|
21
21
|
;(Source as any).defaultProps = { bar: 1 }
|
|
22
|
-
;(Source as any).customProp =
|
|
22
|
+
;(Source as any).customProp = 'should copy'
|
|
23
23
|
|
|
24
24
|
const Target = () => null
|
|
25
|
-
Target.displayName =
|
|
25
|
+
Target.displayName = 'TargetComponent'
|
|
26
26
|
|
|
27
27
|
hoistNonReactStatics(Target, Source)
|
|
28
28
|
|
|
29
|
-
expect(Target.displayName).toBe(
|
|
29
|
+
expect(Target.displayName).toBe('TargetComponent')
|
|
30
30
|
expect((Target as any).defaultProps).toBeUndefined()
|
|
31
|
-
expect((Target as any).customProp).toBe(
|
|
31
|
+
expect((Target as any).customProp).toBe('should copy')
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
it(
|
|
34
|
+
it('does not copy known JS statics (name, length, prototype)', () => {
|
|
35
35
|
const Source = () => null
|
|
36
|
-
;(Source as any).customProp =
|
|
36
|
+
;(Source as any).customProp = 'value'
|
|
37
37
|
|
|
38
38
|
const Target = () => null
|
|
39
39
|
const originalName = Target.name
|
|
@@ -41,25 +41,25 @@ describe("hoistNonReactStatics", () => {
|
|
|
41
41
|
hoistNonReactStatics(Target, Source)
|
|
42
42
|
|
|
43
43
|
expect(Target.name).toBe(originalName)
|
|
44
|
-
expect((Target as any).customProp).toBe(
|
|
44
|
+
expect((Target as any).customProp).toBe('value')
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
it(
|
|
47
|
+
it('respects the excludeList', () => {
|
|
48
48
|
const Source = () => null
|
|
49
|
-
;(Source as any).foo =
|
|
50
|
-
;(Source as any).bar =
|
|
51
|
-
;(Source as any).baz =
|
|
49
|
+
;(Source as any).foo = 'included'
|
|
50
|
+
;(Source as any).bar = 'excluded'
|
|
51
|
+
;(Source as any).baz = 'included'
|
|
52
52
|
|
|
53
53
|
const Target = () => null
|
|
54
54
|
|
|
55
55
|
hoistNonReactStatics(Target, Source, { bar: true })
|
|
56
56
|
|
|
57
|
-
expect((Target as any).foo).toBe(
|
|
57
|
+
expect((Target as any).foo).toBe('included')
|
|
58
58
|
expect((Target as any).bar).toBeUndefined()
|
|
59
|
-
expect((Target as any).baz).toBe(
|
|
59
|
+
expect((Target as any).baz).toBe('included')
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
-
it(
|
|
62
|
+
it('returns the target component', () => {
|
|
63
63
|
const Source = () => null
|
|
64
64
|
const Target = () => null
|
|
65
65
|
|
|
@@ -67,29 +67,29 @@ describe("hoistNonReactStatics", () => {
|
|
|
67
67
|
expect(result).toBe(Target)
|
|
68
68
|
})
|
|
69
69
|
|
|
70
|
-
it(
|
|
70
|
+
it('handles string source (HTML tag) gracefully', () => {
|
|
71
71
|
const Target = () => null
|
|
72
72
|
|
|
73
|
-
const result = hoistNonReactStatics(Target,
|
|
73
|
+
const result = hoistNonReactStatics(Target, 'div' as any)
|
|
74
74
|
expect(result).toBe(Target)
|
|
75
75
|
})
|
|
76
76
|
|
|
77
|
-
it(
|
|
78
|
-
const sym = Symbol(
|
|
77
|
+
it('copies symbol-keyed properties', () => {
|
|
78
|
+
const sym = Symbol('custom')
|
|
79
79
|
const Source = () => null
|
|
80
|
-
;(Source as any)[sym] =
|
|
80
|
+
;(Source as any)[sym] = 'symbol value'
|
|
81
81
|
|
|
82
82
|
const Target = () => null
|
|
83
83
|
|
|
84
84
|
hoistNonReactStatics(Target, Source)
|
|
85
85
|
|
|
86
|
-
expect((Target as any)[sym]).toBe(
|
|
86
|
+
expect((Target as any)[sym]).toBe('symbol value')
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
-
it(
|
|
89
|
+
it('copies getters and setters via property descriptors', () => {
|
|
90
90
|
const Source = () => null
|
|
91
91
|
let value = 0
|
|
92
|
-
Object.defineProperty(Source,
|
|
92
|
+
Object.defineProperty(Source, 'counter', {
|
|
93
93
|
get: () => value,
|
|
94
94
|
set: (v) => {
|
|
95
95
|
value = v
|
|
@@ -109,58 +109,58 @@ describe("hoistNonReactStatics", () => {
|
|
|
109
109
|
expect((Source as any).counter).toBe(5)
|
|
110
110
|
})
|
|
111
111
|
|
|
112
|
-
it(
|
|
112
|
+
it('does not throw on non-configurable target properties', () => {
|
|
113
113
|
const Source = () => null
|
|
114
|
-
;(Source as any).locked =
|
|
114
|
+
;(Source as any).locked = 'source value'
|
|
115
115
|
|
|
116
116
|
const Target = () => null
|
|
117
|
-
Object.defineProperty(Target,
|
|
118
|
-
value:
|
|
117
|
+
Object.defineProperty(Target, 'locked', {
|
|
118
|
+
value: 'target value',
|
|
119
119
|
writable: false,
|
|
120
120
|
configurable: false,
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
expect(() => hoistNonReactStatics(Target, Source)).not.toThrow()
|
|
124
|
-
expect((Target as any).locked).toBe(
|
|
124
|
+
expect((Target as any).locked).toBe('target value')
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
-
it(
|
|
127
|
+
it('hoists statics from prototype chain', () => {
|
|
128
128
|
function Base() {
|
|
129
129
|
// constructor stub
|
|
130
130
|
}
|
|
131
131
|
Base.prototype = Object.create(null)
|
|
132
|
-
;(Base as any).inheritedStatic =
|
|
132
|
+
;(Base as any).inheritedStatic = 'from base'
|
|
133
133
|
|
|
134
134
|
function Source() {
|
|
135
135
|
// constructor stub
|
|
136
136
|
}
|
|
137
137
|
Source.prototype = Object.create(null)
|
|
138
138
|
Object.setPrototypeOf(Source, Base)
|
|
139
|
-
;(Source as any).ownStatic =
|
|
139
|
+
;(Source as any).ownStatic = 'from source'
|
|
140
140
|
|
|
141
141
|
const Target = () => null
|
|
142
142
|
|
|
143
143
|
hoistNonReactStatics(Target, Source as any)
|
|
144
144
|
|
|
145
|
-
expect((Target as any).ownStatic).toBe(
|
|
146
|
-
expect((Target as any).inheritedStatic).toBe(
|
|
145
|
+
expect((Target as any).ownStatic).toBe('from source')
|
|
146
|
+
expect((Target as any).inheritedStatic).toBe('from base')
|
|
147
147
|
})
|
|
148
148
|
|
|
149
|
-
it(
|
|
149
|
+
it('works with components that have no custom statics', () => {
|
|
150
150
|
const Source = () => null
|
|
151
151
|
const Target = () => null
|
|
152
152
|
|
|
153
153
|
expect(() => hoistNonReactStatics(Target, Source)).not.toThrow()
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
it(
|
|
156
|
+
it('stops prototype recursion at Object.prototype', () => {
|
|
157
157
|
const Source = () => null
|
|
158
|
-
;(Source as any).custom =
|
|
158
|
+
;(Source as any).custom = 'value'
|
|
159
159
|
// Source's prototype is Function.prototype which has proto Object.prototype
|
|
160
160
|
// The recursion should walk up and stop at Object.prototype
|
|
161
161
|
|
|
162
162
|
const Target = () => null
|
|
163
163
|
expect(() => hoistNonReactStatics(Target, Source)).not.toThrow()
|
|
164
|
-
expect((Target as any).custom).toBe(
|
|
164
|
+
expect((Target as any).custom).toBe('value')
|
|
165
165
|
})
|
|
166
166
|
})
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import isEmpty from
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import isEmpty from '../isEmpty'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
4
|
+
describe('isEmpty', () => {
|
|
5
|
+
it('should return true for null', () => {
|
|
6
6
|
expect(isEmpty(null)).toBe(true)
|
|
7
7
|
})
|
|
8
8
|
|
|
9
|
-
it(
|
|
9
|
+
it('should return true for undefined', () => {
|
|
10
10
|
expect(isEmpty(undefined)).toBe(true)
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
-
it(
|
|
13
|
+
it('should return true for empty object', () => {
|
|
14
14
|
expect(isEmpty({})).toBe(true)
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
it(
|
|
17
|
+
it('should return true for empty array', () => {
|
|
18
18
|
expect(isEmpty([])).toBe(true)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
it(
|
|
21
|
+
it('should return false for non-empty object', () => {
|
|
22
22
|
expect(isEmpty({ a: 1 })).toBe(false)
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
it(
|
|
25
|
+
it('should return false for non-empty array', () => {
|
|
26
26
|
expect(isEmpty([1])).toBe(false)
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
it(
|
|
29
|
+
it('should return false for array with falsy values', () => {
|
|
30
30
|
expect(isEmpty([0, null, undefined])).toBe(false)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
it(
|
|
33
|
+
it('should return false for object with falsy values', () => {
|
|
34
34
|
expect(isEmpty({ a: 0, b: null })).toBe(false)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
it(
|
|
37
|
+
it('should return true for Object.create(null) with no properties', () => {
|
|
38
38
|
expect(isEmpty(Object.create(null))).toBe(true)
|
|
39
39
|
})
|
|
40
40
|
|
|
41
|
-
it(
|
|
41
|
+
it('should return false for Object.create(null) with properties', () => {
|
|
42
42
|
const obj = Object.create(null)
|
|
43
43
|
obj.a = 1
|
|
44
44
|
expect(isEmpty(obj)).toBe(false)
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
it(
|
|
47
|
+
it('should return true for non-object truthy primitives', () => {
|
|
48
48
|
// Covers typeof param !== 'object' branch
|
|
49
49
|
expect(isEmpty(42 as any)).toBe(true)
|
|
50
|
-
expect(isEmpty(
|
|
50
|
+
expect(isEmpty('hello' as any)).toBe(true)
|
|
51
51
|
expect(isEmpty(true as any)).toBe(true)
|
|
52
52
|
})
|
|
53
53
|
})
|
|
@@ -1,113 +1,113 @@
|
|
|
1
|
-
import { describe, expect, it } from
|
|
2
|
-
import isEqual from
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import isEqual from '../isEqual'
|
|
3
3
|
|
|
4
|
-
describe(
|
|
4
|
+
describe('isEqual', () => {
|
|
5
5
|
// Primitives
|
|
6
|
-
it(
|
|
6
|
+
it('should return true for identical primitives', () => {
|
|
7
7
|
expect(isEqual(1, 1)).toBe(true)
|
|
8
|
-
expect(isEqual(
|
|
8
|
+
expect(isEqual('a', 'a')).toBe(true)
|
|
9
9
|
expect(isEqual(true, true)).toBe(true)
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
it(
|
|
12
|
+
it('should return false for different primitives', () => {
|
|
13
13
|
expect(isEqual(1, 2)).toBe(false)
|
|
14
|
-
expect(isEqual(
|
|
14
|
+
expect(isEqual('a', 'b')).toBe(false)
|
|
15
15
|
expect(isEqual(true, false)).toBe(false)
|
|
16
16
|
})
|
|
17
17
|
|
|
18
|
-
it(
|
|
18
|
+
it('should handle NaN', () => {
|
|
19
19
|
expect(isEqual(NaN, NaN)).toBe(true)
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
it(
|
|
22
|
+
it('should distinguish 0 and -0', () => {
|
|
23
23
|
expect(isEqual(0, -0)).toBe(false)
|
|
24
24
|
})
|
|
25
25
|
|
|
26
26
|
// Null / undefined
|
|
27
|
-
it(
|
|
27
|
+
it('should return true for null === null and undefined === undefined', () => {
|
|
28
28
|
expect(isEqual(null, null)).toBe(true)
|
|
29
29
|
expect(isEqual(undefined, undefined)).toBe(true)
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
it(
|
|
32
|
+
it('should return false for null vs undefined', () => {
|
|
33
33
|
expect(isEqual(null, undefined)).toBe(false)
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
it(
|
|
36
|
+
it('should return false for null vs object', () => {
|
|
37
37
|
expect(isEqual(null, {})).toBe(false)
|
|
38
38
|
expect(isEqual({}, null)).toBe(false)
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
// Objects
|
|
42
|
-
it(
|
|
42
|
+
it('should return true for deeply equal objects', () => {
|
|
43
43
|
expect(isEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true)
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
-
it(
|
|
46
|
+
it('should return true regardless of key order', () => {
|
|
47
47
|
expect(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
it(
|
|
50
|
+
it('should return false for objects with different values', () => {
|
|
51
51
|
expect(isEqual({ a: 1 }, { a: 2 })).toBe(false)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
-
it(
|
|
54
|
+
it('should return false for objects with different keys', () => {
|
|
55
55
|
expect(isEqual({ a: 1 }, { b: 1 })).toBe(false)
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
it(
|
|
58
|
+
it('should return false for objects with different key counts', () => {
|
|
59
59
|
expect(isEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false)
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
// Nested objects
|
|
63
|
-
it(
|
|
63
|
+
it('should deeply compare nested objects', () => {
|
|
64
64
|
const a = { a: { b: { c: 1 } } }
|
|
65
65
|
expect(isEqual(a, { a: { b: { c: 1 } } })).toBe(true)
|
|
66
66
|
expect(isEqual(a, { a: { b: { c: 2 } } })).toBe(false)
|
|
67
67
|
})
|
|
68
68
|
|
|
69
|
-
it(
|
|
69
|
+
it('should handle nested key order differences', () => {
|
|
70
70
|
expect(isEqual({ x: { b: 2, a: 1 }, y: 3 }, { y: 3, x: { a: 1, b: 2 } })).toBe(true)
|
|
71
71
|
})
|
|
72
72
|
|
|
73
73
|
// Arrays
|
|
74
|
-
it(
|
|
74
|
+
it('should return true for identical arrays', () => {
|
|
75
75
|
expect(isEqual([1, 2, 3], [1, 2, 3])).toBe(true)
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
-
it(
|
|
78
|
+
it('should return false for arrays with different values', () => {
|
|
79
79
|
expect(isEqual([1, 2], [1, 3])).toBe(false)
|
|
80
80
|
})
|
|
81
81
|
|
|
82
|
-
it(
|
|
82
|
+
it('should return false for arrays with different lengths', () => {
|
|
83
83
|
expect(isEqual([1, 2], [1, 2, 3])).toBe(false)
|
|
84
84
|
})
|
|
85
85
|
|
|
86
|
-
it(
|
|
86
|
+
it('should not treat array and object as equal', () => {
|
|
87
87
|
expect(isEqual([1], { 0: 1 })).toBe(false)
|
|
88
88
|
expect(isEqual({ 0: 1 }, [1])).toBe(false)
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
// Mixed nested
|
|
92
|
-
it(
|
|
92
|
+
it('should deeply compare arrays of objects', () => {
|
|
93
93
|
expect(isEqual([{ a: 1 }, { b: 2 }], [{ a: 1 }, { b: 2 }])).toBe(true)
|
|
94
94
|
expect(isEqual([{ a: 1 }], [{ a: 2 }])).toBe(false)
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
-
it(
|
|
97
|
+
it('should deeply compare objects with array values', () => {
|
|
98
98
|
expect(isEqual({ a: [1, 2] }, { a: [1, 2] })).toBe(true)
|
|
99
99
|
expect(isEqual({ a: [1, 2] }, { a: [1, 3] })).toBe(false)
|
|
100
100
|
})
|
|
101
101
|
|
|
102
102
|
// Same reference
|
|
103
|
-
it(
|
|
103
|
+
it('should return true for the same reference', () => {
|
|
104
104
|
const obj = { a: 1 }
|
|
105
105
|
expect(isEqual(obj, obj)).toBe(true)
|
|
106
106
|
})
|
|
107
107
|
|
|
108
108
|
// Type mismatches
|
|
109
|
-
it(
|
|
110
|
-
expect(isEqual(1,
|
|
109
|
+
it('should return false for different types', () => {
|
|
110
|
+
expect(isEqual(1, '1')).toBe(false)
|
|
111
111
|
expect(isEqual([], {})).toBe(false)
|
|
112
112
|
expect(isEqual(0, false)).toBe(false)
|
|
113
113
|
})
|