Files
TUIkit/Tests/TUIkitTests/FrameDiffWriterTests.swift
phranck cd2968e345 Feat: Redesign control styling and add foregroundQuaternary palette color
- Redesign Button rendering with half-block caps (U+2590/U+258C) and accent-tinted background
- Apply matching cap style and background to TextField and SecureField
- Add foregroundQuaternary to Palette for subtle UI elements like spinner tracks
- Fix border saturation in all palette presets to preserve hue instead of going gray
- Rework Spinner bouncing trail to interpolate from highlight to quaternary via Color.lerp
- Fix Spinner label to use palette foreground color
- Fix FrameDiffWriter to use ESC[2K for reliable line clearing
- Add Layoutable conformance to NavigationSplitView for height-flexible layout
- Update tests for new button cap style
2026-02-13 16:18:15 +01:00

247 lines
7.1 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 🖥 TUIKit Terminal UI Kit for Swift
// FrameDiffWriterTests.swift
//
// Created by LAYERED.work
// License: MIT
import Testing
@testable import TUIkit
// MARK: - buildOutputLines Tests
@Suite("FrameDiffWriter buildOutputLines Tests")
@MainActor
struct BuildOutputLinesTests {
@Test("Produces exactly terminalHeight output lines")
func outputLineCount() {
let writer = FrameDiffWriter()
var buffer = FrameBuffer()
buffer.appendVertically(FrameBuffer(text: "Hello"))
buffer.appendVertically(FrameBuffer(text: "World"))
let lines = writer.buildOutputLines(
buffer: buffer,
terminalWidth: 20,
terminalHeight: 5,
bgCode: "",
reset: ""
)
#expect(lines.count == 5)
}
@Test("Content lines include background and padding")
func contentLinesHaveBgAndPadding() {
let writer = FrameDiffWriter()
let buffer = FrameBuffer(text: "Hi")
let lines = writer.buildOutputLines(
buffer: buffer,
terminalWidth: 10,
terminalHeight: 1,
bgCode: "[BG]",
reset: "[R]"
)
let line = lines[0]
#expect(line.hasPrefix("[BG]"))
#expect(line.hasSuffix("[R]"))
#expect(line.contains("Hi"))
}
@Test("Empty rows are filled with background-colored spaces")
func emptyRowsFilled() {
let writer = FrameDiffWriter()
let buffer = FrameBuffer()
let lines = writer.buildOutputLines(
buffer: buffer,
terminalWidth: 5,
terminalHeight: 2,
bgCode: "[BG]",
reset: "[R]"
)
// Empty lines use bgCode + ESC[2K (erase entire line with bg color) + reset
let eraseLine = "\u{1B}[2K"
let expected = "[BG]" + eraseLine + "[R]"
#expect(lines[0] == expected)
#expect(lines[1] == expected)
}
@Test("ANSI reset codes in content are replaced with reset+bg")
func resetCodesReplaced() {
let writer = FrameDiffWriter()
let reset = ANSIRenderer.reset
let buffer = FrameBuffer(text: "A\(reset)B")
let lines = writer.buildOutputLines(
buffer: buffer,
terminalWidth: 20,
terminalHeight: 1,
bgCode: "[BG]",
reset: reset
)
#expect(lines[0].contains("\(reset)[BG]"))
}
@Test("Multiple content lines are all processed")
func multipleContentLines() {
let writer = FrameDiffWriter()
var buffer = FrameBuffer()
buffer.appendVertically(FrameBuffer(text: "Line1"))
buffer.appendVertically(FrameBuffer(text: "Line2"))
buffer.appendVertically(FrameBuffer(text: "Line3"))
let lines = writer.buildOutputLines(
buffer: buffer,
terminalWidth: 10,
terminalHeight: 3,
bgCode: "",
reset: ""
)
#expect(lines[0].contains("Line1"))
#expect(lines[1].contains("Line2"))
#expect(lines[2].contains("Line3"))
}
}
// MARK: - Line Diff Logic Tests
@Suite("FrameDiffWriter Diff Logic Tests")
@MainActor
struct DiffLogicTests {
@Test("computeChangedRows returns all rows when previous is empty")
func allRowsChangedOnFirstFrame() {
let changed = FrameDiffWriter.computeChangedRows(
newLines: ["A", "B", "C"],
previousLines: []
)
#expect(changed == [0, 1, 2])
}
@Test("computeChangedRows returns empty when frames are identical")
func noChangesForIdenticalFrames() {
let lines = ["A", "B", "C"]
let changed = FrameDiffWriter.computeChangedRows(
newLines: lines,
previousLines: lines
)
#expect(changed.isEmpty)
}
@Test("computeChangedRows detects single changed line")
func singleLineChanged() {
let changed = FrameDiffWriter.computeChangedRows(
newLines: ["A", "X", "C"],
previousLines: ["A", "B", "C"]
)
#expect(changed == [1])
}
@Test("computeChangedRows detects multiple changed lines")
func multipleLinesChanged() {
let changed = FrameDiffWriter.computeChangedRows(
newLines: ["A", "X", "C", "Y"],
previousLines: ["A", "B", "C", "D"]
)
#expect(changed == [1, 3])
}
@Test("computeChangedRows handles new lines longer than previous")
func newLinesLongerThanPrevious() {
let changed = FrameDiffWriter.computeChangedRows(
newLines: ["A", "B", "C", "D"],
previousLines: ["A", "B"]
)
// C and D are new (indices 2, 3)
#expect(changed == [2, 3])
}
@Test("computeChangedRows handles ANSI-coded strings correctly")
func ansiStringComparison() {
let styledA = "\u{1B}[31mRed\u{1B}[0m"
let styledB = "\u{1B}[32mGreen\u{1B}[0m"
let changed = FrameDiffWriter.computeChangedRows(
newLines: [styledA, styledB],
previousLines: [styledA, styledA]
)
// Only the second line changed (red green)
#expect(changed == [1])
}
}
// MARK: - Integration Tests
@Suite("FrameDiffWriter Integration Tests")
@MainActor
struct DiffIntegrationTests {
@Test("Content and status bar caches are independent")
func independentCaches() {
let writer = FrameDiffWriter()
// Simulate writing content + status bar (using internal state check)
let contentLines = ["Content1", "Content2"]
let statusLines = ["Status1"]
// After writeContentDiff, content cache is set
// After writeStatusBarDiff, status cache is set
// We verify via computeChangedRows that each cache tracks independently
// First: content has all changed (empty previous)
let contentChanged1 = FrameDiffWriter.computeChangedRows(
newLines: contentLines,
previousLines: []
)
#expect(contentChanged1 == [0, 1])
// Status also has all changed (different previous)
let statusChanged1 = FrameDiffWriter.computeChangedRows(
newLines: statusLines,
previousLines: []
)
#expect(statusChanged1 == [0])
// Same content no changes
let contentChanged2 = FrameDiffWriter.computeChangedRows(
newLines: contentLines,
previousLines: contentLines
)
#expect(contentChanged2.isEmpty)
// Status changed only status
let statusChanged2 = FrameDiffWriter.computeChangedRows(
newLines: ["NEW Status"],
previousLines: statusLines
)
#expect(statusChanged2 == [0])
}
@Test("invalidate clears both content and status bar caches")
func invalidateClearsBothCaches() {
let writer = FrameDiffWriter()
// After invalidate, previous lines are empty all rows changed
writer.invalidate()
let changed = FrameDiffWriter.computeChangedRows(
newLines: ["A", "B"],
previousLines: []
)
#expect(changed == [0, 1])
}
}