@furystack/shades 11.0.34 → 11.1.0

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 (70) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +86 -0
  3. package/esm/compile-route.spec.d.ts +2 -0
  4. package/esm/compile-route.spec.d.ts.map +1 -0
  5. package/esm/compile-route.spec.js +34 -0
  6. package/esm/compile-route.spec.js.map +1 -0
  7. package/esm/components/route-link.d.ts.map +1 -1
  8. package/esm/components/route-link.js +4 -5
  9. package/esm/components/route-link.js.map +1 -1
  10. package/esm/components/route-link.spec.js +1 -1
  11. package/esm/components/route-link.spec.js.map +1 -1
  12. package/esm/css-generator.d.ts +50 -0
  13. package/esm/css-generator.d.ts.map +1 -0
  14. package/esm/css-generator.js +107 -0
  15. package/esm/css-generator.js.map +1 -0
  16. package/esm/css-generator.spec.d.ts +2 -0
  17. package/esm/css-generator.spec.d.ts.map +1 -0
  18. package/esm/css-generator.spec.js +162 -0
  19. package/esm/css-generator.spec.js.map +1 -0
  20. package/esm/index.d.ts +2 -0
  21. package/esm/index.d.ts.map +1 -1
  22. package/esm/index.js +2 -0
  23. package/esm/index.js.map +1 -1
  24. package/esm/models/css-object.d.ts +33 -0
  25. package/esm/models/css-object.d.ts.map +1 -0
  26. package/esm/models/css-object.js +2 -0
  27. package/esm/models/css-object.js.map +1 -0
  28. package/esm/models/index.d.ts +1 -0
  29. package/esm/models/index.d.ts.map +1 -1
  30. package/esm/models/index.js +1 -0
  31. package/esm/models/index.js.map +1 -1
  32. package/esm/shade.d.ts +18 -2
  33. package/esm/shade.d.ts.map +1 -1
  34. package/esm/shade.js +8 -0
  35. package/esm/shade.js.map +1 -1
  36. package/esm/shade.spec.d.ts +2 -0
  37. package/esm/shade.spec.d.ts.map +1 -0
  38. package/esm/shade.spec.js +197 -0
  39. package/esm/shade.spec.js.map +1 -0
  40. package/esm/style-manager.d.ts +65 -0
  41. package/esm/style-manager.d.ts.map +1 -0
  42. package/esm/style-manager.js +95 -0
  43. package/esm/style-manager.js.map +1 -0
  44. package/esm/style-manager.spec.d.ts +2 -0
  45. package/esm/style-manager.spec.d.ts.map +1 -0
  46. package/esm/style-manager.spec.js +179 -0
  47. package/esm/style-manager.spec.js.map +1 -0
  48. package/esm/styled-element.spec.d.ts +2 -0
  49. package/esm/styled-element.spec.d.ts.map +1 -0
  50. package/esm/styled-element.spec.js +86 -0
  51. package/esm/styled-element.spec.js.map +1 -0
  52. package/esm/styled-shade.spec.d.ts +2 -0
  53. package/esm/styled-shade.spec.d.ts.map +1 -0
  54. package/esm/styled-shade.spec.js +66 -0
  55. package/esm/styled-shade.spec.js.map +1 -0
  56. package/package.json +15 -10
  57. package/src/compile-route.spec.ts +39 -0
  58. package/src/components/route-link.spec.tsx +1 -1
  59. package/src/components/route-link.tsx +4 -5
  60. package/src/css-generator.spec.ts +183 -0
  61. package/src/css-generator.ts +117 -0
  62. package/src/index.ts +2 -0
  63. package/src/models/css-object.ts +34 -0
  64. package/src/models/index.ts +1 -0
  65. package/src/shade.spec.tsx +238 -0
  66. package/src/shade.ts +29 -2
  67. package/src/style-manager.spec.ts +229 -0
  68. package/src/style-manager.ts +104 -0
  69. package/src/styled-element.spec.tsx +117 -0
  70. package/src/styled-shade.spec.ts +86 -0
