fix: coverage

This commit is contained in:
imccyu
2026-08-09 18:56:29 +08:00
parent 1b1e170c1f
commit 924cfcef95
12 changed files with 46 additions and 28 deletions

View File

@@ -1,6 +1,10 @@
import type { SessionEvent } from '@deepseek-ai/dsh-session/types'
import type { ToolEventView } from '@deepseek-ai/dsh-client-connection/client'
/* oxlint-disable typescript/no-duplicate-type-constituents, typescript/no-redundant-type-constituents --
* The unaugmented declaration-merge maps intentionally resolve to never in the Runtime program;
* installed business packages supply their concrete keys in consuming Client programs. */
/** One raw log event plus its optional envelope-level presentation view. */
export interface ConversationEventInput {
readonly event: SessionEvent

View File

@@ -712,9 +712,8 @@ export class ConversationNodeAssembler {
context: InternalContext,
scope: ConversationLocationDataScope,
): ConversationLocationData | null {
const build = context.definition.buildLocationData
if (build === undefined) return null
const data = build(contextSnapshot(context), scope)
if (context.definition.buildLocationData === undefined) return null
const data = context.definition.buildLocationData(contextSnapshot(context), scope)
if (data === null) return null
if (data.kind !== scope) {
throw new Error(

View File

@@ -20,7 +20,7 @@ export interface ConversationLocationDataChange {
class MutableLocationDataStore {
private entries = new Map<string, OwnedLocationData>()
get<Key extends string>(key: Key): unknown {
get(key: string): unknown {
return this.entries.get(key)?.value
}

View File

@@ -18,6 +18,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
'trt.panel': { kind: 'single'; scope: 'root'; owner: { label?: string } }
'trt.chat': { kind: 'single'; scope: 'session' }
'trt.rows': { kind: 'list'; scope: 'root' }
'trt.rows.hole': { kind: 'single'; scope: 'root' }
}
}
@@ -419,7 +420,7 @@ describe('feature mount and disposal', () => {
await feature.dispose()
await feature.dispose() // idempotent
expect(runtime.slots.entries('trt.rows')).toHaveLength(0)
expect(runtime.slots.spec('trt.rows.hole' as never)).toBeUndefined()
expect(runtime.slots.spec('trt.rows.hole')).toBeUndefined()
expect(runtime.ctx.get('feature-service')).toBeUndefined()
expect(view.queryByTestId('row')).toBeNull()
await runtime.dispose()

View File

@@ -7,7 +7,7 @@ import type {
export interface ChatNodeDataMap {}
/** Renderer kinds contributed by the currently installed Chat business modules. */
export type ChatNodeKind = keyof ChatNodeDataMap & string
export type ChatNodeKind = Extract<keyof ChatNodeDataMap, string>
/** Final Chat Node narrowed to one registered renderer kind and payload. */
export type ChatNode<Kind extends ChatNodeKind = ChatNodeKind> = {

View File

@@ -210,7 +210,7 @@ export interface TurnTailOwnerProps {
}
/** Hook constrained to business data published on the current Chat Node's Turn. */
export type UseChatNodeTurnData = <Key extends keyof ConversationTurnDataMap & string>(
export type UseChatNodeTurnData = <Key extends Extract<keyof ConversationTurnDataMap, string>>(
key: Key,
) => Readonly<ConversationTurnDataMap[Key]> | undefined

View File

@@ -78,13 +78,16 @@ class FixtureLocationIndex implements ChatLocationNodeIndex {
class FixtureTurnDataStore implements ConversationLocationDataStore<ConversationTurnDataMap> {
private readonly values = new Map<string, unknown>()
get<Key extends keyof ConversationTurnDataMap & string>(
get<Key extends Extract<keyof ConversationTurnDataMap, string>>(
key: Key,
): Readonly<ConversationTurnDataMap[Key]> | undefined {
return this.values.get(key) as Readonly<ConversationTurnDataMap[Key]> | undefined
}
set<Key extends keyof ConversationTurnDataMap & string>(key: Key, value: ConversationTurnDataMap[Key]): void {
set<Key extends Extract<keyof ConversationTurnDataMap, string>>(
key: Key,
value: ConversationTurnDataMap[Key],
): void {
this.values.set(key, value)
}
}

View File

@@ -181,12 +181,14 @@ function makeHarness(init?: Partial<ConversationSnapshot>) {
if (key !== 'conversation.chat.node') return opts?.fallback ?? null
const nodeOwner = owner as RoutedChatNodeOwner
const nodeKey = opts?.hookContext as string | undefined
const useTurnData = ((dataKey: string) => props.useSession((snapshot) => {
const location = nodeKey === undefined ? undefined : snapshot.chat.nodes.get(nodeKey)?.location
return location?.kind === 'turn' || location?.kind === 'step'
? location.turn.data.get(dataKey as never)
: undefined
})) as UseChatNodeTurnData
const useTurnData = ((dataKey: string) => {
return props.useSession((snapshot) => {
const location = nodeKey === undefined ? undefined : snapshot.chat.nodes.get(nodeKey)?.location
return location?.kind === 'turn' || location?.kind === 'step'
? location.turn.data.get(dataKey as never)
: undefined
})
}) as UseChatNodeTurnData
const nodeProps = <Kind extends ChatNode['kind']>(): ChatNodeViewProps<Kind> => (
{ ...props, ...nodeOwner, useTurnData } as unknown as ChatNodeViewProps<Kind>
)
@@ -226,7 +228,7 @@ function makeHarness(init?: Partial<ConversationSnapshot>) {
case 'unknown':
return <UnknownNodeView {...nodeProps<'unknown'>()} />
case 'tool-call': {
const block = (nodeOwner.node.data as { readonly root: ToolCallBlock }).root
const block = nodeOwner.node.data.root
const toolName = 'kind' in block ? block.call?.name ?? '' : block.name
const tool = {
callId: block.callId,
@@ -843,7 +845,7 @@ describe('ChatView', () => {
mounted()
return () => { unmounted() }
}, [])
const root = (node.data as { readonly root: ToolCallBlock }).root
const root = node.data.root
return (
<div data-testid="stateful-tool" data-state={'kind' in root ? 'settled' : 'running'}>
{root.callId}

View File

@@ -34,13 +34,16 @@ afterEach(cleanup)
class TestTurnDataStore implements ConversationLocationDataStore<ConversationTurnDataMap> {
private readonly values = new Map<string, unknown>()
get<Key extends keyof ConversationTurnDataMap & string>(
get<Key extends Extract<keyof ConversationTurnDataMap, string>>(
key: Key,
): Readonly<ConversationTurnDataMap[Key]> | undefined {
return this.values.get(key) as Readonly<ConversationTurnDataMap[Key]> | undefined
}
set<Key extends keyof ConversationTurnDataMap & string>(key: Key, value: ConversationTurnDataMap[Key]): void {
set<Key extends Extract<keyof ConversationTurnDataMap, string>>(
key: Key,
value: ConversationTurnDataMap[Key],
): void {
this.values.set(key, value)
}
}

View File

@@ -44,8 +44,8 @@ describe('ui-settings apply', () => {
declare(before.slots)
await before.ctx.plugin({ inject: [...inject], apply }).await()
expect(before.slots.entries('sidebar.settings')[0]!.component).toBe(SettingsRoot)
for (const [name, spec] of Object.entries(CHILD_SPECS)) {
expect(before.slots.spec(name as never)).toEqual(spec)
for (const name of Object.keys(CHILD_SPECS) as Array<keyof typeof CHILD_SPECS>) {
expect(before.slots.spec(name)).toEqual(CHILD_SPECS[name])
}
const after = await bench()
@@ -120,8 +120,8 @@ describe('ui-settings apply', () => {
declare(b.slots)
await Promise.resolve()
expect(b.slots.entries('sidebar.settings')[0]!.component).toBe(SettingsRoot)
for (const [name, spec] of Object.entries(CHILD_SPECS)) {
expect(b.slots.spec(name as never)).toEqual(spec)
for (const name of Object.keys(CHILD_SPECS) as Array<keyof typeof CHILD_SPECS>) {
expect(b.slots.spec(name)).toEqual(CHILD_SPECS[name])
}
})
@@ -132,8 +132,8 @@ describe('ui-settings apply', () => {
await fiber.await()
await fiber.dispose()
expect(b.slots.entries('sidebar.settings')).toHaveLength(0)
for (const name of Object.keys(CHILD_SPECS)) {
expect(b.slots.spec(name as never)).toBeUndefined()
for (const name of Object.keys(CHILD_SPECS) as Array<keyof typeof CHILD_SPECS>) {
expect(b.slots.spec(name)).toBeUndefined()
}
})
})

View File

@@ -677,7 +677,10 @@ export class SlotCore {
>(
options: BaseOptions<K, EntryKey, D, H, M, N> & { inject?: undefined },
component: C
& SlotComponent<ComposedProps<K, NoInfer<EntryKey>, keyof NoInfer<D> & keyof SlotMap & string, HandleOf<NoInfer<H>>, object, NoInfer<M>, NoInfer<N>>>
& SlotComponent<ComposedProps<
K, NoInfer<EntryKey>, keyof NoInfer<D> & keyof SlotMap & string,
HandleOf<NoInfer<H>>, object, NoInfer<M>, NoInfer<N>
>>
& RendersCheck<C, D>,
): () => void
/**
@@ -702,7 +705,10 @@ export class SlotCore {
>(
options: BaseOptions<K, EntryKey, D, H, M, N> & { inject: (...args: InjectParams<K, H>) => I },
component: C
& SlotComponent<ComposedProps<K, NoInfer<EntryKey>, keyof NoInfer<D> & keyof SlotMap & string, HandleOf<NoInfer<H>>, I, NoInfer<M>, NoInfer<N>>>
& SlotComponent<ComposedProps<
K, NoInfer<EntryKey>, keyof NoInfer<D> & keyof SlotMap & string,
HandleOf<NoInfer<H>>, I, NoInfer<M>, NoInfer<N>
>>
& RendersCheck<C, D>,
): () => void
/* jscpd:ignore-end */

View File

@@ -274,7 +274,7 @@ export class WorkspaceAnalyzer {
constructor(options: WorkspaceAnalyzerOptions) {
this.options = {
root: resolve(options.root),
root: realPath(options.root),
hostConfig: options.hostConfig ?? 'tsconfig.host.json',
clientConfig: options.clientConfig ?? 'tsconfig.client.json',
faces: options.faces ?? ['host', 'client'],