73ddf6665c
Extends ctx.os.notify (the curated plugin OS door from #78685) with icon, action buttons, and a serializable `activate` target. Body/action clicks focus the window and navigate to the plugin's screen; activation paths share one resolver (hermes-open-target.ts) with hermes:// OS deep links, so `hermes://index-network/intent/1`, `/index-network/intent/1`, and { path, params } all land on the same hash-router route. Approval notifications keep their existing session-scoped channel. Salvaged from PR #84192 by @serefyarar (net diff of the PR branch applied onto current main; branch carried merge commits so a single authored commit preserves attribution).
64 lines
2.6 KiB
TypeScript
64 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
normalizeHermesOpenString,
|
|
pathFromHermesDeepLink,
|
|
pathFromOpenDeepLink,
|
|
resolveHermesOpenPath
|
|
} from './hermes-open-target'
|
|
|
|
describe('normalizeHermesOpenString', () => {
|
|
it('accepts hash-router paths and strips a leading hash', () => {
|
|
expect(normalizeHermesOpenString('/index-network/intent/1')).toBe('/index-network/intent/1')
|
|
expect(normalizeHermesOpenString('#/index-network/intent/1')).toBe('/index-network/intent/1')
|
|
})
|
|
|
|
it('maps plugin-scoped hermes:// deep links to the same path', () => {
|
|
expect(normalizeHermesOpenString('hermes://index-network/intent/1')).toBe('/index-network/intent/1')
|
|
expect(normalizeHermesOpenString('hermes://index-network/intent/1?focus=true')).toBe(
|
|
'/index-network/intent/1?focus=true'
|
|
)
|
|
})
|
|
|
|
it('maps hermes://open/… deep links by stripping the open host', () => {
|
|
expect(normalizeHermesOpenString('hermes://open/index-network/intent/1')).toBe('/index-network/intent/1')
|
|
expect(normalizeHermesOpenString('hermes://open/settings/plugins')).toBe('/settings/plugins')
|
|
})
|
|
|
|
it('rejects reserved hermes kinds and unsafe paths', () => {
|
|
expect(normalizeHermesOpenString('hermes://blueprint/morning-brief')).toBeNull()
|
|
expect(normalizeHermesOpenString('hermes://plugin/install')).toBeNull()
|
|
expect(normalizeHermesOpenString('https://example.com/x')).toBeNull()
|
|
expect(normalizeHermesOpenString('/../etc/passwd')).toBeNull()
|
|
expect(normalizeHermesOpenString('index-network')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('resolveHermesOpenPath', () => {
|
|
it('merges structured path + params', () => {
|
|
expect(resolveHermesOpenPath({ path: '/index-network/intent/1', params: { focus: 'true' } })).toBe(
|
|
'/index-network/intent/1?focus=true'
|
|
)
|
|
})
|
|
|
|
it('resolves href the same as a bare string', () => {
|
|
expect(resolveHermesOpenPath({ href: 'hermes://index-network/intent/1' })).toBe('/index-network/intent/1')
|
|
})
|
|
})
|
|
|
|
describe('pathFromHermesDeepLink', () => {
|
|
it('builds the navigate path from a plugin-scoped deep-link payload', () => {
|
|
expect(pathFromHermesDeepLink('index-network', 'intent/1')).toBe('/index-network/intent/1')
|
|
})
|
|
|
|
it('builds the navigate path from hermes://open/… payloads', () => {
|
|
expect(pathFromOpenDeepLink('index-network/intent/1')).toBe('/index-network/intent/1')
|
|
expect(pathFromHermesDeepLink('open', 'agent/42')).toBe('/agent/42')
|
|
})
|
|
|
|
it('ignores reserved kinds', () => {
|
|
expect(pathFromHermesDeepLink('blueprint', 'morning-brief')).toBeNull()
|
|
expect(pathFromHermesDeepLink('plugin', 'install')).toBeNull()
|
|
})
|
|
})
|