@@ -0,0 +1,229 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2
+ import { Shade } from './shade.js'
3
+ import { StyleManager } from './style-manager.js'
4
+
5
+ describe('StyleManager', () => {
6
+ beforeEach(() => {
7
+ StyleManager.clear()
8
+ })
9
+
10
+ afterEach(() => {
11
+ StyleManager.clear()
12
+ })
13
+
14
+ describe('registerComponentStyles', () => {
15
+ it('should register styles for a component', () => {
16
+ const result = StyleManager.registerComponentStyles('test-component', {
17
+ color: 'red',
18
+ padding: '10px',
19
+ })
20
+
21
+ expect(result).toBe(true)
22
+ expect(StyleManager.isRegistered('test-component')).toBe(true)
23
+ })
24
+
25
+ it('should inject CSS into a style element', () => {
26
+ StyleManager.registerComponentStyles('test-component', {
27
+ color: 'red',
28
+ })
29
+
30
+ const styleElement = document.querySelector('[data-shades-styles]')
31
+ expect(styleElement).not.toBeNull()
32
+ expect(styleElement?.textContent).toContain('test-component')
33
+ expect(styleElement?.textContent).toContain('color: red')
34
+ })
35
+
36
+ it('should not register the same component twice', () => {
37
+ const result1 = StyleManager.registerComponentStyles('test-component', {
38
+ color: 'red',
39
+ })
40
+ const result2 = StyleManager.registerComponentStyles('test-component', {
41
+ color: 'blue',
42
+ })
43
+
44
+ expect(result1).toBe(true)
45
+ expect(result2).toBe(false)
46
+
47
+ // Should only have the first style
48
+ const styleElement = document.querySelector('[data-shades-styles]')
49
+ expect(styleElement?.textContent).toContain('color: red')
50
+ expect(styleElement?.textContent).not.toContain('color: blue')
51
+ })
52
+
53
+ it('should handle pseudo-selectors', () => {
54
+ StyleManager.registerComponentStyles('test-button', {
55
+ backgroundColor: 'blue',
56
+ '&:hover': { backgroundColor: 'darkblue' },
57
+ })
58
+
59
+ const styleElement = document.querySelector('[data-shades-styles]')
60
+ expect(styleElement?.textContent).toContain('test-button:hover')
61
+ expect(styleElement?.textContent).toContain('background-color: darkblue')
62
+ })
63
+
64
+ it('should return false for empty CSS object', () => {
65
+ const result = StyleManager.registerComponentStyles('empty-component', {})
66
+
67
+ expect(result).toBe(false)
68
+ expect(StyleManager.isRegistered('empty-component')).toBe(false)
69
+ })
70
+
71
+ it('should add comments with component name', () => {
72
+ StyleManager.registerComponentStyles('my-component', {
73
+ color: 'red',
74
+ })
75
+
76
+ const styleElement = document.querySelector('[data-shades-styles]')
77
+ expect(styleElement?.textContent).toContain('/* my-component */')
78
+ })
79
+
80
+ it('should generate attribute selector for customized built-in elements', () => {
81
+ StyleManager.registerComponentStyles(
82
+ 'my-link',
83
+ {
84
+ color: 'blue',
85
+ textDecoration: 'none',
86
+ },
87
+ 'a',
88
+ )
89
+
90
+ const styleElement = document.querySelector('[data-shades-styles]')
91
+ expect(styleElement?.textContent).toContain('a[is="my-link"]')
92
+ expect(styleElement?.textContent).toContain('color: blue')
93
+ expect(styleElement?.textContent).toContain('text-decoration: none')
94
+ })
95
+
96
+ it('should handle pseudo-selectors for customized built-in elements', () => {
97
+ StyleManager.registerComponentStyles(
98
+ 'my-button',
99
+ {
100
+ backgroundColor: 'gray',
101
+ '&:hover': { backgroundColor: 'darkgray' },
102
+ '&:active': { transform: 'scale(0.98)' },
103
+ },
104
+ 'button',
105
+ )
106
+
107
+ const styleElement = document.querySelector('[data-shades-styles]')
108
+ expect(styleElement?.textContent).toContain('button[is="my-button"]')
109
+ expect(styleElement?.textContent).toContain('button[is="my-button"]:hover')
110
+ expect(styleElement?.textContent).toContain('button[is="my-button"]:active')
111
+ })
112
+ })
113
+
114
+ describe('isRegistered', () => {
115
+ it('should return true for registered components', () => {
116
+ StyleManager.registerComponentStyles('test-component', { color: 'red' })
117
+
118
+ expect(StyleManager.isRegistered('test-component')).toBe(true)
119
+ })
120
+
121
+ it('should return false for unregistered components', () => {
122
+ expect(StyleManager.isRegistered('unknown-component')).toBe(false)
123
+ })
124
+ })
125
+
126
+ describe('getRegisteredComponents', () => {
127
+ it('should return all registered component names', () => {
128
+ StyleManager.registerComponentStyles('component-a', { color: 'red' })
129
+ StyleManager.registerComponentStyles('component-b', { color: 'blue' })
130
+
131
+ const registered = StyleManager.getRegisteredComponents()
132
+
133
+ expect(registered.has('component-a')).toBe(true)
134
+ expect(registered.has('component-b')).toBe(true)
135
+ expect(registered.size).toBe(2)
136
+ })
137
+
138
+ it('should return empty set when no components registered', () => {
139
+ const registered = StyleManager.getRegisteredComponents()
140
+ expect(registered.size).toBe(0)
141
+ })
142
+ })
143
+
144
+ describe('clear', () => {
145
+ it('should clear all registered components', () => {
146
+ StyleManager.registerComponentStyles('test-component', { color: 'red' })
147
+ expect(StyleManager.isRegistered('test-component')).toBe(true)
148
+
149
+ StyleManager.clear()
150
+
151
+ expect(StyleManager.isRegistered('test-component')).toBe(false)
152
+ expect(StyleManager.getRegisteredComponents().size).toBe(0)
153
+ })
154
+
155
+ it('should remove style element on clear', () => {
156
+ StyleManager.registerComponentStyles('test-component', { color: 'red' })
157
+ let styleElement = document.querySelector('[data-shades-styles]')
158
+ expect(styleElement).not.toBeNull()
159
+
160
+ StyleManager.clear()
161
+
162
+ styleElement = document.querySelector('[data-shades-styles]')
163
+ expect(styleElement).toBeNull()
164
+ })
165
+ })
166
+
167
+ describe('reusing style element', () => {
168
+ it('should use the same style element for multiple components', () => {
169
+ StyleManager.registerComponentStyles('component-a', { color: 'red' })
170
+ StyleManager.registerComponentStyles('component-b', { color: 'blue' })
171
+
172
+ const styleElements = document.querySelectorAll('[data-shades-styles]')
173
+ expect(styleElements.length).toBe(1)
174
+
175
+ const styleElement = styleElements[0]
176
+ expect(styleElement?.textContent).toContain('component-a')
177
+ expect(styleElement?.textContent).toContain('component-b')
178
+ })
179
+ })
180
+
181
+ describe('Shade integration', () => {
182
+ it('should register CSS styles when Shade component is created with css property', () => {
183
+ Shade({
184
+ shadowDomName: 'shade-css-test-component',
185
+ css: {
186
+ color: 'red',
187
+ padding: '10px',
188
+ '&:hover': { color: 'blue' },
189
+ },
190
+ render: () => null,
191
+ })
192
+
193
+ expect(StyleManager.isRegistered('shade-css-test-component')).toBe(true)
194
+
195
+ const styleElement = document.querySelector('[data-shades-styles]')
196
+ expect(styleElement?.textContent).toContain('shade-css-test-component')
197
+ expect(styleElement?.textContent).toContain('color: red')
198
+ expect(styleElement?.textContent).toContain('shade-css-test-component:hover')
199
+ })
200
+
201
+ it('should register CSS with attribute selector for customized built-in elements', () => {
202
+ Shade({
203
+ shadowDomName: 'shade-css-test-button',
204
+ elementBase: HTMLButtonElement,
205
+ elementBaseName: 'button',
206
+ css: {
207
+ backgroundColor: 'blue',
208
+ '&:hover': { backgroundColor: 'darkblue' },
209
+ },
210
+ render: () => null,
211
+ })
212
+
213
+ expect(StyleManager.isRegistered('shade-css-test-button')).toBe(true)
214
+
215
+ const styleElement = document.querySelector('[data-shades-styles]')
216
+ expect(styleElement?.textContent).toContain('button[is="shade-css-test-button"]')
217
+ expect(styleElement?.textContent).toContain('button[is="shade-css-test-button"]:hover')
218
+ })
219
+
220
+ it('should not register styles when Shade component has no css property', () => {
221
+ Shade({
222
+ shadowDomName: 'shade-no-css-component',
223
+ render: () => null,
224
+ })
225
+
226
+ expect(StyleManager.isRegistered('shade-no-css-component')).toBe(false)
227
+ })
228
+ })
229
+ })
@@ -0,0 +1,104 @@
1
+ import { generateCSS } from './css-generator.js'
2
+ import type { CSSObject } from './models/css-object.js'
3
+
4
+ /**
5
+ * Singleton that manages component CSS injection.
6
+ * Creates and maintains a shared `<style>` element in the document head,
7
+ * and tracks registered component styles to avoid duplicates.
8
+ */
9
+ class StyleManagerClass {
10
+ private styleElement: HTMLStyleElement | null = null
11
+ private registeredComponents = new Set<string>()
12
+
13
+ /**
14
+ * Gets or creates the shared style element
15
+ * @returns The style element for CSS injection
16
+ */
17
+ private getStyleElement(): HTMLStyleElement {
18
+ if (!this.styleElement) {
19
+ this.styleElement = document.createElement('style')
20
+ this.styleElement.setAttribute('data-shades-styles', '')
21
+ document.head.appendChild(this.styleElement)
22
+ }
23
+ return this.styleElement
24
+ }
25
+
26
+ /**
27
+ * Registers CSS styles for a component.
28
+ * Styles are only injected once per component (based on shadowDomName).
29
+ *
30
+ * @param shadowDomName - The unique component identifier (used as CSS selector)
31
+ * @param cssObject - The CSSObject containing styles and nested selectors
32
+ * @param elementBaseName - Optional base element name for customized built-in elements (e.g., 'a', 'button').
33
+ * When provided, generates selector like `a[is="component-name"]` instead of `component-name`
34
+ * @returns True if styles were injected, false if already registered
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // Regular custom element
39
+ * StyleManager.registerComponentStyles('my-button', {
40
+ * padding: '12px',
41
+ * '&:hover': { backgroundColor: 'blue' }
42
+ * })
43
+ *
44
+ * // Customized built-in element (extends anchor)
45
+ * StyleManager.registerComponentStyles('my-link', {
46
+ * color: 'blue',
47
+ * '&:hover': { textDecoration: 'underline' }
48
+ * }, 'a')
49
+ * // Generates: a[is="my-link"] { color: blue; }
50
+ * ```
51
+ */
52
+ public registerComponentStyles(shadowDomName: string, cssObject: CSSObject, elementBaseName?: string): boolean {
53
+ if (this.registeredComponents.has(shadowDomName)) {
54
+ return false
55
+ }
56
+
57
+ const selector = elementBaseName ? `${elementBaseName}[is="${shadowDomName}"]` : shadowDomName
58
+ const css = generateCSS(selector, cssObject)
59
+ if (css) {
60
+ const styleElement = this.getStyleElement()
61
+ styleElement.textContent += `\n/* ${shadowDomName} */\n${css}\n`
62
+ this.registeredComponents.add(shadowDomName)
63
+ return true
64
+ }
65
+
66
+ return false
67
+ }
68
+
69
+ /**
70
+ * Checks if a component's styles have already been registered
71
+ * @param shadowDomName - The component identifier to check
72
+ * @returns True if styles are already registered
73
+ */
74
+ public isRegistered(shadowDomName: string): boolean {
75
+ return this.registeredComponents.has(shadowDomName)
76
+ }
77
+
78
+ /**
79
+ * Gets all registered component names (for debugging/testing)
80
+ * @returns Set of registered component names
81
+ */
82
+ public getRegisteredComponents(): ReadonlySet<string> {
83
+ return this.registeredComponents
84
+ }
85
+
86
+ /**
87
+ * Clears all registered styles (useful for testing)
88
+ */
89
+ public clear(): void {
90
+ this.registeredComponents.clear()
91
+ if (this.styleElement) {
92
+ this.styleElement.textContent = ''
93
+ this.styleElement.remove()
94
+ this.styleElement = null
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Singleton instance for managing component CSS styles.
101
+ * Use this to register component-level styles that support
102
+ * pseudo-selectors and nested selectors.
103
+ */
104
+ export const StyleManager = new StyleManagerClass()
@@ -0,0 +1,117 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { styledElement } from './styled-element.js'
3
+
4
+ describe('styled-element', () => {
5
+ describe('styledElement', () => {
6
+ it('should apply additional styles to an intrinsic element', () => {
7
+ const additionalStyles = { color: 'red', padding: '10px' }
8
+ const StyledDiv = styledElement('div', additionalStyles)
9
+
10
+ const result = StyledDiv({}, [])
11
+
12
+ expect(result.style.color).toBe('red')
13
+ expect(result.style.padding).toBe('10px')
14
+ })
15
+
16
+ it('should merge styles when element already has styles', () => {
17
+ const additionalStyles = { color: 'red' }
18
+ const existingStyles = { backgroundColor: 'blue' }
19
+
20
+ const StyledDiv = styledElement('div', additionalStyles)
21
+ const result = StyledDiv({ style: existingStyles }, [])
22
+
23
+ expect(result.style.backgroundColor).toBe('blue')
24
+ expect(result.style.color).toBe('red')
25
+ })
26
+
27
+ it('should override existing styles with additional styles', () => {
28
+ const additionalStyles = { color: 'red' }
29
+ const existingStyles = { color: 'blue' }
30
+
31
+ const StyledDiv = styledElement('div', additionalStyles)
32
+ const result = StyledDiv({ style: existingStyles }, [])
33
+
34
+ expect(result.style.color).toBe('red')
35
+ })
36
+
37
+ it('should preserve original props', () => {
38
+ const additionalStyles = { color: 'red' }
39
+
40
+ const StyledDiv = styledElement('div', additionalStyles)
41
+ const result = StyledDiv({ className: 'my-class', id: 'my-id' }, [])
42
+
43
+ expect(result.className).toBe('my-class')
44
+ expect(result.id).toBe('my-id')
45
+ expect(result.style.color).toBe('red')
46
+ })
47
+
48
+ it('should pass children correctly', () => {
49
+ const additionalStyles = { color: 'red' }
50
+
51
+ const StyledDiv = styledElement('div', additionalStyles)
52
+ const child1 = document.createElement('span')
53
+ child1.textContent = 'child1'
54
+ const child2 = document.createElement('span')
55
+ child2.textContent = 'child2'
56
+
57
+ const result = StyledDiv({}, [child1, child2])
58
+
59
+ expect(result.children.length).toBe(2)
60
+ expect(result.children[0]).toBe(child1)
61
+ expect(result.children[1]).toBe(child2)
62
+ })
63
+
64
+ it('should handle text children', () => {
65
+ const additionalStyles = { color: 'red' }
66
+
67
+ const StyledDiv = styledElement('div', additionalStyles)
68
+ const result = StyledDiv({}, ['Hello', ' ', 'World'])
69
+
70
+ expect(result.textContent).toBe('Hello World')
71
+ })
72
+
73
+ it('should return an HTMLElement', () => {
74
+ const additionalStyles = { color: 'red' }
75
+
76
+ const StyledDiv = styledElement('div', additionalStyles)
77
+ const result = StyledDiv({}, [])
78
+
79
+ expect(result).toBeInstanceOf(HTMLElement)
80
+ expect(result.tagName.toLowerCase()).toBe('div')
81
+ })
82
+
83
+ it('should work with different element types', () => {
84
+ const spanStyles = { fontWeight: 'bold' }
85
+ const buttonStyles = { cursor: 'pointer' }
86
+
87
+ const StyledSpan = styledElement('span', spanStyles)
88
+ const StyledButton = styledElement('button', buttonStyles)
89
+
90
+ const spanResult = StyledSpan({}, [])
91
+ const buttonResult = StyledButton({}, [])
92
+
93
+ expect(spanResult.tagName.toLowerCase()).toBe('span')
94
+ expect(spanResult.style.fontWeight).toBe('bold')
95
+ expect(buttonResult.tagName.toLowerCase()).toBe('button')
96
+ expect(buttonResult.style.cursor).toBe('pointer')
97
+ })
98
+
99
+ it('should handle props being undefined', () => {
100
+ const additionalStyles = { color: 'red' }
101
+
102
+ const StyledDiv = styledElement('div', additionalStyles)
103
+ const result = StyledDiv(undefined as unknown as Record<string, unknown>, [])
104
+
105
+ expect(result.style.color).toBe('red')
106
+ })
107
+
108
+ it('should handle empty children list', () => {
109
+ const additionalStyles = { color: 'red' }
110
+
111
+ const StyledDiv = styledElement('div', additionalStyles)
112
+ const result = StyledDiv({}, [])
113
+
114
+ expect(result.children.length).toBe(0)
115
+ })
116
+ })
117
+ })
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import { styledShade } from './styled-shade.js'
3
+
4
+ describe('styled-shade', () => {
5
+ describe('styledShade', () => {
6
+ it('should apply additional styles to a component', () => {
7
+ const mockElement = vi.fn().mockReturnValue({} as JSX.Element)
8
+ const additionalStyles = { color: 'red', padding: '10px' }
9
+
10
+ const StyledComponent = styledShade(mockElement, additionalStyles)
11
+ StyledComponent({})
12
+
13
+ expect(mockElement).toHaveBeenCalledTimes(1)
14
+ const [props] = mockElement.mock.calls[0] as [{ style: Record<string, string> }]
15
+ expect(props.style.color).toBe('red')
16
+ expect(props.style.padding).toBe('10px')
17
+ })
18
+
19
+ it('should merge styles when component already has styles', () => {
20
+ const mockElement = vi.fn().mockReturnValue({} as JSX.Element)
21
+ const additionalStyles = { color: 'red' }
22
+ const existingStyles = { backgroundColor: 'blue' }
23
+
24
+ const StyledComponent = styledShade(mockElement, additionalStyles)
25
+ StyledComponent({ style: existingStyles })
26
+
27
+ expect(mockElement).toHaveBeenCalledTimes(1)
28
+ const [props] = mockElement.mock.calls[0] as [{ style: Record<string, string> }]
29
+ expect(props.style.backgroundColor).toBe('blue')
30
+ expect(props.style.color).toBe('red')
31
+ })
32
+
33
+ it('should override existing styles with additional styles', () => {
34
+ const mockElement = vi.fn().mockReturnValue({} as JSX.Element)
35
+ const additionalStyles = { color: 'red' }
36
+ const existingStyles = { color: 'blue' }
37
+
38
+ const StyledComponent = styledShade(mockElement, additionalStyles)
39
+ StyledComponent({ style: existingStyles })
40
+
41
+ expect(mockElement).toHaveBeenCalledTimes(1)
42
+ const [props] = mockElement.mock.calls[0] as [{ style: Record<string, string> }]
43
+ expect(props.style.color).toBe('red')
44
+ })
45
+
46
+ it('should preserve original props', () => {
47
+ const mockElement = vi.fn().mockReturnValue({} as JSX.Element)
48
+ const additionalStyles = { color: 'red' }
49
+
50
+ const StyledComponent = styledShade<{ className?: string; style?: Partial<CSSStyleDeclaration> }>(
51
+ mockElement,
52
+ additionalStyles,
53
+ )
54
+ StyledComponent({ className: 'my-class' })
55
+
56
+ expect(mockElement).toHaveBeenCalledTimes(1)
57
+ const [props] = mockElement.mock.calls[0] as [{ className: string; style: Record<string, string> }]
58
+ expect(props.className).toBe('my-class')
59
+ expect(props.style.color).toBe('red')
60
+ })
61
+
62
+ it('should pass children correctly', () => {
63
+ const mockElement = vi.fn().mockReturnValue({} as JSX.Element)
64
+ const additionalStyles = { color: 'red' }
65
+ const children = ['child1', 'child2'] as unknown as JSX.Element[]
66
+
67
+ const StyledComponent = styledShade(mockElement, additionalStyles)
68
+ StyledComponent({}, children)
69
+
70
+ expect(mockElement).toHaveBeenCalledTimes(1)
71
+ const [, passedChildren] = mockElement.mock.calls[0] as [unknown, JSX.Element[]]
72
+ expect(passedChildren).toBe(children)
73
+ })
74
+
75
+ it('should return a function that returns JSX.Element', () => {
76
+ const expectedElement = { tagName: 'div' } as unknown as JSX.Element
77
+ const mockElement = vi.fn().mockReturnValue(expectedElement)
78
+ const additionalStyles = { color: 'red' }
79
+
80
+ const StyledComponent = styledShade(mockElement, additionalStyles)
81
+ const result = StyledComponent({})
82
+
83
+ expect(result).toBe(expectedElement)
84
+ })
85
+ })
86
+ })