88 lines
3.8 KiB
TypeScript
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/);
|
||
|
|
});
|