livetext 0.9.52 → 0.9.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/imports/bookish.rb +3 -3
  3. data/lib/livetext/ast/show_ast_clean.rb +10 -0
  4. data/lib/livetext/ast/show_ast_result.rb +60 -0
  5. data/lib/livetext/ast/show_raw_arrays.rb +13 -0
  6. data/lib/livetext/ast.rb +464 -0
  7. data/lib/livetext/ast_to_html.rb +32 -0
  8. data/lib/livetext/core.rb +110 -53
  9. data/lib/livetext/errors.rb +1 -0
  10. data/lib/livetext/expansion.rb +21 -21
  11. data/lib/livetext/formatter.rb +70 -200
  12. data/lib/livetext/formatter_component.rb +189 -0
  13. data/lib/livetext/function_registry.rb +163 -0
  14. data/lib/livetext/functions.rb +26 -0
  15. data/lib/livetext/handler/mixin.rb +53 -0
  16. data/lib/livetext/helpers.rb +33 -16
  17. data/lib/livetext/reopen.rb +2 -0
  18. data/lib/livetext/skeleton.rb +0 -3
  19. data/lib/livetext/standard.rb +120 -72
  20. data/lib/livetext/userapi.rb +20 -1
  21. data/lib/livetext/variable_manager.rb +78 -0
  22. data/lib/livetext/variables.rb +9 -1
  23. data/lib/livetext/version.rb +1 -1
  24. data/lib/livetext.rb +9 -3
  25. data/plugin/booktool.rb +14 -14
  26. data/plugin/lt3scriptor.rb +914 -0
  27. data/plugin/mixin_functions_class.rb +33 -0
  28. data/test/snapshots/complex_body/expected-error.txt +0 -0
  29. data/test/snapshots/complex_body/expected-output.txt +8 -0
  30. data/test/snapshots/complex_body/source.lt3 +19 -0
  31. data/test/snapshots/debug_command/expected-error.txt +0 -0
  32. data/test/snapshots/debug_command/expected-output.txt +1 -0
  33. data/test/snapshots/debug_command/source.lt3 +3 -0
  34. data/test/snapshots/def_parameters/expected-error.txt +0 -0
  35. data/test/snapshots/def_parameters/expected-output.txt +21 -0
  36. data/test/snapshots/def_parameters/source.lt3 +44 -0
  37. data/test/snapshots/error_missing_end/match-error.txt +1 -1
  38. data/test/snapshots/functions_reflection/expected-error.txt +0 -0
  39. data/test/snapshots/functions_reflection/expected-output.txt +27 -0
  40. data/test/snapshots/functions_reflection/source.lt3 +5 -0
  41. data/test/snapshots/mixin_functions_class/expected-error.txt +0 -0
  42. data/test/snapshots/mixin_functions_class/expected-output.txt +20 -0
  43. data/test/snapshots/mixin_functions_class/mixin_functions_class.rb +33 -0
  44. data/test/snapshots/mixin_functions_class/source.lt3 +17 -0
  45. data/test/snapshots/multiple_functions/expected-error.txt +0 -0
  46. data/test/snapshots/multiple_functions/expected-output.txt +5 -0
  47. data/test/snapshots/multiple_functions/source.lt3 +16 -0
  48. data/test/snapshots/nested_includes/expected-error.txt +0 -0
  49. data/test/snapshots/nested_includes/expected-output.txt +68 -0
  50. data/test/snapshots/nested_includes/level2.inc +34 -0
  51. data/test/snapshots/nested_includes/level3.inc +20 -0
  52. data/test/snapshots/nested_includes/source.lt3 +49 -0
  53. data/test/snapshots/parameter_handling/expected-error.txt +0 -0
  54. data/test/snapshots/parameter_handling/expected-output.txt +7 -0
  55. data/test/snapshots/parameter_handling/source.lt3 +10 -0
  56. data/test/snapshots/subset.txt +1 -0
  57. data/test/snapshots/system_info/expected-error.txt +0 -0
  58. data/test/snapshots/system_info/match-output.txt +18 -0
  59. data/test/snapshots/system_info/source.lt3 +16 -0
  60. data/test/unit/all.rb +7 -0
  61. data/test/unit/ast.rb +90 -0
  62. data/test/unit/ast_directives.rb +104 -0
  63. data/test/unit/ast_variables.rb +71 -0
  64. data/test/unit/core_methods.rb +317 -0
  65. data/test/unit/formatter.rb +84 -0
  66. data/test/unit/formatter_component.rb +84 -0
  67. data/test/unit/function_registry.rb +132 -0
  68. data/test/unit/mixin_functions_class.rb +131 -0
  69. data/test/unit/stringparser.rb +14 -32
  70. data/test/unit/variable_manager.rb +71 -0
  71. metadata +51 -5
  72. data/imports/markdown.rb +0 -44
  73. data/lib/livetext/processor.rb +0 -88
  74. data/plugin/markdown.rb +0 -43
