Add CI workflow and fix workspace tests

This commit is contained in:
Peter Steinberger 2025-12-02 12:12:17 +00:00
parent 30f69c5f83
commit c43f1d307c
11 changed files with 192 additions and 51 deletions

View file

@ -48,6 +48,7 @@ export class Markdown implements Component {
private paddingY: number; // Top/bottom padding
private defaultTextStyle?: DefaultTextStyle;
private theme: MarkdownTheme;
private defaultStylePrefix?: string;
// Cache for rendered output
private cachedText?: string;
@ -193,6 +194,40 @@ export class Markdown implements Component {
return styled;
}
private getDefaultStylePrefix(): string {
if (!this.defaultTextStyle) {
return "";
}
if (this.defaultStylePrefix !== undefined) {
return this.defaultStylePrefix;
}
const sentinel = "\u0000";
let styled = sentinel;
if (this.defaultTextStyle.color) {
styled = this.defaultTextStyle.color(styled);
}
if (this.defaultTextStyle.bold) {
styled = this.theme.bold(styled);
}
if (this.defaultTextStyle.italic) {
styled = this.theme.italic(styled);
}
if (this.defaultTextStyle.strikethrough) {
styled = this.theme.strikethrough(styled);
}
if (this.defaultTextStyle.underline) {
styled = this.theme.underline(styled);
}
const sentinelIndex = styled.indexOf(sentinel);
this.defaultStylePrefix = sentinelIndex >= 0 ? styled.slice(0, sentinelIndex) : "";
return this.defaultStylePrefix;
}
private renderToken(token: Token, width: number, nextTokenType?: string): string[] {
const lines: string[] = [];
@ -302,20 +337,20 @@ export class Markdown implements Component {
case "strong": {
// Apply bold, then reapply default style after
const boldContent = this.renderInlineTokens(token.tokens || []);
result += this.theme.bold(boldContent) + this.applyDefaultStyle("");
result += this.theme.bold(boldContent) + this.getDefaultStylePrefix();
break;
}
case "em": {
// Apply italic, then reapply default style after
const italicContent = this.renderInlineTokens(token.tokens || []);
result += this.theme.italic(italicContent) + this.applyDefaultStyle("");
result += this.theme.italic(italicContent) + this.getDefaultStylePrefix();
break;
}
case "codespan":
// Apply code styling without backticks
result += this.theme.code(token.text) + this.applyDefaultStyle("");
result += this.theme.code(token.text) + this.getDefaultStylePrefix();
break;
case "link": {
@ -323,12 +358,12 @@ export class Markdown implements Component {
// If link text matches href, only show the link once
// Compare raw text (token.text) not styled text (linkText) since linkText has ANSI codes
if (token.text === token.href) {
result += this.theme.link(this.theme.underline(linkText)) + this.applyDefaultStyle("");
result += this.theme.link(this.theme.underline(linkText)) + this.getDefaultStylePrefix();
} else {
result +=
this.theme.link(this.theme.underline(linkText)) +
this.theme.linkUrl(` (${token.href})`) +
this.applyDefaultStyle("");
this.getDefaultStylePrefix();
}
break;
}
@ -339,7 +374,7 @@ export class Markdown implements Component {
case "del": {
const delContent = this.renderInlineTokens(token.tokens || []);
result += this.theme.strikethrough(delContent) + this.applyDefaultStyle("");
result += this.theme.strikethrough(delContent) + this.getDefaultStylePrefix();
break;
}

View file

@ -179,7 +179,7 @@ function wrapSingleLine(line: string, width: number): string[] {
if (totalNeeded > width && currentVisibleLength > 0) {
// Wrap to next line - don't carry trailing whitespace
wrapped.push(currentLine);
wrapped.push(currentLine.trimEnd());
if (isWhitespace) {
// Don't start new line with whitespace
currentLine = tracker.getActiveCodes();

View file

@ -1,9 +1,12 @@
import assert from "node:assert";
import { describe, it } from "node:test";
import chalk from "chalk";
import { Chalk } from "chalk";
import { Markdown } from "../src/components/markdown.js";
import { defaultMarkdownTheme } from "./test-themes.js";
// Force full color in CI so ANSI assertions are deterministic
const chalk = new Chalk({ level: 3 });
describe("Markdown component", () => {
describe("Nested lists", () => {
it("should render simple nested list", () => {
@ -218,9 +221,9 @@ describe("Markdown component", () => {
assert.ok(joinedOutput.includes("\x1b[90m"), "Should have gray color code");
assert.ok(joinedOutput.includes("\x1b[3m"), "Should have italic code");
// Verify that after the inline code (cyan text), we reapply gray italic
const hasCyan = joinedOutput.includes("\x1b[36m"); // cyan
assert.ok(hasCyan, "Should have cyan for inline code");
// Verify that inline code is styled (theme uses yellow)
const hasCodeColor = joinedOutput.includes("\x1b[33m");
assert.ok(hasCodeColor, "Should style inline code");
});
it("should preserve gray italic styling after bold text", () => {

View file

@ -2,9 +2,11 @@
* Default themes for TUI tests using chalk
*/
import chalk from "chalk";
import { Chalk } from "chalk";
import type { EditorTheme, MarkdownTheme, SelectListTheme } from "../src/index.js";
const chalk = new Chalk({ level: 3 });
export const defaultSelectListTheme: SelectListTheme = {
selectedPrefix: (text: string) => chalk.blue(text),
selectedText: (text: string) => chalk.bold(text),

View file

@ -1,9 +1,12 @@
import assert from "node:assert";
import { describe, it } from "node:test";
import chalk from "chalk";
import { Chalk } from "chalk";
import { TruncatedText } from "../src/components/truncated-text.js";
import { visibleWidth } from "../src/utils.js";
// Force full color in CI so ANSI assertions are deterministic
const chalk = new Chalk({ level: 3 });
describe("TruncatedText component", () => {
it("pads output lines to exactly match width", () => {
const text = new TruncatedText("Hello world", 1, 0);