Files
NetMesh/components/ai/aiMarkdownWarmup.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

88 lines
3.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { readFileSync } from 'node:fs';
import {
AI_COMPOSER_IDLE_MS,
AI_MARKDOWN_WARMUP_INITIAL_DELAY_MS,
AI_MARKDOWN_WARMUP_RESUME_DELAY_MS,
isAiComposerBusy,
isAiComposerTarget,
markAiComposerActivity,
resolveAiMarkdownWarmupDelay,
shouldDeferAiMarkdownWarmup,
} from './aiMarkdownWarmup';
test('defers markdown warmup while the composer is focused, composing, or recently active', () => {
assert.equal(shouldDeferAiMarkdownWarmup({}), false);
assert.equal(shouldDeferAiMarkdownWarmup({ composerFocused: true }), true);
assert.equal(shouldDeferAiMarkdownWarmup({ isComposing: true }), true);
assert.equal(shouldDeferAiMarkdownWarmup({ recentlyActive: true }), true);
});
test('recent composer activity counts as busy', () => {
markAiComposerActivity();
assert.equal(isAiComposerBusy(), true);
assert.ok(AI_COMPOSER_IDLE_MS >= 2000);
});
test('recognizes the chat composer as a busy warmup target', () => {
const body = { closest: (sel: string) => (sel.includes('ai-chat-input-body') ? {} : null) };
assert.equal(isAiComposerTarget(body as unknown as EventTarget), true);
assert.equal(isAiComposerTarget(null), false);
});
test('composer-idle IPC waits the same expand grace as history markdown', () => {
const warmup = readFileSync(new URL('./aiMarkdownWarmup.ts', import.meta.url), 'utf8');
assert.match(warmup, /initialDelayMs:\s*options\?\.initialDelayMs \?\? AI_MARKDOWN_WARMUP_INITIAL_DELAY_MS/);
assert.match(warmup, /isBusy: isAiComposerBusy/);
assert.match(warmup, /markAiComposerActivity\(\);\n\s*arm\(\);/);
assert.match(warmup, /if \(isAiComposerTyping\(\)\) \{\s*hydrateScheduled = true;/s);
});
test('history markdown waits after expand, then resumes quickly after blur', () => {
assert.equal(AI_MARKDOWN_WARMUP_INITIAL_DELAY_MS, 4000);
assert.equal(AI_MARKDOWN_WARMUP_RESUME_DELAY_MS, 600);
assert.equal(resolveAiMarkdownWarmupDelay({
hasArmed: false,
initialDelayMs: AI_MARKDOWN_WARMUP_INITIAL_DELAY_MS,
resumeDelayMs: AI_MARKDOWN_WARMUP_RESUME_DELAY_MS,
}), 4000);
assert.equal(resolveAiMarkdownWarmupDelay({
hasArmed: true,
initialDelayMs: AI_MARKDOWN_WARMUP_INITIAL_DELAY_MS,
resumeDelayMs: AI_MARKDOWN_WARMUP_RESUME_DELAY_MS,
}), 600);
});
test('AI panel no longer starts Streamdown just because it became visible', () => {
const panel = readFileSync(new URL('../AIChatSidePanel.tsx', import.meta.url), 'utf8');
assert.doesNotMatch(panel, /scheduleAiMarkdownWarmup/);
assert.match(panel, /warmAiMarkdownRenderer/);
assert.doesNotMatch(panel, /timeout:\s*2500/);
assert.doesNotMatch(panel, /import\('\.\/ai-elements\/messageResponse'\)/);
assert.doesNotMatch(panel, /@streamdown\/code/);
});
test('chat history defers Streamdown until warmup is already done', () => {
const list = readFileSync(new URL('./ChatMessageList.tsx', import.meta.url), 'utf8');
assert.match(list, /deferUntilWarm/);
assert.match(list, /scheduleAiMarkdownWarmup/);
assert.match(list, /isAiComposerTyping/);
assert.match(list, /AI_MARKDOWN_WARMUP_INITIAL_DELAY_MS/);
assert.match(list, /AI_MARKDOWN_WARMUP_RESUME_DELAY_MS/);
});
test('composer focus alone does not count as typing', () => {
assert.equal(shouldDeferAiMarkdownWarmup({ composerFocused: true }), true);
assert.equal(shouldDeferAiMarkdownWarmup({ composerFocused: true, isComposing: false, recentlyActive: false }), true);
assert.equal(shouldDeferAiMarkdownWarmup({ isComposing: false, recentlyActive: false }), false);
});
test('LazyMessageResponse keeps plaintext while chat asks to defer', () => {
const source = readFileSync(new URL('../ai-elements/LazyMessageResponse.tsx', import.meta.url), 'utf8');
assert.match(source, /deferUntilWarm/);
assert.match(source, /enqueueChatMarkdownHydrate/);
assert.match(source, /isAiMarkdownRendererReady/);
});