@lukso/core 1.1.0-dev.a98e9bc → 1.1.0-dev.c21633f
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 +55 -0
- package/dist/chains/index.cjs +1 -0
- package/dist/chains/index.cjs.map +1 -1
- package/dist/chains/index.js +1 -0
- package/dist/chunk-CUDG6NPH.cjs +111 -0
- package/dist/chunk-CUDG6NPH.cjs.map +1 -0
- package/dist/chunk-DWXFDFMM.cjs +1 -0
- package/dist/chunk-DWXFDFMM.cjs.map +1 -0
- package/dist/chunk-EUXUH3YW.js +15 -0
- package/dist/chunk-GFLV5EJV.js +159 -0
- package/dist/chunk-GFLV5EJV.js.map +1 -0
- package/dist/chunk-JEE6C34P.js +1 -0
- package/dist/chunk-JEE6C34P.js.map +1 -0
- package/dist/chunk-LQIOVPBE.js +111 -0
- package/dist/chunk-LQIOVPBE.js.map +1 -0
- package/dist/chunk-QU6NUTY6.cjs +159 -0
- package/dist/chunk-QU6NUTY6.cjs.map +1 -0
- package/dist/chunk-ZBDE64SD.cjs +15 -0
- package/dist/chunk-ZBDE64SD.cjs.map +1 -0
- package/dist/config.cjs +1 -0
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +1 -0
- package/dist/index.cjs +14 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +13 -4
- package/dist/mixins/device.cjs +1 -0
- package/dist/mixins/device.cjs.map +1 -1
- package/dist/mixins/device.js +1 -0
- package/dist/mixins/index.cjs +7 -2
- package/dist/mixins/index.cjs.map +1 -1
- package/dist/mixins/index.d.cts +1 -0
- package/dist/mixins/index.d.ts +1 -0
- package/dist/mixins/index.js +7 -2
- package/dist/mixins/intl.cjs +1 -0
- package/dist/mixins/intl.cjs.map +1 -1
- package/dist/mixins/intl.js +1 -0
- package/dist/mixins/theme.cjs +8 -0
- package/dist/mixins/theme.cjs.map +1 -0
- package/dist/mixins/theme.d.cts +45 -0
- package/dist/mixins/theme.d.ts +45 -0
- package/dist/mixins/theme.js +8 -0
- package/dist/mixins/theme.js.map +1 -0
- package/dist/services/device.cjs +1 -0
- package/dist/services/device.cjs.map +1 -1
- package/dist/services/device.js +1 -0
- package/dist/services/index.cjs +1 -0
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/intl.cjs +1 -0
- package/dist/services/intl.cjs.map +1 -1
- package/dist/services/intl.js +1 -0
- package/dist/utils/index.cjs +7 -2
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.cts +34 -1
- package/dist/utils/index.d.ts +34 -1
- package/dist/utils/index.js +6 -1
- package/package.json +7 -1
- package/src/mixins/__tests__/theme.spec.ts +478 -0
- package/src/mixins/index.ts +1 -0
- package/src/mixins/theme.ts +172 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/url-resolver.ts +93 -0
- package/dist/chunk-AMRGSLR5.cjs +0 -1
- package/dist/chunk-AMRGSLR5.cjs.map +0 -1
- package/dist/chunk-DKEXQFNE.js +0 -1
- package/dist/chunk-DKXHVRHM.js +0 -84
- package/dist/chunk-DKXHVRHM.js.map +0 -1
- package/dist/chunk-MBIRTPNM.cjs +0 -84
- package/dist/chunk-MBIRTPNM.cjs.map +0 -1
- /package/dist/{chunk-DKEXQFNE.js.map → chunk-EUXUH3YW.js.map} +0 -0
package/dist/services/intl.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/services/intl.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACF,8SAAC","file":"/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/services/intl.cjs"}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/services/intl.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACF,8SAAC","file":"/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/services/intl.cjs"}
|
package/dist/services/intl.js
CHANGED
package/dist/utils/index.cjs
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var _chunkMBIRTPNMcjs = require('../chunk-MBIRTPNM.cjs');
|
|
6
5
|
|
|
7
6
|
|
|
7
|
+
var _chunkQU6NUTY6cjs = require('../chunk-QU6NUTY6.cjs');
|
|
8
|
+
require('../chunk-ZBDE64SD.cjs');
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
exports.EXTENSION_STORE_LINKS = _chunkQU6NUTY6cjs.EXTENSION_STORE_LINKS; exports.UrlConverter = _chunkQU6NUTY6cjs.UrlConverter; exports.UrlResolver = _chunkQU6NUTY6cjs.UrlResolver; exports.browserInfo = _chunkQU6NUTY6cjs.browserInfo; exports.slug = _chunkQU6NUTY6cjs.slug;
|
|
11
16
|
//# sourceMappingURL=index.cjs.map
|
package/dist/utils/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/utils/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,yDAA8B;AAC9B;AACE;AACA;AACA;AACF,
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/utils/index.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACA;AACA;AACA;AACA;AACF,gRAAC","file":"/home/runner/work/service-auth-simple/service-auth-simple/packages/core/dist/utils/index.cjs"}
|
package/dist/utils/index.d.cts
CHANGED
|
@@ -29,4 +29,37 @@ declare const browserInfo: (deviceService: DeviceService) => BrowserInfo;
|
|
|
29
29
|
*/
|
|
30
30
|
declare const slug: (value?: string) => string;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
declare class UrlConverter {
|
|
33
|
+
private destination;
|
|
34
|
+
/**
|
|
35
|
+
* It will relatively append pathname or hostname to the destination URL
|
|
36
|
+
*
|
|
37
|
+
* For example:
|
|
38
|
+
* destination=https://some.api.gateway/something/ipfs
|
|
39
|
+
* url=ipfs://QmSomeHash
|
|
40
|
+
* output=https://some.api.gateway/something/ipfs/QmSomeHash
|
|
41
|
+
*
|
|
42
|
+
* destination=https://some.api.gateway/something/ipfs
|
|
43
|
+
* url=https://something.com/somewhere
|
|
44
|
+
* output=https://some.api.gateway/something/ipfs/somewhere
|
|
45
|
+
*
|
|
46
|
+
* @param destination destination string | URL
|
|
47
|
+
*/
|
|
48
|
+
constructor(destination: string | URL);
|
|
49
|
+
resolveUrl(url: string): string;
|
|
50
|
+
}
|
|
51
|
+
declare class UrlResolver {
|
|
52
|
+
private converters;
|
|
53
|
+
constructor(converters: Array<[string | RegExp, UrlConverter | string]>);
|
|
54
|
+
/**
|
|
55
|
+
* Resolves a URL to a gateway URL.
|
|
56
|
+
* Supports possible multiple converters transforming the URL
|
|
57
|
+
* in sequence until no converter matches.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} url to resolve
|
|
60
|
+
* @returns {string} resolved url (if resolver is found, otherwise the parameter url is returned)
|
|
61
|
+
*/
|
|
62
|
+
resolveUrl(url_: string): string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { type BrowserInfo, type BrowserName, EXTENSION_STORE_LINKS, UrlConverter, UrlResolver, browserInfo, slug };
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -29,4 +29,37 @@ declare const browserInfo: (deviceService: DeviceService) => BrowserInfo;
|
|
|
29
29
|
*/
|
|
30
30
|
declare const slug: (value?: string) => string;
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
declare class UrlConverter {
|
|
33
|
+
private destination;
|
|
34
|
+
/**
|
|
35
|
+
* It will relatively append pathname or hostname to the destination URL
|
|
36
|
+
*
|
|
37
|
+
* For example:
|
|
38
|
+
* destination=https://some.api.gateway/something/ipfs
|
|
39
|
+
* url=ipfs://QmSomeHash
|
|
40
|
+
* output=https://some.api.gateway/something/ipfs/QmSomeHash
|
|
41
|
+
*
|
|
42
|
+
* destination=https://some.api.gateway/something/ipfs
|
|
43
|
+
* url=https://something.com/somewhere
|
|
44
|
+
* output=https://some.api.gateway/something/ipfs/somewhere
|
|
45
|
+
*
|
|
46
|
+
* @param destination destination string | URL
|
|
47
|
+
*/
|
|
48
|
+
constructor(destination: string | URL);
|
|
49
|
+
resolveUrl(url: string): string;
|
|
50
|
+
}
|
|
51
|
+
declare class UrlResolver {
|
|
52
|
+
private converters;
|
|
53
|
+
constructor(converters: Array<[string | RegExp, UrlConverter | string]>);
|
|
54
|
+
/**
|
|
55
|
+
* Resolves a URL to a gateway URL.
|
|
56
|
+
* Supports possible multiple converters transforming the URL
|
|
57
|
+
* in sequence until no converter matches.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} url to resolve
|
|
60
|
+
* @returns {string} resolved url (if resolver is found, otherwise the parameter url is returned)
|
|
61
|
+
*/
|
|
62
|
+
resolveUrl(url_: string): string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { type BrowserInfo, type BrowserName, EXTENSION_STORE_LINKS, UrlConverter, UrlResolver, browserInfo, slug };
|
package/dist/utils/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EXTENSION_STORE_LINKS,
|
|
3
|
+
UrlConverter,
|
|
4
|
+
UrlResolver,
|
|
3
5
|
browserInfo,
|
|
4
6
|
slug
|
|
5
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-GFLV5EJV.js";
|
|
8
|
+
import "../chunk-EUXUH3YW.js";
|
|
6
9
|
export {
|
|
7
10
|
EXTENSION_STORE_LINKS,
|
|
11
|
+
UrlConverter,
|
|
12
|
+
UrlResolver,
|
|
8
13
|
browserInfo,
|
|
9
14
|
slug
|
|
10
15
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lukso/core",
|
|
3
|
-
"version": "1.1.0-dev.
|
|
3
|
+
"version": "1.1.0-dev.c21633f",
|
|
4
4
|
"description": "Core utilities, services, and mixins for LUKSO web components and applications",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
"import": "./dist/mixins/intl.js",
|
|
53
53
|
"require": "./dist/mixins/intl.cjs"
|
|
54
54
|
},
|
|
55
|
+
"./mixins/theme": {
|
|
56
|
+
"types": "./dist/mixins/theme.d.ts",
|
|
57
|
+
"import": "./dist/mixins/theme.js",
|
|
58
|
+
"require": "./dist/mixins/theme.cjs"
|
|
59
|
+
},
|
|
55
60
|
"./utils": {
|
|
56
61
|
"types": "./dist/utils/index.d.ts",
|
|
57
62
|
"import": "./dist/utils/index.js",
|
|
@@ -111,6 +116,7 @@
|
|
|
111
116
|
"src/mixins/index.ts",
|
|
112
117
|
"src/mixins/device.ts",
|
|
113
118
|
"src/mixins/intl.ts",
|
|
119
|
+
"src/mixins/theme.ts",
|
|
114
120
|
"src/utils/index.ts"
|
|
115
121
|
],
|
|
116
122
|
"format": [
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { withTheme } from '../theme'
|
|
4
|
+
|
|
5
|
+
describe('withTheme Mixin', () => {
|
|
6
|
+
let element: any
|
|
7
|
+
let TestComponent: any
|
|
8
|
+
let tagName: string
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Create a unique tag name for each test to avoid registration conflicts
|
|
12
|
+
tagName = `test-theme-${Math.random().toString(36).slice(2)}`
|
|
13
|
+
|
|
14
|
+
// Create a test component using the mixin
|
|
15
|
+
TestComponent = class extends withTheme(LitElement) {
|
|
16
|
+
render() {
|
|
17
|
+
return html`
|
|
18
|
+
<div class="test-content bg-neutral-100 dark:bg-neutral-10">
|
|
19
|
+
Theme: ${this.theme}
|
|
20
|
+
</div>
|
|
21
|
+
`
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Register the component before instantiation
|
|
26
|
+
if (!customElements.get(tagName)) {
|
|
27
|
+
customElements.define(tagName, TestComponent)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
element = new TestComponent()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
if (element?.parentElement) {
|
|
35
|
+
element.remove()
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('Initialization', () => {
|
|
40
|
+
it('should have default theme property set to "light"', () => {
|
|
41
|
+
expect(element.theme).toBe('light')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should have isDark property set to false by default', () => {
|
|
45
|
+
expect(element.isDark).toBe(false)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should have theme property reflected to attribute', async () => {
|
|
49
|
+
document.body.appendChild(element)
|
|
50
|
+
await element.updateComplete
|
|
51
|
+
|
|
52
|
+
expect(element.hasAttribute('theme')).toBe(true)
|
|
53
|
+
expect(element.getAttribute('theme')).toBe('light')
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('Theme Root Creation', () => {
|
|
58
|
+
it('should create a theme root div', async () => {
|
|
59
|
+
document.body.appendChild(element)
|
|
60
|
+
await element.updateComplete
|
|
61
|
+
|
|
62
|
+
const shadowRoot = element.shadowRoot
|
|
63
|
+
expect(shadowRoot).toBeDefined()
|
|
64
|
+
|
|
65
|
+
const themeRoot = shadowRoot?.querySelector('[data-theme-root]')
|
|
66
|
+
expect(themeRoot).toBeDefined()
|
|
67
|
+
expect(themeRoot?.tagName).toBe('DIV')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should have data-theme-root attribute', async () => {
|
|
71
|
+
document.body.appendChild(element)
|
|
72
|
+
await element.updateComplete
|
|
73
|
+
|
|
74
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
75
|
+
expect(themeRoot?.hasAttribute('data-theme-root')).toBe(true)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should render content inside theme root', async () => {
|
|
79
|
+
document.body.appendChild(element)
|
|
80
|
+
await element.updateComplete
|
|
81
|
+
|
|
82
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
83
|
+
const content = themeRoot?.querySelector('.test-content')
|
|
84
|
+
expect(content).toBeDefined()
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('Light Theme', () => {
|
|
89
|
+
it('should not have dark class when theme is light', async () => {
|
|
90
|
+
element.theme = 'light'
|
|
91
|
+
document.body.appendChild(element)
|
|
92
|
+
await element.updateComplete
|
|
93
|
+
|
|
94
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
95
|
+
expect(themeRoot?.classList.contains('dark')).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should set isDark to false when theme is light', async () => {
|
|
99
|
+
element.theme = 'light'
|
|
100
|
+
document.body.appendChild(element)
|
|
101
|
+
await element.updateComplete
|
|
102
|
+
|
|
103
|
+
expect(element.isDark).toBe(false)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should update attribute when theme is set to light', async () => {
|
|
107
|
+
element.theme = 'light'
|
|
108
|
+
document.body.appendChild(element)
|
|
109
|
+
await element.updateComplete
|
|
110
|
+
|
|
111
|
+
expect(element.getAttribute('theme')).toBe('light')
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('Dark Theme', () => {
|
|
116
|
+
it('should have dark class when theme is dark', async () => {
|
|
117
|
+
element.theme = 'dark'
|
|
118
|
+
document.body.appendChild(element)
|
|
119
|
+
await element.updateComplete
|
|
120
|
+
|
|
121
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
122
|
+
expect(themeRoot?.classList.contains('dark')).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should set isDark to true when theme is dark', async () => {
|
|
126
|
+
element.theme = 'dark'
|
|
127
|
+
document.body.appendChild(element)
|
|
128
|
+
await element.updateComplete
|
|
129
|
+
|
|
130
|
+
expect(element.isDark).toBe(true)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should update attribute when theme is set to dark', async () => {
|
|
134
|
+
element.theme = 'dark'
|
|
135
|
+
document.body.appendChild(element)
|
|
136
|
+
await element.updateComplete
|
|
137
|
+
|
|
138
|
+
expect(element.getAttribute('theme')).toBe('dark')
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe('Auto Theme', () => {
|
|
143
|
+
it('should set theme to auto', () => {
|
|
144
|
+
element.theme = 'auto'
|
|
145
|
+
expect(element.theme).toBe('auto')
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should detect system preference when theme is auto', async () => {
|
|
149
|
+
// Mock matchMedia to return dark mode
|
|
150
|
+
const matchMediaMock = vi.fn((query) => ({
|
|
151
|
+
matches: query === '(prefers-color-scheme: dark)',
|
|
152
|
+
media: query,
|
|
153
|
+
addEventListener: vi.fn(),
|
|
154
|
+
removeEventListener: vi.fn(),
|
|
155
|
+
}))
|
|
156
|
+
vi.stubGlobal('matchMedia', matchMediaMock)
|
|
157
|
+
|
|
158
|
+
element.theme = 'auto'
|
|
159
|
+
document.body.appendChild(element)
|
|
160
|
+
await element.updateComplete
|
|
161
|
+
|
|
162
|
+
expect(element.isDark).toBe(true)
|
|
163
|
+
|
|
164
|
+
vi.unstubAllGlobals()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should listen for system theme changes when theme is auto', async () => {
|
|
168
|
+
const addEventListenerSpy = vi.fn()
|
|
169
|
+
const matchMediaMock = vi.fn(() => ({
|
|
170
|
+
matches: false,
|
|
171
|
+
media: '(prefers-color-scheme: dark)',
|
|
172
|
+
addEventListener: addEventListenerSpy,
|
|
173
|
+
removeEventListener: vi.fn(),
|
|
174
|
+
}))
|
|
175
|
+
vi.stubGlobal('matchMedia', matchMediaMock)
|
|
176
|
+
|
|
177
|
+
element.theme = 'auto'
|
|
178
|
+
document.body.appendChild(element)
|
|
179
|
+
await element.updateComplete
|
|
180
|
+
|
|
181
|
+
expect(addEventListenerSpy).toHaveBeenCalledWith(
|
|
182
|
+
'change',
|
|
183
|
+
expect.any(Function)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
vi.unstubAllGlobals()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should remove event listener when theme changes from auto', async () => {
|
|
190
|
+
const removeEventListenerSpy = vi.fn()
|
|
191
|
+
const addEventListenerSpy = vi.fn()
|
|
192
|
+
const matchMediaMock = vi.fn(() => ({
|
|
193
|
+
matches: false,
|
|
194
|
+
media: '(prefers-color-scheme: dark)',
|
|
195
|
+
addEventListener: addEventListenerSpy,
|
|
196
|
+
removeEventListener: removeEventListenerSpy,
|
|
197
|
+
}))
|
|
198
|
+
vi.stubGlobal('matchMedia', matchMediaMock)
|
|
199
|
+
|
|
200
|
+
element.theme = 'auto'
|
|
201
|
+
document.body.appendChild(element)
|
|
202
|
+
await element.updateComplete
|
|
203
|
+
|
|
204
|
+
// Change theme from auto to light
|
|
205
|
+
element.theme = 'light'
|
|
206
|
+
await element.updateComplete
|
|
207
|
+
|
|
208
|
+
expect(removeEventListenerSpy).toHaveBeenCalled()
|
|
209
|
+
|
|
210
|
+
vi.unstubAllGlobals()
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('Theme Switching', () => {
|
|
215
|
+
it('should switch from light to dark', async () => {
|
|
216
|
+
document.body.appendChild(element)
|
|
217
|
+
element.theme = 'light'
|
|
218
|
+
await element.updateComplete
|
|
219
|
+
await element.updateComplete // Wait for property change update
|
|
220
|
+
|
|
221
|
+
let themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
222
|
+
expect(themeRoot?.classList.contains('dark')).toBe(false)
|
|
223
|
+
|
|
224
|
+
element.theme = 'dark'
|
|
225
|
+
await element.updateComplete
|
|
226
|
+
await element.updateComplete // Wait for property change update
|
|
227
|
+
|
|
228
|
+
themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
229
|
+
expect(themeRoot?.classList.contains('dark')).toBe(true)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should switch from dark to light', async () => {
|
|
233
|
+
document.body.appendChild(element)
|
|
234
|
+
element.theme = 'dark'
|
|
235
|
+
await element.updateComplete
|
|
236
|
+
await element.updateComplete // Wait for property change update
|
|
237
|
+
|
|
238
|
+
let themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
239
|
+
expect(themeRoot?.classList.contains('dark')).toBe(true)
|
|
240
|
+
|
|
241
|
+
element.theme = 'light'
|
|
242
|
+
await element.updateComplete
|
|
243
|
+
await element.updateComplete // Wait for property change update
|
|
244
|
+
|
|
245
|
+
themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
246
|
+
expect(themeRoot?.classList.contains('dark')).toBe(false)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('should update isDark when switching themes', async () => {
|
|
250
|
+
document.body.appendChild(element)
|
|
251
|
+
await element.updateComplete
|
|
252
|
+
|
|
253
|
+
element.theme = 'dark'
|
|
254
|
+
await element.updateComplete
|
|
255
|
+
expect(element.isDark).toBe(true)
|
|
256
|
+
|
|
257
|
+
element.theme = 'light'
|
|
258
|
+
await element.updateComplete
|
|
259
|
+
expect(element.isDark).toBe(false)
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
describe('Multiple Instances', () => {
|
|
264
|
+
it('should work with multiple component instances', async () => {
|
|
265
|
+
const element2 = new TestComponent()
|
|
266
|
+
|
|
267
|
+
element.theme = 'dark'
|
|
268
|
+
element2.theme = 'light'
|
|
269
|
+
|
|
270
|
+
document.body.appendChild(element)
|
|
271
|
+
document.body.appendChild(element2)
|
|
272
|
+
await element.updateComplete
|
|
273
|
+
await element2.updateComplete
|
|
274
|
+
|
|
275
|
+
expect(element.isDark).toBe(true)
|
|
276
|
+
expect(element2.isDark).toBe(false)
|
|
277
|
+
|
|
278
|
+
const themeRoot1 = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
279
|
+
const themeRoot2 = element2.shadowRoot?.querySelector('[data-theme-root]')
|
|
280
|
+
|
|
281
|
+
expect(themeRoot1?.classList.contains('dark')).toBe(true)
|
|
282
|
+
expect(themeRoot2?.classList.contains('dark')).toBe(false)
|
|
283
|
+
|
|
284
|
+
element2.remove()
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should not affect other instances when changing theme', async () => {
|
|
288
|
+
const element2 = new TestComponent()
|
|
289
|
+
|
|
290
|
+
document.body.appendChild(element)
|
|
291
|
+
document.body.appendChild(element2)
|
|
292
|
+
await element.updateComplete
|
|
293
|
+
await element2.updateComplete
|
|
294
|
+
|
|
295
|
+
element.theme = 'dark'
|
|
296
|
+
await element.updateComplete
|
|
297
|
+
|
|
298
|
+
expect(element.isDark).toBe(true)
|
|
299
|
+
expect(element2.isDark).toBe(false)
|
|
300
|
+
|
|
301
|
+
element2.remove()
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
describe('Lifecycle', () => {
|
|
306
|
+
it('should cleanup event listener on disconnect when theme is auto', async () => {
|
|
307
|
+
const removeEventListenerSpy = vi.fn()
|
|
308
|
+
const matchMediaMock = vi.fn(() => ({
|
|
309
|
+
matches: false,
|
|
310
|
+
media: '(prefers-color-scheme: dark)',
|
|
311
|
+
addEventListener: vi.fn(),
|
|
312
|
+
removeEventListener: removeEventListenerSpy,
|
|
313
|
+
}))
|
|
314
|
+
vi.stubGlobal('matchMedia', matchMediaMock)
|
|
315
|
+
|
|
316
|
+
element.theme = 'auto'
|
|
317
|
+
document.body.appendChild(element)
|
|
318
|
+
await element.updateComplete
|
|
319
|
+
|
|
320
|
+
element.remove()
|
|
321
|
+
|
|
322
|
+
expect(removeEventListenerSpy).toHaveBeenCalled()
|
|
323
|
+
|
|
324
|
+
vi.unstubAllGlobals()
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
it('should maintain theme property after disconnection', async () => {
|
|
328
|
+
element.theme = 'dark'
|
|
329
|
+
document.body.appendChild(element)
|
|
330
|
+
await element.updateComplete
|
|
331
|
+
|
|
332
|
+
const themeBefore = element.theme
|
|
333
|
+
const isDarkBefore = element.isDark
|
|
334
|
+
|
|
335
|
+
element.remove()
|
|
336
|
+
|
|
337
|
+
expect(element.theme).toBe(themeBefore)
|
|
338
|
+
expect(element.isDark).toBe(isDarkBefore)
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
describe('Mixin Composition', () => {
|
|
343
|
+
it('should work when composed with other mixins', async () => {
|
|
344
|
+
// Simple test mixin
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
346
|
+
const withTestFeature = <T extends typeof LitElement>(Base: T): any => {
|
|
347
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
348
|
+
return class extends (Base as any) {
|
|
349
|
+
testProperty = 'test'
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
354
|
+
const ComposedComponent: any = class extends withTheme(
|
|
355
|
+
withTestFeature(LitElement)
|
|
356
|
+
) {
|
|
357
|
+
render() {
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
359
|
+
return html`<div>${(this as any).testProperty}</div>`
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const composedTagName = `test-composed-${Math.random().toString(36).slice(2)}`
|
|
364
|
+
customElements.define(composedTagName, ComposedComponent)
|
|
365
|
+
|
|
366
|
+
const composedElement = new ComposedComponent()
|
|
367
|
+
document.body.appendChild(composedElement)
|
|
368
|
+
await composedElement.updateComplete
|
|
369
|
+
|
|
370
|
+
// Should have both theme and test properties
|
|
371
|
+
expect(composedElement.theme).toBeDefined()
|
|
372
|
+
expect(composedElement.isDark).toBeDefined()
|
|
373
|
+
expect(composedElement.testProperty).toBe('test')
|
|
374
|
+
|
|
375
|
+
// Should have theme root
|
|
376
|
+
const themeRoot =
|
|
377
|
+
composedElement.shadowRoot?.querySelector('[data-theme-root]')
|
|
378
|
+
expect(themeRoot).toBeDefined()
|
|
379
|
+
|
|
380
|
+
composedElement.remove()
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
describe('System Preference Changes', () => {
|
|
385
|
+
it('should update isDark when system preference changes in auto mode', async () => {
|
|
386
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
387
|
+
const handlers: any[] = []
|
|
388
|
+
const matchMediaMock = vi.fn(() => ({
|
|
389
|
+
matches: false,
|
|
390
|
+
media: '(prefers-color-scheme: dark)',
|
|
391
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
392
|
+
addEventListener: (_: string, handler: any) => {
|
|
393
|
+
handlers.push(handler)
|
|
394
|
+
},
|
|
395
|
+
removeEventListener: vi.fn(),
|
|
396
|
+
}))
|
|
397
|
+
vi.stubGlobal('matchMedia', matchMediaMock)
|
|
398
|
+
|
|
399
|
+
element.theme = 'auto'
|
|
400
|
+
document.body.appendChild(element)
|
|
401
|
+
await element.updateComplete
|
|
402
|
+
|
|
403
|
+
expect(element.isDark).toBe(false)
|
|
404
|
+
|
|
405
|
+
// Simulate system preference change to dark
|
|
406
|
+
expect(handlers.length).toBeGreaterThan(0)
|
|
407
|
+
handlers[0]({ matches: true })
|
|
408
|
+
await element.updateComplete
|
|
409
|
+
|
|
410
|
+
expect(element.isDark).toBe(true)
|
|
411
|
+
|
|
412
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
413
|
+
expect(themeRoot?.classList.contains('dark')).toBe(true)
|
|
414
|
+
|
|
415
|
+
vi.unstubAllGlobals()
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
it('should update isDark when system preference changes to light in auto mode', async () => {
|
|
419
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
420
|
+
const handlers: any[] = []
|
|
421
|
+
const matchMediaMock = vi.fn(() => ({
|
|
422
|
+
matches: true, // Start with dark
|
|
423
|
+
media: '(prefers-color-scheme: dark)',
|
|
424
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
425
|
+
addEventListener: (_: string, handler: any) => {
|
|
426
|
+
handlers.push(handler)
|
|
427
|
+
},
|
|
428
|
+
removeEventListener: vi.fn(),
|
|
429
|
+
}))
|
|
430
|
+
vi.stubGlobal('matchMedia', matchMediaMock)
|
|
431
|
+
|
|
432
|
+
element.theme = 'auto'
|
|
433
|
+
document.body.appendChild(element)
|
|
434
|
+
await element.updateComplete
|
|
435
|
+
|
|
436
|
+
expect(element.isDark).toBe(true)
|
|
437
|
+
|
|
438
|
+
// Simulate system preference change to light
|
|
439
|
+
expect(handlers.length).toBeGreaterThan(0)
|
|
440
|
+
handlers[0]({ matches: false })
|
|
441
|
+
await element.updateComplete
|
|
442
|
+
|
|
443
|
+
expect(element.isDark).toBe(false)
|
|
444
|
+
|
|
445
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
446
|
+
expect(themeRoot?.classList.contains('dark')).toBe(false)
|
|
447
|
+
|
|
448
|
+
vi.unstubAllGlobals()
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('Edge Cases', () => {
|
|
453
|
+
it('should handle rapid theme changes', () => {
|
|
454
|
+
element.theme = 'dark'
|
|
455
|
+
element.theme = 'light'
|
|
456
|
+
element.theme = 'dark'
|
|
457
|
+
element.theme = 'auto'
|
|
458
|
+
|
|
459
|
+
expect(element.theme).toBe('auto')
|
|
460
|
+
expect(element.isDark).toBeDefined()
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
it('should handle theme changes before connection', () => {
|
|
464
|
+
element.theme = 'dark'
|
|
465
|
+
expect(element.theme).toBe('dark')
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('should properly initialize when connected with pre-set theme', async () => {
|
|
469
|
+
element.theme = 'dark'
|
|
470
|
+
document.body.appendChild(element)
|
|
471
|
+
await element.updateComplete
|
|
472
|
+
|
|
473
|
+
expect(element.isDark).toBe(true)
|
|
474
|
+
const themeRoot = element.shadowRoot?.querySelector('[data-theme-root]')
|
|
475
|
+
expect(themeRoot?.classList.contains('dark')).toBe(true)
|
|
476
|
+
})
|
|
477
|
+
})
|
|
478
|
+
})
|
package/src/mixins/index.ts
CHANGED