Skip to content

Commit 0feb569

Browse files
committed
Add tests for ANSI output, tab conversion, and edge cases
36 new tests covering: ANSI code verification, tab conversion, style getters, word wrapping, combined styles, empty content, border_row, border_style on tables, Ansi module, Color module, inheritance, nested trees/lists.
1 parent 8f711af commit 0feb569

1 file changed

Lines changed: 288 additions & 0 deletions

File tree

test/pure_ruby_test.rb

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "test_helper"
4+
5+
module Lipgloss
6+
class PureRubyTest < Minitest::Spec
7+
# ---- ANSI code verification ----
8+
9+
it "emits bold ANSI codes" do
10+
style = Style.new.bold(true)
11+
result = style.render("Bold")
12+
assert_includes result, "\e[1m"
13+
assert_includes result, "\e[0m"
14+
end
15+
16+
it "emits italic ANSI codes" do
17+
style = Style.new.italic(true)
18+
result = style.render("Italic")
19+
assert_includes result, "\e[3m"
20+
end
21+
22+
it "emits foreground color ANSI codes" do
23+
style = Style.new.foreground("#FF0000")
24+
result = style.render("Red")
25+
assert_includes result, "\e[38;2;255;0;0m"
26+
end
27+
28+
it "emits background color ANSI codes" do
29+
style = Style.new.background("#00FF00")
30+
result = style.render("Green")
31+
assert_includes result, "\e[48;2;0;255;0m"
32+
end
33+
34+
it "emits combined ANSI codes" do
35+
style = Style.new.bold(true).italic(true).foreground("#0000FF")
36+
result = style.render("Blue Bold Italic")
37+
assert_includes result, "\e[1m"
38+
assert_includes result, "\e[3m"
39+
assert_includes result, "\e[38;2;0;0;255m"
40+
end
41+
42+
it "applies ANSI codes per line" do
43+
style = Style.new.bold(true)
44+
result = style.render("Line1\nLine2")
45+
lines = result.split("\n")
46+
lines.each do |line|
47+
assert_includes line, "\e[1m"
48+
assert_includes line, "\e[0m"
49+
end
50+
end
51+
52+
it "does not apply ANSI codes to empty lines" do
53+
style = Style.new.bold(true)
54+
result = style.render("X\n\nY")
55+
lines = result.split("\n")
56+
# Middle line is empty and should NOT have bold codes
57+
assert_equal "", lines[1]
58+
assert_includes lines[0], "\e[1m"
59+
assert_includes lines[2], "\e[1m"
60+
end
61+
62+
# ---- Tab conversion ----
63+
64+
it "converts tabs to spaces with default tab width" do
65+
style = Style.new
66+
result = style.render("A\tB")
67+
assert_equal "A B", strip_ansi(result)
68+
end
69+
70+
it "converts tabs with custom tab width" do
71+
style = Style.new.tab_width(2)
72+
result = style.render("A\tB")
73+
assert_equal "A B", strip_ansi(result)
74+
end
75+
76+
it "removes tabs when tab_width is 0" do
77+
style = Style.new.tab_width(0)
78+
result = style.render("A\tB")
79+
assert_equal "AB", strip_ansi(result)
80+
end
81+
82+
it "preserves tabs when tab_width is NO_TAB_CONVERSION" do
83+
style = Style.new.tab_width(Lipgloss::NO_TAB_CONVERSION)
84+
result = style.render("A\tB")
85+
assert_equal "A\tB", strip_ansi(result)
86+
end
87+
88+
# ---- Style getters ----
89+
90+
it "returns correct bold? value" do
91+
assert_equal false, Style.new.bold?
92+
assert_equal true, Style.new.bold(true).bold?
93+
assert_equal false, Style.new.bold(true).unset_bold.bold?
94+
end
95+
96+
it "returns correct get_foreground" do
97+
assert_nil Style.new.get_foreground
98+
assert_equal "#FF0000", Style.new.foreground("#FF0000").get_foreground
99+
assert_nil Style.new.foreground("#FF0000").unset_foreground.get_foreground
100+
end
101+
102+
it "returns correct get_width" do
103+
assert_equal 0, Style.new.get_width
104+
assert_equal 20, Style.new.width(20).get_width
105+
assert_equal 0, Style.new.width(20).unset_width.get_width
106+
end
107+
108+
it "returns correct get_height" do
109+
assert_equal 0, Style.new.get_height
110+
assert_equal 5, Style.new.height(5).get_height
111+
end
112+
113+
# ---- Word wrapping edge cases ----
114+
115+
it "wraps single long word" do
116+
style = Style.new.max_width(5)
117+
result = style.render("ABCDEFGHIJ")
118+
lines = strip_ansi(result).split("\n")
119+
lines.each { |l| assert l.length <= 5, "Line too long: '#{l}'" }
120+
assert_equal "ABCDEFGHIJ", lines.join
121+
end
122+
123+
it "wraps multiple words" do
124+
style = Style.new.max_width(10)
125+
result = style.render("one two three four five")
126+
lines = strip_ansi(result).split("\n")
127+
lines.each { |l| assert l.length <= 10, "Line too long: '#{l}' (#{l.length})" }
128+
end
129+
130+
it "preserves short text with max_width" do
131+
style = Style.new.max_width(20)
132+
result = style.render("Short")
133+
assert_equal "Short", strip_ansi(result)
134+
end
135+
136+
# ---- Combined styles ----
137+
138+
it "combines padding + border" do
139+
style = Style.new.padding(0, 1).border(:rounded)
140+
result = strip_ansi(style.render("Hi"))
141+
assert_includes result, "╭"
142+
assert_includes result, "╯"
143+
assert_includes result, " Hi "
144+
end
145+
146+
it "combines width + alignment + border" do
147+
style = Style.new.width(10).align_horizontal(:center).border(:rounded)
148+
result = strip_ansi(style.render("Hi"))
149+
lines = result.split("\n")
150+
# All lines should be 12 wide (10 content + 2 border)
151+
lines.each { |l| assert_equal 12, l.length, "Line: '#{l}'" }
152+
end
153+
154+
# ---- Empty content ----
155+
156+
it "renders empty string" do
157+
style = Style.new
158+
result = style.render("")
159+
assert_equal "", strip_ansi(result)
160+
end
161+
162+
it "renders empty string with border" do
163+
style = Style.new.border(:rounded)
164+
result = strip_ansi(style.render(""))
165+
assert_includes result, "╭╮"
166+
assert_includes result, "╰╯"
167+
end
168+
169+
it "renders empty string with width" do
170+
style = Style.new.width(5)
171+
result = strip_ansi(style.render(""))
172+
assert_equal " ", result
173+
end
174+
175+
# ---- Table with border_row ----
176+
177+
it "renders table with border_row enabled" do
178+
table = Table.new
179+
.headers(["X"])
180+
.rows([["A"], ["B"]])
181+
.border(:normal)
182+
.border_row(true)
183+
184+
result = strip_ansi(table.render)
185+
# Should have row separator between A and B
186+
assert_includes result, "├─┤"
187+
end
188+
189+
# ---- Table with border_style ----
190+
191+
it "applies border_style to table borders" do
192+
border_s = Style.new.foreground("#FF0000")
193+
table = Table.new
194+
.headers(["X"])
195+
.rows([["Y"]])
196+
.border_style(border_s)
197+
198+
result = table.render
199+
# Border characters should have ANSI codes
200+
assert_includes result, "\e["
201+
assert_equal "╭─╮\n│X│\n├─┤\n│Y│\n╰─╯", strip_ansi(result)
202+
end
203+
204+
# ---- Ansi module ----
205+
206+
it "strips ANSI codes" do
207+
assert_equal "Hello", Ansi.strip("\e[1mHello\e[0m")
208+
assert_equal "test", Ansi.strip("\e[38;2;255;0;0mtest\e[0m")
209+
end
210+
211+
it "calculates width correctly" do
212+
assert_equal 5, Ansi.width("Hello")
213+
assert_equal 5, Ansi.width("\e[1mHello\e[0m")
214+
assert_equal 5, Ansi.width("Hello\nHi")
215+
end
216+
217+
it "calculates height correctly" do
218+
assert_equal 1, Ansi.height("Hello")
219+
assert_equal 3, Ansi.height("A\nB\nC")
220+
end
221+
222+
# ---- Color module ----
223+
224+
it "generates foreground ANSI code from hex" do
225+
assert_equal "\e[38;2;255;0;0m", Color.to_ansi_fg("#FF0000")
226+
assert_equal "\e[38;2;255;0;0m", Color.to_ansi_fg("#F00")
227+
end
228+
229+
it "generates background ANSI code from hex" do
230+
assert_equal "\e[48;2;0;255;0m", Color.to_ansi_bg("#00FF00")
231+
end
232+
233+
it "handles adaptive color" do
234+
color = AdaptiveColor.new(light: "#000000", dark: "#FFFFFF")
235+
result = Color.to_ansi_fg(color)
236+
refute_empty result
237+
end
238+
239+
it "handles complete color" do
240+
color = CompleteColor.new(true_color: "#FF0000", ansi256: "196", ansi: "9")
241+
result = Color.to_ansi_fg(color)
242+
refute_empty result
243+
end
244+
245+
# ---- Inherit edge cases ----
246+
247+
it "inherits multiple properties" do
248+
parent = Style.new.bold(true).italic(true).foreground("#FF0000")
249+
child = Style.new.inherit(parent)
250+
251+
assert_equal true, child.bold?
252+
assert_equal true, child.italic?
253+
assert_equal "#FF0000", child.get_foreground
254+
end
255+
256+
it "child properties take precedence over inherited" do
257+
parent = Style.new.bold(true).foreground("#FF0000")
258+
child = Style.new.bold(false).inherit(parent)
259+
260+
assert_equal false, child.bold?
261+
assert_equal "#FF0000", child.get_foreground
262+
end
263+
264+
# ---- Deeply nested tree ----
265+
266+
it "renders deeply nested tree" do
267+
inner = Tree.root("C").child("D")
268+
mid = Tree.root("B").child(inner)
269+
tree = Tree.root("A").child(mid)
270+
271+
result = strip_ansi(tree.render)
272+
expected = "A\n└── B\n └── C\n └── D"
273+
assert_equal expected, result
274+
end
275+
276+
# ---- Nested list with different enumerators ----
277+
278+
it "renders nested list inheriting parent enumerator" do
279+
inner = List.new("X", "Y").enumerator(:arabic)
280+
outer = List.new.item("Main").item(inner)
281+
282+
result = strip_ansi(outer.render)
283+
assert_includes result, "• Main"
284+
assert_includes result, " 1. X"
285+
assert_includes result, " 2. Y"
286+
end
287+
end
288+
end

0 commit comments

Comments
 (0)