Files
sirmalloc--ccstatusline/src/tui/components/__tests__/StatusLinePreview.test.ts
T
2026-07-13 12:49:29 +08:00

157 lines
4.3 KiB
TypeScript

import { render } from 'ink';
import { PassThrough } from 'node:stream';
import React from 'react';
import {
describe,
expect,
it
} from 'vitest';
import {
DEFAULT_SETTINGS,
type Settings
} from '../../../types/Settings';
import type { WidgetItem } from '../../../types/Widget';
import { getVisibleWidth } from '../../../utils/ansi';
import { renderOsc8Link } from '../../../utils/hyperlink';
import {
StatusLinePreview,
preparePreviewLineForTerminal
} from '../StatusLinePreview';
class MockTtyStream extends PassThrough {
isTTY = true;
columns = 160;
rows = 40;
setRawMode() {
return this;
}
ref() {
return this;
}
unref() {
return this;
}
}
interface CapturedWriteStream extends NodeJS.WriteStream { getOutput: () => string }
function createMockStdin(): NodeJS.ReadStream {
return new MockTtyStream() as unknown as NodeJS.ReadStream;
}
function createMockStdout(): CapturedWriteStream {
const stream = new MockTtyStream();
const chunks: string[] = [];
stream.on('data', (chunk: Buffer | string) => {
chunks.push(chunk.toString());
});
return Object.assign(stream as unknown as NodeJS.WriteStream, {
getOutput() {
return chunks.join('');
}
});
}
function flushInk() {
return new Promise((resolve) => {
setTimeout(resolve, 25);
});
}
describe('StatusLinePreview helpers', () => {
it('strips OSC links and clamps preview lines to the terminal width', () => {
const line = `${renderOsc8Link(
'https://github.com/owner/repo/pull/42',
'PR #42'
)} OPEN ${'Example PR title '.repeat(8)}`;
const prepared = preparePreviewLineForTerminal(line, 40);
expect(prepared).not.toContain('github.com');
expect(prepared.endsWith('...')).toBe(true);
expect(getVisibleWidth(` ${prepared}`)).toBeLessThanOrEqual(40);
});
it('keeps parens dim scoped in the Ink preview when global bold is active', async () => {
const stdin = createMockStdin();
const stdout = createMockStdout();
const stderr = createMockStdout();
const settings: Settings = {
...DEFAULT_SETTINGS,
colorLevel: 3,
globalBold: true,
powerline: {
...DEFAULT_SETTINGS.powerline,
enabled: true,
theme: 'custom',
separators: ['\uE0B0'],
separatorInvertBackground: [false]
}
};
const lines: WidgetItem[][] = [[
{
id: 'w1',
type: 'custom-text',
customText: 'Cache Hit: 87.0%',
color: 'hex:282C34',
backgroundColor: 'hex:61AFEF'
},
{
id: 'w2',
type: 'custom-text',
customText: 'Cache Read: 12k (64.0%)',
color: 'hex:ABB2BF',
backgroundColor: 'hex:3E4452',
dim: 'parens'
},
{
id: 'w3',
type: 'custom-text',
customText: 'Cache Write: 3k (16.0%)',
color: 'hex:282C34',
backgroundColor: 'hex:98C379'
}
]];
const instance = render(
React.createElement(StatusLinePreview, {
lines,
terminalWidth: 160,
settings
}),
{
stdin,
stdout,
stderr,
debug: true,
exitOnCtrlC: false,
patchConsole: false
}
);
try {
await flushInk();
const output = stdout.getOutput();
const dimIndex = output.indexOf('\x1b[2m(64.0%)');
const resetIndex = output.indexOf('\x1b[22;1m', dimIndex);
const nextWidgetIndex = output.indexOf('Cache Write');
expect(dimIndex).toBeGreaterThanOrEqual(0);
expect(resetIndex).toBeGreaterThan(dimIndex);
expect(resetIndex).toBeLessThan(nextWidgetIndex);
} finally {
instance.unmount();
instance.cleanup();
stdin.destroy();
stdout.destroy();
stderr.destroy();
}
});
});