Files
i7t5--edmund/Tests/EdmundTests/TableAlignmentTests.swift
wehub-resource-sync 5b99bf6bca
CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:54 +08:00

182 lines
7.9 KiB
Swift

import Testing
import Foundation
import AppKit
@testable import EdmundCore
// GFM table column alignment (`:--`/`:-:`/`--:`) applied in the live editor.
// The non-active render distributes each cell's slack via `.kern`: left pads
// after content, right pads before it (on the leading hidden pipe), center
// splits. The kern *location* encodes the alignment.
@Suite("Table column alignment — parsing")
struct TableAlignmentParseTests {
@Test("Separator row maps to left/center/right")
func mixed() {
#expect(tableColumnAlignments(separatorRow: "|:--|:-:|--:|", count: 3)
== [.left, .center, .right])
}
@Test("Plain `---` columns default to left")
func plain() {
#expect(tableColumnAlignments(separatorRow: "| --- | --- |", count: 2)
== [.left, .left])
}
@Test("Missing cells pad with .left")
func shortRow() {
#expect(tableColumnAlignments(separatorRow: "|--:|", count: 3)
== [.right, .left, .left])
}
@Test("A backslash-escaped pipe does not split a cell")
func escapedPipeNotSeparator() {
#expect(splitTableRow("| a \\| b | c |").count == 2)
}
}
@Suite("Table column alignment — rendering")
@MainActor
struct TableAlignmentRenderTests {
/// Offset of the leading `|` of the table's last (data) row.
private func lastRowStart(_ styled: NSAttributedString) -> Int {
let s = styled.string as NSString
let nl = s.range(of: "\n", options: .backwards)
return nl.location == NSNotFound ? 0 : nl.location + 1
}
private func kern(at offset: Int, in styled: NSAttributedString) -> CGFloat? {
guard offset < styled.length else { return nil }
return styled.attribute(.kern, at: offset, effectiveRange: nil) as? CGFloat
}
@Test("Right-aligned column kerns the leading pipe of the data cell")
func rightAlign() {
let editor = makeEditor()
let styled = editor.styleBlock("| aaa | bbb |\n|--:|--:|\n| x | y |", cursorPosition: nil)
let start = lastRowStart(styled)
// Leading pipe of col 0 carries the right-pad kern.
#expect((kern(at: start, in: styled) ?? 0) > 0.5)
}
@Test("Left-aligned column does NOT kern the leading pipe")
func leftAlign() {
let editor = makeEditor()
let styled = editor.styleBlock("| aaa | bbb |\n|---|---|\n| x | y |", cursorPosition: nil)
let start = lastRowStart(styled)
// Slack sits after the content, not on the leading pipe.
#expect(kern(at: start, in: styled) == nil)
}
@Test("Active table has no alignment kern (raw monospace)")
func activeUnaffected() {
let editor = makeEditor()
let table = "| aaa | bbb |\n|--:|--:|\n| x | y |"
let styled = editor.styleBlock(table, cursorPosition: 2)
let start = lastRowStart(styled)
#expect(kern(at: start, in: styled) == nil)
}
@Test("Column width uses the styled cell width, not the raw source width")
func styledWidthAlignment() {
let editor = makeEditor()
// Col 0: "**wide**" renders as bold "wide" (4 chars), so the raw
// 8-char source must not inflate the column. The plain data cell
// "wide" needs (nearly) no slack once the ** measure ~zero — its
// only slack is the header's bold-vs-regular width difference.
let styled = editor.styleBlock("| **wide** | b |\n|---|---|\n| wide | y |", cursorPosition: nil)
let start = lastRowStart(styled)
// Data cell content "wide" starts after "| " → trailing char is 'e'
// at start+5; its left-align kern must be tiny (bold-regular delta),
// far below the ~4-character width the raw `**`s would add.
let s = styled.string as NSString
let cellEnd = s.range(of: "wide", options: [], range: NSRange(location: start, length: styled.length - start))
#expect(cellEnd.location != NSNotFound)
let slack = kern(at: cellEnd.upperBound - 1, in: styled) ?? 0
let twoChars = "aa".size(withAttributes: [.font: editor.bodyFont]).width
#expect(slack < twoChars)
}
}
@Suite("Table cell inline styling")
@MainActor
struct TableInlineStylingTests {
private func table(_ cell: String) -> NSAttributedString {
makeEditor().styleBlock("| \(cell) | b |\n|---|---|\n| x | y |", cursorPosition: nil)
}
@Test("Bold in a header cell renders bold at body size")
func boldCell() {
let editor = makeEditor()
let styled = editor.styleBlock("| a | b |\n|---|---|\n| **bold** | y |", cursorPosition: nil)
let s = styled.string as NSString
let bold = s.range(of: "bold")
let f = styled.attribute(.font, at: bold.location, effectiveRange: nil) as? NSFont
#expect(f != nil && NSFontManager.shared.traits(of: f!).contains(.boldFontMask))
// Its ** delimiters are hidden.
let d = styled.attribute(.font, at: bold.location - 1, effectiveRange: nil) as? NSFont
#expect((d?.pointSize ?? 99) < 1.0)
}
@Test("Inline code in a cell gets the mono font")
func codeCell() {
let editor = makeEditor()
let styled = editor.styleBlock("| a | b |\n|---|---|\n| `code` | y |", cursorPosition: nil)
let s = styled.string as NSString
let code = s.range(of: "code", options: .backwards)
let f = styled.attribute(.font, at: code.location, effectiveRange: nil) as? NSFont
#expect(f == editor.inlineCodeFont)
}
@Test("Link in a cell is colored and carries its destination")
func linkCell() {
let editor = makeEditor()
let styled = editor.styleBlock("| a | b |\n|---|---|\n| [x](http://e.com) | y |", cursorPosition: nil)
let s = styled.string as NSString
let x = s.range(of: "[x]", options: .backwards)
let dest = styled.attribute(.editorLinkURL, at: x.location + 1, effectiveRange: nil) as? String
#expect(dest == "http://e.com")
}
@Test("Header-row styling stays bold when a cell has italic (boldItalic)")
func headerItalicCell() {
let editor = makeEditor()
let styled = editor.styleBlock("| *it* | b |\n|---|---|\n| x | y |", cursorPosition: nil)
let s = styled.string as NSString
let it = s.range(of: "it")
let f = styled.attribute(.font, at: it.location, effectiveRange: nil) as? NSFont
let traits = f.map { NSFontManager.shared.traits(of: $0) } ?? []
#expect(traits.contains(.boldFontMask))
#expect(traits.contains(.italicFontMask))
}
@Test("Escaped pipe in a data cell stays visible; structural pipes stay hidden")
func escapedPipeStaysVisible() {
let editor = makeEditor()
let styled = editor.styleBlock("| a | b |\n|---|---|\n| x \\| y | z |", cursorPosition: nil)
let s = styled.string as NSString
let escaped = s.range(of: "\\|")
#expect(escaped.location != NSNotFound)
let escapedPipeFont = styled.attribute(.font, at: escaped.location + 1, effectiveRange: nil) as? NSFont
#expect((escapedPipeFont?.pointSize ?? 0) >= 1.0)
// A structural pipe (the leading pipe of the data row) stays hidden.
let lastRow = s.range(of: "\n", options: .backwards).location + 1
let structuralPipeFont = styled.attribute(.font, at: lastRow, effectiveRange: nil) as? NSFont
#expect((structuralPipeFont?.pointSize ?? 99) < 1.0)
}
@Test("Row paragraph geometry survives cell styling (table owns it)")
func rowGeometryPreserved() {
let styled = table("**b**")
// First char of the header row still carries the table's paragraph
// style (indent = cell padding), not styleBlock's body style.
let ps = styled.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle
#expect(ps != nil && ps!.firstLineHeadIndent > 0)
// And the row decoration is a table row.
#expect(styled.attribute(.blockDecoration, at: 0, effectiveRange: nil) != nil)
}
}