@talex-touch/utils 1.0.42 → 1.0.45
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/.eslintcache +1 -0
- package/__tests__/cloud-sync-sdk.test.ts +442 -0
- package/__tests__/icons/icons.test.ts +84 -0
- package/__tests__/plugin-sdk-lifecycle.test.ts +130 -0
- package/__tests__/power-sdk.test.ts +143 -0
- package/__tests__/preset-export-types.test.ts +108 -0
- package/__tests__/search/fuzzy-match.test.ts +137 -0
- package/__tests__/transport/port-policy.test.ts +44 -0
- package/__tests__/transport-domain-sdks.test.ts +152 -0
- package/__tests__/types/update.test.ts +67 -0
- package/account/account-sdk.ts +915 -0
- package/account/index.ts +2 -0
- package/account/types.ts +321 -0
- package/analytics/client.ts +136 -0
- package/analytics/index.ts +2 -0
- package/analytics/types.ts +156 -0
- package/animation/auto-resize.ts +322 -0
- package/animation/window-node.ts +26 -19
- package/auth/clerk-types.ts +12 -30
- package/auth/index.ts +0 -2
- package/auth/useAuthState.ts +6 -14
- package/base/index.ts +2 -0
- package/base/log-level.ts +105 -0
- package/channel/index.ts +170 -69
- package/cloud-sync/cloud-sync-sdk.ts +450 -0
- package/cloud-sync/index.ts +1 -0
- package/common/file-scan-utils.ts +17 -9
- package/common/index.ts +4 -0
- package/common/logger/index.ts +46 -0
- package/common/logger/logger-manager.ts +303 -0
- package/common/logger/module-logger.ts +270 -0
- package/common/logger/transport-logger.ts +234 -0
- package/common/logger/types.ts +93 -0
- package/common/search/gather.ts +48 -6
- package/common/search/index.ts +8 -0
- package/common/storage/constants.ts +13 -0
- package/common/storage/entity/app-settings.ts +245 -0
- package/common/storage/entity/index.ts +3 -0
- package/common/storage/entity/layout-atom-types.ts +147 -0
- package/common/storage/entity/openers.ts +1 -0
- package/common/storage/entity/preset-cloud-api.ts +132 -0
- package/common/storage/entity/preset-export-types.ts +256 -0
- package/common/storage/entity/shortcut-settings.ts +1 -0
- package/common/storage/shortcut-storage.ts +11 -0
- package/common/utils/clone-diagnostics.ts +105 -0
- package/common/utils/file.ts +16 -8
- package/common/utils/index.ts +6 -2
- package/common/utils/payload-preview.ts +173 -0
- package/common/utils/polling.ts +167 -13
- package/common/utils/safe-path.ts +103 -0
- package/common/utils/safe-shell.ts +115 -0
- package/common/utils/task-queue.ts +4 -1
- package/core-box/builder/tuff-builder.ts +0 -1
- package/core-box/index.ts +1 -1
- package/core-box/recommendation.ts +38 -1
- package/core-box/tuff/tuff-dsl.ts +32 -0
- package/electron/download-manager.ts +10 -7
- package/electron/env-tool.ts +42 -40
- package/electron/index.ts +0 -1
- package/env/index.ts +156 -0
- package/eslint.config.js +55 -0
- package/i18n/index.ts +62 -0
- package/i18n/locales/en.json +226 -0
- package/i18n/locales/zh.json +226 -0
- package/i18n/message-keys.ts +236 -0
- package/i18n/resolver.ts +181 -0
- package/icons/index.ts +257 -0
- package/icons/svg.ts +69 -0
- package/index.ts +9 -1
- package/intelligence/client.ts +72 -42
- package/market/constants.ts +9 -5
- package/market/index.ts +1 -1
- package/market/types.ts +19 -4
- package/package.json +15 -5
- package/permission/index.ts +143 -46
- package/permission/legacy.ts +26 -0
- package/permission/registry.ts +304 -0
- package/permission/types.ts +164 -0
- package/plugin/channel.ts +68 -39
- package/plugin/index.ts +80 -7
- package/plugin/install.ts +3 -0
- package/plugin/log/types.ts +22 -5
- package/plugin/node/logger-manager.ts +11 -3
- package/plugin/node/logger.ts +24 -17
- package/plugin/preload.ts +25 -2
- package/plugin/providers/index.ts +4 -4
- package/plugin/providers/market-client.ts +6 -3
- package/plugin/providers/npm-provider.ts +22 -7
- package/plugin/providers/tpex-provider.ts +22 -8
- package/plugin/sdk/box-items.ts +14 -0
- package/plugin/sdk/box-sdk.ts +64 -0
- package/plugin/sdk/channel.ts +119 -4
- package/plugin/sdk/clipboard.ts +26 -12
- package/plugin/sdk/cloud-sync.ts +113 -0
- package/plugin/sdk/common.ts +19 -11
- package/plugin/sdk/core-box.ts +6 -15
- package/plugin/sdk/division-box.ts +160 -65
- package/plugin/sdk/examples/storage-onDidChange-example.js +5 -2
- package/plugin/sdk/feature-sdk.ts +111 -76
- package/plugin/sdk/flow.ts +146 -45
- package/plugin/sdk/hooks/bridge.ts +13 -6
- package/plugin/sdk/hooks/life-cycle.ts +35 -16
- package/plugin/sdk/index.ts +14 -3
- package/plugin/sdk/intelligence.ts +87 -0
- package/plugin/sdk/meta/README.md +179 -0
- package/plugin/sdk/meta-sdk.ts +244 -0
- package/plugin/sdk/notification.ts +9 -0
- package/plugin/sdk/plugin-info.ts +64 -0
- package/plugin/sdk/power.ts +155 -0
- package/plugin/sdk/recommend.ts +21 -0
- package/plugin/sdk/service/index.ts +12 -8
- package/plugin/sdk/sqlite.ts +141 -0
- package/plugin/sdk/storage.ts +2 -6
- package/plugin/sdk/system.ts +2 -9
- package/plugin/sdk/temp-files.ts +41 -0
- package/plugin/sdk/touch-sdk.ts +18 -0
- package/plugin/sdk/types.ts +44 -4
- package/plugin/sdk/window/index.ts +12 -9
- package/plugin/sdk-version.ts +231 -0
- package/preload/renderer.ts +3 -2
- package/renderer/hooks/arg-mapper.ts +16 -2
- package/renderer/hooks/index.ts +13 -0
- package/renderer/hooks/initialize.ts +2 -1
- package/renderer/hooks/use-agent-market-sdk.ts +7 -0
- package/renderer/hooks/use-agent-market.ts +106 -0
- package/renderer/hooks/use-agents-sdk.ts +7 -0
- package/renderer/hooks/use-app-sdk.ts +7 -0
- package/renderer/hooks/use-channel.ts +33 -4
- package/renderer/hooks/use-download-sdk.ts +21 -0
- package/renderer/hooks/use-intelligence-sdk.ts +7 -0
- package/renderer/hooks/use-intelligence-stats.ts +290 -0
- package/renderer/hooks/use-intelligence.ts +55 -214
- package/renderer/hooks/use-market-sdk.ts +16 -0
- package/renderer/hooks/use-notification-sdk.ts +7 -0
- package/renderer/hooks/use-permission-sdk.ts +7 -0
- package/renderer/hooks/use-permission.ts +325 -0
- package/renderer/hooks/use-platform-sdk.ts +7 -0
- package/renderer/hooks/use-plugin-sdk.ts +16 -0
- package/renderer/hooks/use-settings-sdk.ts +7 -0
- package/renderer/hooks/use-update-sdk.ts +21 -0
- package/renderer/index.ts +1 -0
- package/renderer/ref.ts +19 -10
- package/renderer/shared/components/SharedPluginDetailContent.vue +84 -0
- package/renderer/shared/components/SharedPluginDetailHeader.vue +116 -0
- package/renderer/shared/components/SharedPluginDetailMetaList.vue +39 -0
- package/renderer/shared/components/SharedPluginDetailReadme.vue +45 -0
- package/renderer/shared/components/SharedPluginDetailVersions.vue +98 -0
- package/renderer/shared/components/index.ts +5 -0
- package/renderer/shared/components/shims-vue.d.ts +5 -0
- package/renderer/shared/index.ts +2 -0
- package/renderer/shared/plugin-detail.ts +62 -0
- package/renderer/storage/app-settings.ts +3 -1
- package/renderer/storage/base-storage.ts +508 -82
- package/renderer/storage/intelligence-storage.ts +31 -40
- package/renderer/storage/openers.ts +3 -1
- package/renderer/storage/storage-subscription.ts +126 -42
- package/renderer/touch-sdk/env.ts +10 -10
- package/renderer/touch-sdk/index.ts +114 -18
- package/renderer/touch-sdk/terminal.ts +24 -13
- package/search/feature-matcher.ts +279 -0
- package/search/fuzzy-match.ts +64 -34
- package/search/index.ts +10 -0
- package/search/levenshtein-utils.ts +17 -11
- package/transport/errors.ts +310 -0
- package/transport/event/builder.ts +378 -0
- package/transport/event/index.ts +7 -0
- package/transport/event/types.ts +292 -0
- package/transport/events/index.ts +2690 -0
- package/transport/events/meta-overlay.ts +79 -0
- package/transport/events/types/agents.ts +177 -0
- package/transport/events/types/app-index.ts +20 -0
- package/transport/events/types/app.ts +475 -0
- package/transport/events/types/box-item.ts +222 -0
- package/transport/events/types/clipboard.ts +80 -0
- package/transport/events/types/core-box.ts +534 -0
- package/transport/events/types/device-idle.ts +7 -0
- package/transport/events/types/division-box.ts +99 -0
- package/transport/events/types/download.ts +115 -0
- package/transport/events/types/file-index.ts +84 -0
- package/transport/events/types/flow.ts +149 -0
- package/transport/events/types/index.ts +70 -0
- package/transport/events/types/market.ts +39 -0
- package/transport/events/types/meta-overlay.ts +184 -0
- package/transport/events/types/notification.ts +140 -0
- package/transport/events/types/permission.ts +90 -0
- package/transport/events/types/platform.ts +8 -0
- package/transport/events/types/plugin.ts +631 -0
- package/transport/events/types/sentry.ts +20 -0
- package/transport/events/types/storage.ts +208 -0
- package/transport/events/types/transport.ts +60 -0
- package/transport/events/types/tray.ts +16 -0
- package/transport/events/types/update.ts +78 -0
- package/transport/index.ts +141 -0
- package/transport/main.ts +2 -0
- package/transport/prelude.ts +208 -0
- package/transport/sdk/constants.ts +29 -0
- package/transport/sdk/domains/agents-market.ts +47 -0
- package/transport/sdk/domains/agents.ts +62 -0
- package/transport/sdk/domains/app.ts +48 -0
- package/transport/sdk/domains/disposable.ts +35 -0
- package/transport/sdk/domains/download.ts +139 -0
- package/transport/sdk/domains/index.ts +13 -0
- package/transport/sdk/domains/intelligence.ts +616 -0
- package/transport/sdk/domains/market.ts +35 -0
- package/transport/sdk/domains/notification.ts +62 -0
- package/transport/sdk/domains/permission.ts +85 -0
- package/transport/sdk/domains/platform.ts +19 -0
- package/transport/sdk/domains/plugin.ts +144 -0
- package/transport/sdk/domains/settings.ts +102 -0
- package/transport/sdk/domains/update.ts +64 -0
- package/transport/sdk/index.ts +60 -0
- package/transport/sdk/main-transport.ts +710 -0
- package/transport/sdk/main.ts +9 -0
- package/transport/sdk/plugin-transport.ts +654 -0
- package/transport/sdk/port-policy.ts +38 -0
- package/transport/sdk/renderer-transport.ts +1165 -0
- package/transport/types.ts +605 -0
- package/types/agent.ts +399 -0
- package/types/cloud-sync.ts +157 -0
- package/types/division-box.ts +31 -31
- package/types/download.ts +1 -0
- package/types/flow.ts +63 -12
- package/types/icon.ts +2 -1
- package/types/index.ts +5 -0
- package/types/intelligence.ts +166 -173
- package/types/modules/base.ts +2 -0
- package/types/path-browserify.d.ts +5 -0
- package/types/platform.ts +12 -0
- package/types/startup-info.ts +32 -0
- package/types/touch-app-core.ts +8 -8
- package/types/update.ts +94 -1
- package/vitest.config.ts +25 -0
- package/auth/useClerkConfig.ts +0 -40
- package/auth/useClerkProvider.ts +0 -52
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { DivisionBoxEvents } from '../transport/events'
|
|
3
|
+
import { createDivisionBoxSDK } from '../plugin/sdk/division-box'
|
|
4
|
+
import { createFeatureSDK } from '../plugin/sdk/feature-sdk'
|
|
5
|
+
import { createMetaSDK } from '../plugin/sdk/meta-sdk'
|
|
6
|
+
|
|
7
|
+
type Handler = (payload: unknown) => unknown
|
|
8
|
+
|
|
9
|
+
function createMockChannel() {
|
|
10
|
+
const listeners = new Map<string, Set<Handler>>()
|
|
11
|
+
|
|
12
|
+
const channel = {
|
|
13
|
+
send: vi.fn(async () => undefined),
|
|
14
|
+
regChannel: vi.fn((eventName: string, handler: Handler) => {
|
|
15
|
+
const bucket = listeners.get(eventName) || new Set<Handler>()
|
|
16
|
+
bucket.add(handler)
|
|
17
|
+
listeners.set(eventName, bucket)
|
|
18
|
+
|
|
19
|
+
return () => {
|
|
20
|
+
const current = listeners.get(eventName)
|
|
21
|
+
current?.delete(handler)
|
|
22
|
+
}
|
|
23
|
+
}),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const emit = async (eventName: string, payload: unknown) => {
|
|
27
|
+
const bucket = listeners.get(eventName)
|
|
28
|
+
if (!bucket) {
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const handler of bucket) {
|
|
33
|
+
await handler(payload)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const listenerCount = (eventName: string) => listeners.get(eventName)?.size ?? 0
|
|
38
|
+
|
|
39
|
+
return { channel, emit, listenerCount }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
describe('plugin sdk lifecycle', () => {
|
|
43
|
+
it('feature sdk removes channel listeners after dispose', async () => {
|
|
44
|
+
const { channel, emit, listenerCount } = createMockChannel()
|
|
45
|
+
const boxItemsAPI = {
|
|
46
|
+
pushItems: vi.fn(),
|
|
47
|
+
update: vi.fn(),
|
|
48
|
+
remove: vi.fn(),
|
|
49
|
+
clear: vi.fn(),
|
|
50
|
+
getItems: vi.fn(() => []),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sdk = createFeatureSDK(boxItemsAPI, channel as any)
|
|
54
|
+
const onInput = vi.fn()
|
|
55
|
+
sdk.onInputChange(onInput)
|
|
56
|
+
|
|
57
|
+
expect(listenerCount('core-box:input-change')).toBe(1)
|
|
58
|
+
|
|
59
|
+
await emit('core-box:input-change', { input: 'hello' })
|
|
60
|
+
expect(onInput).toHaveBeenCalledWith('hello')
|
|
61
|
+
|
|
62
|
+
sdk.dispose()
|
|
63
|
+
expect(listenerCount('core-box:input-change')).toBe(0)
|
|
64
|
+
|
|
65
|
+
await emit('core-box:input-change', { input: 'world' })
|
|
66
|
+
expect(onInput).toHaveBeenCalledTimes(1)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('division box sdk removes state listener after dispose', async () => {
|
|
70
|
+
const { channel, emit, listenerCount } = createMockChannel()
|
|
71
|
+
const sdk = createDivisionBoxSDK(channel as any)
|
|
72
|
+
const onStateChange = vi.fn()
|
|
73
|
+
sdk.onStateChange(onStateChange)
|
|
74
|
+
|
|
75
|
+
const eventName = DivisionBoxEvents.stateChanged.toEventName()
|
|
76
|
+
expect(listenerCount(eventName)).toBe(1)
|
|
77
|
+
|
|
78
|
+
await emit(eventName, { sessionId: 's1', newState: 'active' })
|
|
79
|
+
expect(onStateChange).toHaveBeenCalledWith({ sessionId: 's1', state: 'active' })
|
|
80
|
+
|
|
81
|
+
sdk.dispose()
|
|
82
|
+
expect(listenerCount(eventName)).toBe(0)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('meta sdk clears execute listener after dispose', async () => {
|
|
86
|
+
const { channel, emit, listenerCount } = createMockChannel()
|
|
87
|
+
const sdk = createMetaSDK(channel as any, 'demo-plugin')
|
|
88
|
+
const onExecute = vi.fn()
|
|
89
|
+
|
|
90
|
+
sdk.onActionExecute(onExecute)
|
|
91
|
+
sdk.registerAction({
|
|
92
|
+
id: 'action-1',
|
|
93
|
+
render: {
|
|
94
|
+
basic: {
|
|
95
|
+
title: 'Action',
|
|
96
|
+
subtitle: 'Run action',
|
|
97
|
+
icon: { type: 'class', value: 'i-ri-star-line' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
priority: 1,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(listenerCount('meta-overlay:action-executed')).toBe(1)
|
|
104
|
+
|
|
105
|
+
await emit('meta-overlay:action-executed', {
|
|
106
|
+
pluginId: 'demo-plugin',
|
|
107
|
+
actionId: 'action-1',
|
|
108
|
+
item: {
|
|
109
|
+
id: 'item-1',
|
|
110
|
+
title: { text: 'Item 1' },
|
|
111
|
+
source: { id: 'demo', name: 'Demo' },
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
expect(onExecute).toHaveBeenCalledTimes(1)
|
|
115
|
+
|
|
116
|
+
sdk.dispose()
|
|
117
|
+
expect(listenerCount('meta-overlay:action-executed')).toBe(0)
|
|
118
|
+
|
|
119
|
+
await emit('meta-overlay:action-executed', {
|
|
120
|
+
pluginId: 'demo-plugin',
|
|
121
|
+
actionId: 'action-1',
|
|
122
|
+
item: {
|
|
123
|
+
id: 'item-2',
|
|
124
|
+
title: { text: 'Item 2' },
|
|
125
|
+
source: { id: 'demo', name: 'Demo' },
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
expect(onExecute).toHaveBeenCalledTimes(1)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { AppEvents } from '../transport/events'
|
|
3
|
+
import { createPowerSDK } from '../plugin/sdk/power'
|
|
4
|
+
|
|
5
|
+
type ChannelListener = (payload: unknown) => unknown
|
|
6
|
+
|
|
7
|
+
function createMockChannel(options: {
|
|
8
|
+
sendImpl?: (eventName: string, payload?: unknown) => Promise<unknown>
|
|
9
|
+
} = {}) {
|
|
10
|
+
const listeners = new Map<string, Set<ChannelListener>>()
|
|
11
|
+
|
|
12
|
+
const channel = {
|
|
13
|
+
send: vi.fn(async (eventName: string, payload?: unknown) => {
|
|
14
|
+
if (options.sendImpl) {
|
|
15
|
+
return options.sendImpl(eventName, payload)
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}),
|
|
19
|
+
regChannel: vi.fn((eventName: string, handler: ChannelListener) => {
|
|
20
|
+
const set = listeners.get(eventName) || new Set<ChannelListener>()
|
|
21
|
+
set.add(handler)
|
|
22
|
+
listeners.set(eventName, set)
|
|
23
|
+
|
|
24
|
+
return () => {
|
|
25
|
+
const current = listeners.get(eventName)
|
|
26
|
+
current?.delete(handler)
|
|
27
|
+
}
|
|
28
|
+
}),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const emit = async (eventName: string, payload: unknown) => {
|
|
32
|
+
const set = listeners.get(eventName)
|
|
33
|
+
if (!set || set.size === 0) {
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const handler of set) {
|
|
38
|
+
await handler(payload)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { channel, emit }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('PowerSDK', () => {
|
|
46
|
+
it('returns lowPower=true when on battery and below threshold', async () => {
|
|
47
|
+
const { channel } = createMockChannel({
|
|
48
|
+
sendImpl: async (eventName) => {
|
|
49
|
+
if (eventName === AppEvents.fileIndex.batteryLevel.toEventName()) {
|
|
50
|
+
return { level: 15, charging: false }
|
|
51
|
+
}
|
|
52
|
+
return null
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const sdk = createPowerSDK(channel as any)
|
|
57
|
+
const status = await sdk.getLowPowerStatus({ threshold: 20 })
|
|
58
|
+
|
|
59
|
+
expect(status).toEqual({
|
|
60
|
+
lowPower: true,
|
|
61
|
+
onBattery: true,
|
|
62
|
+
percent: 15,
|
|
63
|
+
threshold: 20,
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('falls back to safe defaults when battery query fails', async () => {
|
|
68
|
+
const { channel } = createMockChannel({
|
|
69
|
+
sendImpl: async () => {
|
|
70
|
+
throw new Error('query failed')
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const sdk = createPowerSDK(channel as any)
|
|
75
|
+
const status = await sdk.getLowPowerStatus({ threshold: 999 })
|
|
76
|
+
|
|
77
|
+
expect(status).toEqual({
|
|
78
|
+
lowPower: false,
|
|
79
|
+
onBattery: false,
|
|
80
|
+
percent: null,
|
|
81
|
+
threshold: 100,
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('isLowPower returns boolean shortcut value', async () => {
|
|
86
|
+
const { channel } = createMockChannel({
|
|
87
|
+
sendImpl: async (eventName) => {
|
|
88
|
+
if (eventName === AppEvents.fileIndex.batteryLevel.toEventName()) {
|
|
89
|
+
return { level: 60, charging: false }
|
|
90
|
+
}
|
|
91
|
+
return null
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const sdk = createPowerSDK(channel as any)
|
|
96
|
+
|
|
97
|
+
await expect(sdk.isLowPower({ threshold: 50 })).resolves.toBe(false)
|
|
98
|
+
await expect(sdk.isLowPower({ threshold: 70 })).resolves.toBe(true)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('onLowPowerChanged emits immediately and deduplicates repeated states', async () => {
|
|
102
|
+
const { channel, emit } = createMockChannel({
|
|
103
|
+
sendImpl: async (eventName) => {
|
|
104
|
+
if (eventName === AppEvents.fileIndex.batteryLevel.toEventName()) {
|
|
105
|
+
return { level: 40, charging: false }
|
|
106
|
+
}
|
|
107
|
+
return null
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const sdk = createPowerSDK(channel as any)
|
|
112
|
+
const callback = vi.fn()
|
|
113
|
+
|
|
114
|
+
const dispose = sdk.onLowPowerChanged(callback, { threshold: 30 })
|
|
115
|
+
|
|
116
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
117
|
+
expect(callback).toHaveBeenCalledTimes(1)
|
|
118
|
+
expect(callback).toHaveBeenLastCalledWith({
|
|
119
|
+
lowPower: false,
|
|
120
|
+
onBattery: true,
|
|
121
|
+
percent: 40,
|
|
122
|
+
threshold: 30,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const batteryStatusEvent = AppEvents.power.batteryStatus.toEventName()
|
|
126
|
+
|
|
127
|
+
await emit(batteryStatusEvent, { onBattery: true, percent: 20 })
|
|
128
|
+
expect(callback).toHaveBeenCalledTimes(2)
|
|
129
|
+
expect(callback).toHaveBeenLastCalledWith({
|
|
130
|
+
lowPower: true,
|
|
131
|
+
onBattery: true,
|
|
132
|
+
percent: 20,
|
|
133
|
+
threshold: 30,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
await emit(batteryStatusEvent, { onBattery: true, percent: 20 })
|
|
137
|
+
expect(callback).toHaveBeenCalledTimes(2)
|
|
138
|
+
|
|
139
|
+
dispose()
|
|
140
|
+
await emit(batteryStatusEvent, { onBattery: false, percent: 90 })
|
|
141
|
+
expect(callback).toHaveBeenCalledTimes(2)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
PRESET_EXPORT_VERSION,
|
|
4
|
+
createPresetExport,
|
|
5
|
+
validatePresetData,
|
|
6
|
+
} from '../common/storage/entity/preset-export-types'
|
|
7
|
+
import type {
|
|
8
|
+
CoreBoxThemeConfig,
|
|
9
|
+
LayoutAtomConfig,
|
|
10
|
+
} from '../common/storage/entity/layout-atom-types'
|
|
11
|
+
|
|
12
|
+
const layoutConfig: LayoutAtomConfig = {
|
|
13
|
+
preset: 'custom',
|
|
14
|
+
header: { border: 'solid', opacity: 1, height: 26, blur: false },
|
|
15
|
+
aside: { position: 'left', width: 68, border: 'solid', opacity: 0.6, collapsed: false },
|
|
16
|
+
view: { radius: [0, 0, 0, 0], shadow: 'none', padding: 0, background: 'transparent' },
|
|
17
|
+
nav: { style: 'icon', activeIndicator: 'dot' },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const coreBoxConfig: CoreBoxThemeConfig = {
|
|
21
|
+
preset: 'custom',
|
|
22
|
+
logo: { position: 'left', size: 24, style: 'default' },
|
|
23
|
+
input: { border: 'bottom', radius: 8, background: 'transparent' },
|
|
24
|
+
results: { itemRadius: 6, itemPadding: 8, divider: false, hoverStyle: 'background' },
|
|
25
|
+
container: { radius: 8, shadow: 'none', border: false },
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('preset-export-types', () => {
|
|
29
|
+
it('validates legacy v1 payloads (layout/coreBox only)', () => {
|
|
30
|
+
const legacyPayload = {
|
|
31
|
+
version: 1,
|
|
32
|
+
exportedAt: new Date().toISOString(),
|
|
33
|
+
meta: {
|
|
34
|
+
name: 'Legacy Preset',
|
|
35
|
+
},
|
|
36
|
+
layout: layoutConfig,
|
|
37
|
+
coreBox: coreBoxConfig,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = validatePresetData(legacyPayload)
|
|
41
|
+
expect(result.valid).toBe(true)
|
|
42
|
+
expect(result.errors).toHaveLength(0)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('validates v2 payload with theme and canvas fields', () => {
|
|
46
|
+
const payload = createPresetExport({
|
|
47
|
+
name: 'V2 Preset',
|
|
48
|
+
channel: 'beta',
|
|
49
|
+
source: 'nexus',
|
|
50
|
+
layout: layoutConfig,
|
|
51
|
+
coreBox: coreBoxConfig,
|
|
52
|
+
theme: {
|
|
53
|
+
window: 'Mica',
|
|
54
|
+
style: { dark: false, auto: true },
|
|
55
|
+
addon: { contrast: false, coloring: true },
|
|
56
|
+
transition: { route: 'slide' },
|
|
57
|
+
},
|
|
58
|
+
mainCanvas: {
|
|
59
|
+
enabled: true,
|
|
60
|
+
preset: 'custom',
|
|
61
|
+
columns: 12,
|
|
62
|
+
rowHeight: 24,
|
|
63
|
+
gap: 8,
|
|
64
|
+
items: [
|
|
65
|
+
{ id: 'header', area: 'header', x: 0, y: 0, w: 12, h: 2 },
|
|
66
|
+
{ id: 'aside', area: 'aside', x: 0, y: 2, w: 2, h: 8 },
|
|
67
|
+
{ id: 'view', area: 'view', x: 2, y: 2, w: 10, h: 8 },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
coreBoxCanvas: {
|
|
71
|
+
enabled: true,
|
|
72
|
+
preset: 'custom',
|
|
73
|
+
columns: 12,
|
|
74
|
+
rowHeight: 24,
|
|
75
|
+
gap: 8,
|
|
76
|
+
items: [
|
|
77
|
+
{ id: 'logo', area: 'logo', x: 0, y: 0, w: 1, h: 1 },
|
|
78
|
+
{ id: 'input', area: 'input', x: 1, y: 0, w: 9, h: 1 },
|
|
79
|
+
{ id: 'actions', area: 'actions', x: 10, y: 0, w: 2, h: 1 },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const result = validatePresetData(payload)
|
|
85
|
+
expect(payload.version).toBe(PRESET_EXPORT_VERSION)
|
|
86
|
+
expect(result.valid).toBe(true)
|
|
87
|
+
expect(result.errors).toHaveLength(0)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('rejects malformed coreBox config without results field', () => {
|
|
91
|
+
const badPayload = {
|
|
92
|
+
version: PRESET_EXPORT_VERSION,
|
|
93
|
+
exportedAt: new Date().toISOString(),
|
|
94
|
+
meta: {
|
|
95
|
+
name: 'Bad CoreBox Preset',
|
|
96
|
+
},
|
|
97
|
+
coreBox: {
|
|
98
|
+
preset: 'custom',
|
|
99
|
+
logo: { position: 'left', size: 24, style: 'default' },
|
|
100
|
+
input: { border: 'bottom', radius: 8, background: 'transparent' },
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = validatePresetData(badPayload)
|
|
105
|
+
expect(result.valid).toBe(false)
|
|
106
|
+
expect(result.errors.some(error => error.includes('CoreBox'))).toBe(true)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { fuzzyMatch, indicesToRanges } from '../../search/fuzzy-match'
|
|
4
|
+
|
|
5
|
+
describe('fuzzyMatch', () => {
|
|
6
|
+
describe('exact match', () => {
|
|
7
|
+
it('should match identical strings with score 1', () => {
|
|
8
|
+
const result = fuzzyMatch('hello', 'hello')
|
|
9
|
+
expect(result.matched).toBe(true)
|
|
10
|
+
expect(result.score).toBe(1)
|
|
11
|
+
expect(result.matchedIndices).toEqual([0, 1, 2, 3, 4])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('should be case insensitive', () => {
|
|
15
|
+
const result = fuzzyMatch('Hello', 'hello')
|
|
16
|
+
expect(result.matched).toBe(true)
|
|
17
|
+
expect(result.score).toBe(1)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should match exact with mixed case', () => {
|
|
21
|
+
const result = fuzzyMatch('HeLLo', 'HELLO')
|
|
22
|
+
expect(result.matched).toBe(true)
|
|
23
|
+
expect(result.score).toBe(1)
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('substring match', () => {
|
|
28
|
+
it('should match substring with score 0.95', () => {
|
|
29
|
+
const result = fuzzyMatch('hello world', 'hello')
|
|
30
|
+
expect(result.matched).toBe(true)
|
|
31
|
+
expect(result.score).toBe(0.95)
|
|
32
|
+
expect(result.matchedIndices).toEqual([0, 1, 2, 3, 4])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should match substring in middle', () => {
|
|
36
|
+
const result = fuzzyMatch('say hello there', 'hello')
|
|
37
|
+
expect(result.matched).toBe(true)
|
|
38
|
+
expect(result.score).toBe(0.95)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('subsequence match', () => {
|
|
43
|
+
it('should match subsequence like "vsc" to "Visual Studio Code"', () => {
|
|
44
|
+
const result = fuzzyMatch('Visual Studio Code', 'vsc')
|
|
45
|
+
expect(result.matched).toBe(true)
|
|
46
|
+
expect(result.score).toBeGreaterThan(0.8)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should match non-consecutive characters', () => {
|
|
50
|
+
const result = fuzzyMatch('abcdef', 'ace')
|
|
51
|
+
expect(result.matched).toBe(true)
|
|
52
|
+
expect(result.matchedIndices).toEqual([0, 2, 4])
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('fuzzy match with errors', () => {
|
|
57
|
+
it('should match with one typo', () => {
|
|
58
|
+
const result = fuzzyMatch('hello', 'helol')
|
|
59
|
+
expect(result.matched).toBe(true)
|
|
60
|
+
expect(result.score).toBeGreaterThan(0.4)
|
|
61
|
+
expect(result.score).toBeLessThan(0.8)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should match with transposed characters', () => {
|
|
65
|
+
const result = fuzzyMatch('hello', 'hlelo')
|
|
66
|
+
expect(result.matched).toBe(true)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should not match with too many errors', () => {
|
|
70
|
+
const result = fuzzyMatch('hello', 'abcde')
|
|
71
|
+
expect(result.matched).toBe(false)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('edge cases', () => {
|
|
76
|
+
it('should return no match for empty query', () => {
|
|
77
|
+
const result = fuzzyMatch('hello', '')
|
|
78
|
+
expect(result.matched).toBe(false)
|
|
79
|
+
expect(result.score).toBe(0)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should return no match for empty target', () => {
|
|
83
|
+
const result = fuzzyMatch('', 'hello')
|
|
84
|
+
expect(result.matched).toBe(false)
|
|
85
|
+
expect(result.score).toBe(0)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should handle single character query', () => {
|
|
89
|
+
const result = fuzzyMatch('hello', 'h')
|
|
90
|
+
expect(result.matched).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should handle query longer than target', () => {
|
|
94
|
+
const result = fuzzyMatch('hi', 'hello')
|
|
95
|
+
expect(result.matched).toBe(false)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('indicesToRanges', () => {
|
|
101
|
+
it('should convert consecutive indices to single range', () => {
|
|
102
|
+
const ranges = indicesToRanges([0, 1, 2, 3, 4])
|
|
103
|
+
expect(ranges).toEqual([{ start: 0, end: 5 }])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should split non-consecutive indices into multiple ranges', () => {
|
|
107
|
+
const ranges = indicesToRanges([0, 2, 4])
|
|
108
|
+
expect(ranges).toEqual([
|
|
109
|
+
{ start: 0, end: 1 },
|
|
110
|
+
{ start: 2, end: 3 },
|
|
111
|
+
{ start: 4, end: 5 },
|
|
112
|
+
])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('should handle mixed consecutive and non-consecutive', () => {
|
|
116
|
+
const ranges = indicesToRanges([0, 1, 2, 5, 6])
|
|
117
|
+
expect(ranges).toEqual([
|
|
118
|
+
{ start: 0, end: 3 },
|
|
119
|
+
{ start: 5, end: 7 },
|
|
120
|
+
])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('should handle empty array', () => {
|
|
124
|
+
const ranges = indicesToRanges([])
|
|
125
|
+
expect(ranges).toEqual([])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('should handle single index', () => {
|
|
129
|
+
const ranges = indicesToRanges([3])
|
|
130
|
+
expect(ranges).toEqual([{ start: 3, end: 4 }])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should handle duplicate indices', () => {
|
|
134
|
+
const ranges = indicesToRanges([0, 0, 1, 1, 2])
|
|
135
|
+
expect(ranges).toEqual([{ start: 0, end: 3 }])
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
+
import { AppEvents, ClipboardEvents, CoreBoxEvents } from '../../transport/events'
|
|
3
|
+
import { isPortChannelEnabled, resolvePortChannelAllowlist } from '../../transport/sdk/port-policy'
|
|
4
|
+
|
|
5
|
+
const ENV_KEY = 'TALEX_TRANSPORT_PORT_CHANNELS'
|
|
6
|
+
const ORIGINAL_ENV = process.env[ENV_KEY]
|
|
7
|
+
|
|
8
|
+
function restoreEnv(): void {
|
|
9
|
+
if (ORIGINAL_ENV === undefined) {
|
|
10
|
+
delete process.env[ENV_KEY]
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
process.env[ENV_KEY] = ORIGINAL_ENV
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
restoreEnv()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('port-policy', () => {
|
|
21
|
+
it('uses default allowlist when env is unset', () => {
|
|
22
|
+
delete process.env[ENV_KEY]
|
|
23
|
+
const allowlist = resolvePortChannelAllowlist()
|
|
24
|
+
expect(allowlist.has(ClipboardEvents.change.toEventName())).toBe(true)
|
|
25
|
+
expect(allowlist.has(AppEvents.fileIndex.progress.toEventName())).toBe(true)
|
|
26
|
+
expect(allowlist.has(CoreBoxEvents.search.update.toEventName())).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('parses env override list', () => {
|
|
30
|
+
process.env[ENV_KEY] = [
|
|
31
|
+
CoreBoxEvents.search.update.toEventName(),
|
|
32
|
+
ClipboardEvents.change.toEventName(),
|
|
33
|
+
].join(',')
|
|
34
|
+
const allowlist = resolvePortChannelAllowlist()
|
|
35
|
+
expect(allowlist.has(CoreBoxEvents.search.update.toEventName())).toBe(true)
|
|
36
|
+
expect(allowlist.has(ClipboardEvents.change.toEventName())).toBe(true)
|
|
37
|
+
expect(allowlist.has(AppEvents.fileIndex.progress.toEventName())).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('disables allowlist when env is blank', () => {
|
|
41
|
+
process.env[ENV_KEY] = ' '
|
|
42
|
+
expect(isPortChannelEnabled(ClipboardEvents.change.toEventName())).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
})
|