@@ -0,0 +1,104 @@
1
+ require 'minitest/autorun'
2
+
3
+ require_relative '../../lib/livetext/ast'
4
+
5
+ class TestingLivetextASTDirectives < Minitest::Test
6
+ def setup
7
+ @ast = LivetextAST.new
8
+ end
9
+
10
+ def test_single_line_directives
11
+ # Single line directives (2 parameters: args, data)
12
+ assert_equal([LivetextAST::DIRECTIVE, "h1", "My Title"],
13
+ @ast.parse_directives([".h1 My Title"]))
14
+
15
+ assert_equal([LivetextAST::DIRECTIVE, "set", "name=value"],
16
+ @ast.parse_directives([".set name=value"]))
17
+
18
+ assert_equal([LivetextAST::DIRECTIVE, "comment", "This is a comment"],
19
+ @ast.parse_directives([". This is a comment"]))
20
+ end
21
+
22
+ def test_multi_line_comment_directive
23
+ # Multi-line .comment directive
24
+ input = [
25
+ ".comment",
26
+ " This is a multi-line comment",
27
+ " It can span multiple lines",
28
+ ".end"
29
+ ]
30
+ expected = [
31
+ LivetextAST::DIRECTIVE, "comment", "",
32
+ [LivetextAST::BODY, [" This is a multi-line comment", " It can span multiple lines"]]
33
+ ]
34
+ assert_equal(expected, @ast.parse_directives(input))
35
+ end
36
+
37
+ def test_multi_line_directives
38
+ # Multi line directives (3 parameters: args, data, body)
39
+ input = [
40
+ ".list",
41
+ " * item 1",
42
+ " * item 2",
43
+ " * item 3",
44
+ ".end"
45
+ ]
46
+ expected = [
47
+ LivetextAST::DIRECTIVE, "list", "",
48
+ [LivetextAST::BODY, [" * item 1", " * item 2", " * item 3"]]
49
+ ]
50
+ assert_equal(expected, @ast.parse_directives(input))
51
+ end
52
+
53
+ def test_directive_with_args_and_body
54
+ input = [
55
+ ".func myfunc",
56
+ " return 'Hello, World!'",
57
+ ".end"
58
+ ]
59
+ expected = [
60
+ LivetextAST::DIRECTIVE, "func", "myfunc",
61
+ [LivetextAST::BODY, [" return 'Hello, World!'"]]
62
+ ]
63
+ assert_equal(expected, @ast.parse_directives(input))
64
+ end
65
+
66
+ def test_missing_end_error
67
+ input = [
68
+ ".list",
69
+ " * item 1",
70
+ " * item 2"
71
+ # Missing .end
72
+ ]
73
+ expected = [
74
+ LivetextAST::ERROR, "Missing .end", 1, "list"
75
+ ]
76
+ assert_equal(expected, @ast.parse_directives(input))
77
+ end
78
+
79
+ def test_unknown_directive
80
+ input = [".unknown_directive some args"]
81
+ expected = [
82
+ LivetextAST::ERROR, "Unknown directive", 1, "unknown_directive"
83
+ ]
84
+ assert_equal(expected, @ast.parse_directives(input))
85
+ end
86
+
87
+ def test_empty_input
88
+ assert_equal([], @ast.parse_directives([]))
89
+ assert_equal([], @ast.parse_directives(nil))
90
+ end
91
+
92
+ def test_non_directive_lines
93
+ input = [
94
+ "Just some text",
95
+ "More text here",
96
+ ".h1 A Title",
97
+ "More text"
98
+ ]
99
+ expected = [
100
+ LivetextAST::DIRECTIVE, "h1", "A Title"
101
+ ]
102
+ assert_equal(expected, @ast.parse_directives(input))
103
+ end
104
+ end
@@ -0,0 +1,71 @@
1
+ require 'minitest/autorun'
2
+
3
+ require_relative '../../lib/livetext/ast'
4
+
5
+ class TestingLivetextASTVariables < Minitest::Test
6
+ def setup
7
+ @ast = LivetextAST.new
8
+ end
9
+
10
+ def test_variable_parsing
11
+ # Basic variables
12
+ assert_equal([LivetextAST::VAR, "name"], @ast.parse_variables("$name"))
13
+ assert_equal([LivetextAST::VAR, "my_var"], @ast.parse_variables("$my_var"))
14
+ assert_equal([LivetextAST::VAR, "font.title"], @ast.parse_variables("$font.title"))
15
+
16
+ # Invalid names should be left as literal text
17
+ assert_equal("$_invalid", @ast.parse_variables("$_invalid"))
18
+ assert_equal("$3invalid", @ast.parse_variables("$3invalid"))
19
+
20
+ # Edge cases - Livetext behavior:
21
+ # $foo. -> parses $foo as variable, . as literal (matches Livetext)
22
+ assert_equal([LivetextAST::TEXT, [LivetextAST::VAR, "foo"], "."], @ast.parse_variables("$foo."))
23
+ # $a..b -> parses $a as variable, ..b as literal (matches Livetext)
24
+ assert_equal([LivetextAST::TEXT, [LivetextAST::VAR, "a"], "..b"], @ast.parse_variables("$a..b"))
25
+ end
26
+
27
+ def test_function_parsing
28
+ # No parameter
29
+ assert_equal([LivetextAST::FUNC, "myfunc", LivetextAST::SPACE, nil], @ast.parse_functions("$$myfunc "))
30
+ assert_equal([LivetextAST::FUNC, "myfunc", LivetextAST::EOL, nil], @ast.parse_functions("$$myfunc"))
31
+
32
+ # Colon parameter
33
+ assert_equal([LivetextAST::FUNC, "greet", LivetextAST::COLON, "world"], @ast.parse_functions("$$greet:world"))
34
+
35
+ # Bracket parameter
36
+ assert_equal([LivetextAST::FUNC, "mean", LivetextAST::LBRACK, "1,2,3"], @ast.parse_functions("$$mean[1,2,3]"))
37
+ assert_equal([LivetextAST::FUNC, "title", LivetextAST::LBRACK, "My Title"], @ast.parse_functions("$$title[My Title]"))
38
+
39
+ # Note: Function parameter delimiters are tracked as requested:
40
+ # SPACE, EOL, COLON, LBRACK for debugging/future use
41
+ end
42
+
43
+ def test_mixed_content
44
+ # Variables in text
45
+ input = "Hello $name, welcome to $nation!"
46
+ expected = [LivetextAST::TEXT, "Hello ", [LivetextAST::VAR, "name"], ", welcome to ", [LivetextAST::VAR, "nation"], "!"]
47
+ assert_equal(expected, @ast.parse_variables(input))
48
+
49
+ # Functions in text
50
+ input = "The result is $$mean[1,2,3] as expected."
51
+ expected = [LivetextAST::TEXT, "The result is ", [LivetextAST::FUNC, "mean", LivetextAST::LBRACK, "1,2,3"], " as expected."]
52
+ assert_equal(expected, @ast.parse_functions(input))
53
+ end
54
+
55
+ def test_escaped_dollars
56
+ # Escaped variables and functions
57
+ # Note: Backslash escapes the entire $ or $$ sequence
58
+ assert_equal("\\$literal", @ast.parse_variables("\\$literal"))
59
+ assert_equal("\\$\\$literal", @ast.parse_functions("\\$\\$literal"))
60
+ end
61
+
62
+ def test_edge_cases
63
+ # Empty strings
64
+ assert_equal([], @ast.parse_variables(""))
65
+ assert_equal([], @ast.parse_functions(""))
66
+
67
+ # No variables/functions
68
+ assert_equal("plain text", @ast.parse_variables("plain text"))
69
+ assert_equal("plain text", @ast.parse_functions("plain text"))
70
+ end
71
+ end
@@ -0,0 +1,317 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/livetext'
3
+
4
+ class TestingLivetextCoreMethods < Minitest::Test
5
+ def setup
6
+ @live = Livetext.new
7
+ end
8
+
9
+ def test_customize_class_method
10
+ # Test the class-level customize method
11
+ obj = Livetext.customize(
12
+ mix: [],
13
+ call: ['.nopara'],
14
+ vars: { 'test_var' => 'test_value' }
15
+ )
16
+
17
+ assert_instance_of(Livetext, obj)
18
+ # The customize method should set up the instance with mixins, commands, and vars
19
+ end
20
+
21
+ def test_customize_instance_method
22
+ # Test the instance-level customize method
23
+ result = @live.customize(
24
+ mix: [],
25
+ call: ['.nopara'],
26
+ vars: { 'test_var' => 'test_value' }
27
+ )
28
+
29
+ assert_equal(@live, result) # Should return self
30
+ end
31
+
32
+ def test_customize_with_plugin
33
+ # Test customize with a real plugin
34
+ obj = Livetext.customize(
35
+ mix: ['tutorial'],
36
+ call: [],
37
+ vars: {}
38
+ )
39
+
40
+ assert_instance_of(Livetext, obj)
41
+ # Should have loaded the tutorial plugin
42
+ assert_includes(obj.methods, :title)
43
+ end
44
+
45
+ def test_customize_with_commands
46
+ # Test customize with dot commands
47
+ obj = Livetext.customize(
48
+ mix: [],
49
+ call: ['.set VAR="test_value"'],
50
+ vars: {}
51
+ )
52
+
53
+ assert_instance_of(Livetext, obj)
54
+ # Process some text to see if the variable was set
55
+ body, vars = obj.process(text: "Variable: $VAR")
56
+ assert_includes(body, "Variable: test_value")
57
+ end
58
+
59
+ def test_customize_with_variables
60
+ # Test customize with variables
61
+ obj = Livetext.customize(
62
+ mix: [],
63
+ call: [],
64
+ vars: { 'name' => 'World', 'title' => 'Test' }
65
+ )
66
+
67
+ assert_instance_of(Livetext, obj)
68
+ # Process some text to see if variables were set
69
+ body, vars = obj.process(text: "Hello $name\n.h1 $title")
70
+ assert_includes(body, "Hello World")
71
+ assert_includes(body, "Test")
72
+ end
73
+
74
+ def test_customize_comprehensive
75
+ # Test all features together
76
+ obj = Livetext.customize(
77
+ mix: ['tutorial'],
78
+ call: ['.set PLUGIN_VAR="plugin_value"'],
79
+ vars: { 'custom_var' => 'custom_value' }
80
+ )
81
+
82
+ assert_instance_of(Livetext, obj)
83
+
84
+ # Test that plugin was loaded
85
+ assert_includes(obj.methods, :title)
86
+
87
+ # Test that variables were set
88
+ body, vars = obj.process(text: "Plugin: $PLUGIN_VAR\nCustom: $custom_var")
89
+ assert_includes(body, "Plugin: plugin_value")
90
+ assert_includes(body, "Custom: custom_value")
91
+ end
92
+
93
+ def test_customize_error_handling
94
+ # Test customize with invalid plugin
95
+ assert_raises(FileNotFound) do
96
+ Livetext.customize(mix: ['nonexistent_plugin'])
97
+ end
98
+ end
99
+
100
+ def test_customize_empty_parameters
101
+ # Test customize with empty parameters
102
+ obj = Livetext.customize()
103
+ assert_instance_of(Livetext, obj)
104
+
105
+ # Should still work for basic processing
106
+ body, vars = obj.process(text: "Hello world")
107
+ assert_includes(body, "Hello world")
108
+ end
109
+
110
+ def test_customize_string_vs_array
111
+ # Test that string parameters are converted to arrays
112
+ obj = Livetext.customize(
113
+ mix: 'tutorial',
114
+ call: '.nopara',
115
+ vars: { 'test' => 'value' }
116
+ )
117
+
118
+ assert_instance_of(Livetext, obj)
119
+ assert_includes(obj.methods, :title)
120
+ end
121
+
122
+ def test_customize_variables_accessible
123
+ # Test that variables passed to customize are actually accessible afterward
124
+ obj = Livetext.customize(
125
+ mix: [],
126
+ call: [],
127
+ vars: { 'name' => 'World', 'title' => 'Test Title', 'count' => '42' }
128
+ )
129
+
130
+ assert_instance_of(Livetext, obj)
131
+
132
+ # Test that variables are accessible via vars.to_h
133
+ vars_hash = obj.vars.to_h
134
+ assert_equal('World', vars_hash[:name])
135
+ assert_equal('Test Title', vars_hash[:title])
136
+ assert_equal('42', vars_hash[:count])
137
+
138
+ # Test that variables are accessible via variables.to_h
139
+ variables_hash = obj.variables.to_h
140
+ assert_equal('World', variables_hash[:name])
141
+ assert_equal('Test Title', variables_hash[:title])
142
+ assert_equal('42', variables_hash[:count])
143
+
144
+ # Test that variables work in processing
145
+ body, vars = obj.process(text: "Hello $name\n.h1 $title\nCount: $count")
146
+ assert_includes(body, "Hello World")
147
+ assert_includes(body, "Test Title")
148
+ assert_includes(body, "Count: 42")
149
+ end
150
+
151
+ def test_customize_variables_global_access
152
+ # Test that variables passed to customize are accessible via Livetext::Vars
153
+ obj = Livetext.customize(
154
+ mix: [],
155
+ call: [],
156
+ vars: { 'name' => 'World', 'title' => 'Test Title', 'count' => '42' }
157
+ )
158
+
159
+ assert_instance_of(Livetext, obj)
160
+
161
+ # Test that variables are accessible via Livetext::Vars (global access)
162
+ assert_equal('World', Livetext::Vars[:name])
163
+ assert_equal('Test Title', Livetext::Vars[:title])
164
+ assert_equal('42', Livetext::Vars[:count])
165
+ end
166
+
167
+ def test_variable_dot_access
168
+ # Test that variables can be accessed via live.vars.myvar syntax
169
+ obj = Livetext.customize(
170
+ mix: [],
171
+ call: [],
172
+ vars: { 'name' => 'World', 'title' => 'Test Title', 'count' => '42' }
173
+ )
174
+
175
+ assert_instance_of(Livetext, obj)
176
+
177
+ # Test dot access syntax
178
+ assert_equal('World', obj.vars.name)
179
+ assert_equal('Test Title', obj.vars.title)
180
+ assert_equal('42', obj.vars.count)
181
+
182
+ # Test that built-in variables also work
183
+ assert_equal(Livetext::VERSION, obj.vars.Version)
184
+ assert_equal(`hostname`.chomp, obj.vars.Hostname)
185
+
186
+ # Test that respond_to? works correctly
187
+ assert(obj.vars.respond_to?(:name))
188
+ assert(obj.vars.respond_to?(:title))
189
+ assert(obj.vars.respond_to?(:Version))
190
+ assert(obj.vars.respond_to?(:nonexistent)) # Now returns true since method_missing always returns a value
191
+ end
192
+
193
+ def test_variable_dot_access_nonexistent
194
+ # Test what happens when accessing a nonexistent variable with dot syntax
195
+ obj = Livetext.customize(
196
+ mix: [],
197
+ call: [],
198
+ vars: { 'name' => 'World' }
199
+ )
200
+
201
+ assert_instance_of(Livetext, obj)
202
+
203
+ # Test that all access methods return the same fallback message
204
+ assert_equal('[nonexistent is undefined]', obj.vars.nonexistent)
205
+ assert_equal('[nonexistent is undefined]', obj.vars[:nonexistent])
206
+ assert_equal('[nonexistent is undefined]', obj.vars.get(:nonexistent))
207
+
208
+ # Test global access also returns fallback message
209
+ assert_equal('[nonexistent is undefined]', Livetext::Vars[:nonexistent])
210
+ end
211
+
212
+ def test_process_text
213
+ # Test processing text directly
214
+ text = "Hello world\nThis is a test\n.h1 Title"
215
+ body, vars = @live.process(text: text)
216
+
217
+ assert_includes(body, "Hello world")
218
+ assert_includes(body, "This is a test")
219
+ # Should process the .h1 command
220
+ assert_instance_of(Hash, vars)
221
+ end
222
+
223
+ def test_process_file
224
+ # Test processing a file
225
+ # Create a temporary test file
226
+ require 'tempfile'
227
+ temp_file = Tempfile.new(['test', '.lt3'])
228
+ temp_file.write("Hello from file\n.h1 File Title")
229
+ temp_file.close
230
+
231
+ body, vars = @live.process(file: temp_file.path)
232
+
233
+ assert_includes(body, "Hello from file")
234
+ # Should process the .h1 command
235
+ assert_instance_of(Hash, vars)
236
+
237
+ temp_file.unlink
238
+ end
239
+
240
+ def test_process_with_file
241
+ # Test process method with file parameter
242
+ require 'tempfile'
243
+ temp_file = Tempfile.new(['test', '.lt3'])
244
+ temp_file.write("Hello from process file\n.h1 Process Title")
245
+ temp_file.close
246
+
247
+ body, vars = @live.process(file: temp_file.path)
248
+
249
+ assert_includes(body, "Hello from process file")
250
+ # Should process the .h1 command
251
+ assert_instance_of(Hash, vars)
252
+
253
+ temp_file.unlink
254
+ end
255
+
256
+ def test_process_with_text
257
+ # Test process method with text parameter
258
+ text = "Hello from process text\n.h1 Process Text Title"
259
+ body, vars = @live.process(text: text)
260
+
261
+ assert_includes(body, "Hello from process text")
262
+ # Should process the .h1 command
263
+ assert_instance_of(Hash, vars)
264
+ end
265
+
266
+ def test_process_with_vars
267
+ # Test process method with variables
268
+ text = "Hello $name\n.h1 $title"
269
+ body, vars = @live.process(text: text, vars: { 'name' => 'World', 'title' => 'Test Title' })
270
+
271
+ assert_includes(body, "Hello World")
272
+ assert_includes(body, "Test Title")
273
+ assert_equal('World', vars[:name])
274
+ assert_equal('Test Title', vars[:title])
275
+ end
276
+
277
+ def test_process_error_handling
278
+ # Test process method error cases
279
+ assert_raises(RuntimeError) { @live.process } # No file or text
280
+ assert_raises(RuntimeError) { @live.process(file: 'nonexistent.lt3', text: 'some text') } # Both file and text
281
+ end
282
+
283
+ def test_backward_compatibility
284
+ # Test that old methods still work
285
+ text = "Hello from old method\n.h1 Old Title"
286
+
287
+ # Test transform
288
+ result1 = @live.transform(text)
289
+ assert_includes(result1, "Hello from old method")
290
+
291
+ # Test xform with text
292
+ result2 = @live.xform(text: text)
293
+ assert_includes(result2, "Hello from old method")
294
+
295
+ # Test xform_file
296
+ require 'tempfile'
297
+ temp_file = Tempfile.new(['test', '.lt3'])
298
+ temp_file.write("Hello from old file method\n.h1 Old File Title")
299
+ temp_file.close
300
+
301
+ result3 = @live.xform_file(temp_file.path)
302
+ assert_includes(result3, "Hello from old file method")
303
+
304
+ temp_file.unlink
305
+ end
306
+
307
+ def test_process_returns_array
308
+ # Test that process returns [body, vars] array
309
+ text = "Hello $name\n.set title=\"Test Title\"\n.h1 $title"
310
+ body, vars = @live.process(text: text, vars: { 'name' => 'World' })
311
+
312
+ assert_includes(body, "Hello World")
313
+ assert_includes(body, "Test Title")
314
+ assert_equal('World', vars[:name])
315
+ assert_equal('Test Title', vars[:title])
316
+ end
317
+ end
@@ -0,0 +1,84 @@
1
+ require 'minitest/autorun'
2
+
3
+ require_relative '../../lib/livetext'
4
+
5
+ class TestingLivetextFormatter < Minitest::Test
6
+ def setup
7
+ @live = Livetext.new
8
+ end
9
+
10
+ def test_formatter_component_initialization
11
+ # Test that Formatter is properly initialized
12
+ assert(@live.formatter, "Formatter should be initialized")
13
+ assert_instance_of(Livetext::Formatter, @live.formatter)
14
+ end
15
+
16
+ def test_basic_formatting
17
+ # Test basic text formatting
18
+ assert_equal("<b>bold</b>", @live.formatter.format("*bold"))
19
+ assert_equal("<i>italic</i>", @live.formatter.format("_italic"))
20
+ assert_equal("<tt>code</tt>", @live.formatter.format("`code"))
21
+ assert_equal("<strike>strike</strike>", @live.formatter.format("~strike"))
22
+ end
23
+
24
+ def test_double_markers
25
+ # Test double markers
26
+ assert_equal("<b>bold</b>", @live.formatter.format("**bold"))
27
+ assert_equal("**", @live.formatter.format("**")) # standalone
28
+ assert_equal(" ** ", @live.formatter.format(" ** ")) # surrounded by spaces
29
+ end
30
+
31
+ def test_bracketed_markers
32
+ # Test bracketed markers
33
+ assert_equal("<b>content</b>", @live.formatter.format("*[content]"))
34
+ assert_equal("<i>content</i>", @live.formatter.format("_[content]"))
35
+ assert_equal("<tt>content</tt>", @live.formatter.format("`[content]"))
36
+ assert_equal("", @live.formatter.format("*[]")) # empty brackets disappear
37
+ end
38
+
39
+ def test_escaped_markers
40
+ # Test escaped markers
41
+ assert_equal("*literal", @live.formatter.format("\\*literal"))
42
+ assert_equal("_literal", @live.formatter.format("\\_literal"))
43
+ assert_equal("`literal", @live.formatter.format("\\`literal"))
44
+ assert_equal("~literal", @live.formatter.format("\\~literal"))
45
+ end
46
+
47
+ def test_format_line
48
+ # Test format_line method
49
+ assert_equal("<b>bold</b>", @live.formatter.format_line("*bold\n"))
50
+ assert_equal("", @live.formatter.format_line(nil))
51
+ assert_equal("", @live.formatter.format_line(""))
52
+ end
53
+
54
+ def test_format_multiple
55
+ # Test format_multiple method
56
+ lines = ["*bold\n", "_italic\n", "`code\n"]
57
+ expected = ["<b>bold</b>", "<i>italic</i>", "<tt>code</tt>"]
58
+ assert_equal(expected, @live.formatter.format_multiple(lines))
59
+ end
60
+
61
+ def test_convenience_methods
62
+ # Test convenience methods
63
+ assert_equal("<b>text</b>", @live.formatter.bold("text"))
64
+ assert_equal("<i>text</i>", @live.formatter.italic("text"))
65
+ assert_equal("<tt>text</tt>", @live.formatter.code("text"))
66
+ assert_equal("<strike>text</strike>", @live.formatter.strike("text"))
67
+ assert_equal("<a href='url'>text</a>", @live.formatter.link("text", "url"))
68
+ end
69
+
70
+ def test_escape_html
71
+ # Test HTML escaping
72
+ assert_equal("&lt;tag&gt;", @live.formatter.escape_html("<tag>"))
73
+ assert_equal("&amp;", @live.formatter.escape_html("&"))
74
+ assert_equal("&quot;text&quot;", @live.formatter.escape_html('"text"'))
75
+ assert_equal("&#39;text&#39;", @live.formatter.escape_html("'text'"))
76
+ end
77
+
78
+ def test_edge_cases
79
+ # Test edge cases
80
+ assert_equal("", @live.formatter.format(nil))
81
+ assert_equal("", @live.formatter.format(""))
82
+ assert_equal("plain text", @live.formatter.format("plain text"))
83
+ end
84
+ end
@@ -0,0 +1,84 @@
1
+ require 'minitest/autorun'
2
+
3
+ require_relative '../../lib/livetext'
4
+
5
+ class TestingLivetextFormatter < Minitest::Test
6
+ def setup
7
+ @live = Livetext.new
8
+ end
9
+
10
+ def test_formatter_component_initialization
11
+ # Test that FormatterComponent is properly initialized
12
+ assert(@live.formatter, "FormatterComponent should be initialized")
13
+ assert_instance_of(Livetext::Formatter, @live.formatter)
14
+ end
15
+
16
+ def test_basic_formatting
17
+ # Test basic text formatting
18
+ assert_equal("<b>bold</b>", @live.formatter.format("*bold"))
19
+ assert_equal("<i>italic</i>", @live.formatter.format("_italic"))
20
+ assert_equal("<tt>code</tt>", @live.formatter.format("`code"))
21
+ assert_equal("<strike>strike</strike>", @live.formatter.format("~strike"))
22
+ end
23
+
24
+ def test_double_markers
25
+ # Test double markers
26
+ assert_equal("<b>bold</b>", @live.formatter.format("**bold"))
27
+ assert_equal("**", @live.formatter.format("**")) # standalone
28
+ assert_equal(" ** ", @live.formatter.format(" ** ")) # surrounded by spaces
29
+ end
30
+
31
+ def test_bracketed_markers
32
+ # Test bracketed markers
33
+ assert_equal("<b>content</b>", @live.formatter.format("*[content]"))
34
+ assert_equal("<i>content</i>", @live.formatter.format("_[content]"))
35
+ assert_equal("<tt>content</tt>", @live.formatter.format("`[content]"))
36
+ assert_equal("", @live.formatter.format("*[]")) # empty brackets disappear
37
+ end
38
+
39
+ def test_escaped_markers
40
+ # Test escaped markers
41
+ assert_equal("*literal", @live.formatter.format("\\*literal"))
42
+ assert_equal("_literal", @live.formatter.format("\\_literal"))
43
+ assert_equal("`literal", @live.formatter.format("\\`literal"))
44
+ assert_equal("~literal", @live.formatter.format("\\~literal"))
45
+ end
46
+
47
+ def test_format_line
48
+ # Test format_line method
49
+ assert_equal("<b>bold</b>", @live.formatter.format_line("*bold\n"))
50
+ assert_equal("", @live.formatter.format_line(nil))
51
+ assert_equal("", @live.formatter.format_line(""))
52
+ end
53
+
54
+ def test_format_multiple
55
+ # Test format_multiple method
56
+ lines = ["*bold\n", "_italic\n", "`code\n"]
57
+ expected = ["<b>bold</b>", "<i>italic</i>", "<tt>code</tt>"]
58
+ assert_equal(expected, @live.formatter.format_multiple(lines))
59
+ end
60
+
61
+ def test_convenience_methods
62
+ # Test convenience methods
63
+ assert_equal("<b>text</b>", @live.formatter.bold("text"))
64
+ assert_equal("<i>text</i>", @live.formatter.italic("text"))
65
+ assert_equal("<tt>text</tt>", @live.formatter.code("text"))
66
+ assert_equal("<strike>text</strike>", @live.formatter.strike("text"))
67
+ assert_equal("<a href='url'>text</a>", @live.formatter.link("text", "url"))
68
+ end
69
+
70
+ def test_escape_html
71
+ # Test HTML escaping
72
+ assert_equal("&lt;tag&gt;", @live.formatter.escape_html("<tag>"))
73
+ assert_equal("&amp;", @live.formatter.escape_html("&"))
74
+ assert_equal("&quot;text&quot;", @live.formatter.escape_html('"text"'))
75
+ assert_equal("&#39;text&#39;", @live.formatter.escape_html("'text'"))
76
+ end
77
+
78
+ def test_edge_cases
79
+ # Test edge cases
80
+ assert_equal("", @live.formatter.format(nil))
81
+ assert_equal("", @live.formatter.format(""))
82
+ assert_equal("plain text", @live.formatter.format("plain text"))
83
+ end
84
+ end