prosereflect 0.1.0 → 0.1.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +23 -4
  3. data/README.adoc +193 -12
  4. data/lib/prosereflect/attribute/base.rb +34 -0
  5. data/lib/prosereflect/attribute/bold.rb +20 -0
  6. data/lib/prosereflect/attribute/href.rb +24 -0
  7. data/lib/prosereflect/attribute/id.rb +24 -0
  8. data/lib/prosereflect/attribute.rb +13 -0
  9. data/lib/prosereflect/blockquote.rb +85 -0
  10. data/lib/prosereflect/bullet_list.rb +83 -0
  11. data/lib/prosereflect/code_block.rb +135 -0
  12. data/lib/prosereflect/code_block_wrapper.rb +66 -0
  13. data/lib/prosereflect/document.rb +99 -24
  14. data/lib/prosereflect/hard_break.rb +11 -9
  15. data/lib/prosereflect/heading.rb +64 -0
  16. data/lib/prosereflect/horizontal_rule.rb +70 -0
  17. data/lib/prosereflect/image.rb +126 -0
  18. data/lib/prosereflect/input/html.rb +505 -0
  19. data/lib/prosereflect/list_item.rb +65 -0
  20. data/lib/prosereflect/mark/base.rb +49 -0
  21. data/lib/prosereflect/mark/bold.rb +15 -0
  22. data/lib/prosereflect/mark/code.rb +14 -0
  23. data/lib/prosereflect/mark/italic.rb +15 -0
  24. data/lib/prosereflect/mark/link.rb +18 -0
  25. data/lib/prosereflect/mark/strike.rb +15 -0
  26. data/lib/prosereflect/mark/subscript.rb +15 -0
  27. data/lib/prosereflect/mark/superscript.rb +15 -0
  28. data/lib/prosereflect/mark/underline.rb +15 -0
  29. data/lib/prosereflect/mark.rb +11 -0
  30. data/lib/prosereflect/node.rb +181 -32
  31. data/lib/prosereflect/ordered_list.rb +85 -0
  32. data/lib/prosereflect/output/html.rb +374 -0
  33. data/lib/prosereflect/paragraph.rb +26 -15
  34. data/lib/prosereflect/parser.rb +111 -24
  35. data/lib/prosereflect/table.rb +40 -9
  36. data/lib/prosereflect/table_cell.rb +33 -8
  37. data/lib/prosereflect/table_header.rb +92 -0
  38. data/lib/prosereflect/table_row.rb +31 -8
  39. data/lib/prosereflect/text.rb +13 -17
  40. data/lib/prosereflect/user.rb +63 -0
  41. data/lib/prosereflect/version.rb +1 -1
  42. data/lib/prosereflect.rb +6 -0
  43. data/prosereflect.gemspec +1 -0
  44. data/spec/prosereflect/document_spec.rb +436 -36
  45. data/spec/prosereflect/hard_break_spec.rb +218 -22
  46. data/spec/prosereflect/input/html_spec.rb +797 -0
  47. data/spec/prosereflect/node_spec.rb +258 -89
  48. data/spec/prosereflect/output/html_spec.rb +369 -0
  49. data/spec/prosereflect/paragraph_spec.rb +424 -49
  50. data/spec/prosereflect/parser_spec.rb +304 -91
  51. data/spec/prosereflect/table_cell_spec.rb +268 -57
  52. data/spec/prosereflect/table_row_spec.rb +210 -40
  53. data/spec/prosereflect/table_spec.rb +392 -61
  54. data/spec/prosereflect/text_spec.rb +206 -48
  55. data/spec/prosereflect/user_spec.rb +73 -0
  56. data/spec/prosereflect_spec.rb +5 -0
  57. data/spec/support/shared_examples.rb +44 -15
  58. metadata +47 -3
  59. data/debug_loading.rb +0 -34
@@ -0,0 +1,369 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Prosereflect::Output::Html do
6
+ describe '.convert' do
7
+ it 'converts a simple document to HTML' do
8
+ document = Prosereflect::Document.new
9
+ document.add_paragraph('This is a test paragraph.')
10
+
11
+ html = described_class.convert(document)
12
+ expect(html).to eq('<p>This is a test paragraph.</p>')
13
+ end
14
+
15
+ it 'renders basic styled text correctly' do
16
+ document = Prosereflect::Document.new
17
+ paragraph = document.add_paragraph
18
+ paragraph.add_text('This is ')
19
+
20
+ bold_text = Prosereflect::Text.new(text: 'bold')
21
+ bold_text.marks = [Prosereflect::Mark::Bold.new]
22
+ paragraph.add_child(bold_text)
23
+
24
+ paragraph.add_text(' and ')
25
+
26
+ italic_text = Prosereflect::Text.new(text: 'italic')
27
+ italic_text.marks = [Prosereflect::Mark::Italic.new]
28
+ paragraph.add_child(italic_text)
29
+
30
+ paragraph.add_text(' text.')
31
+
32
+ html = described_class.convert(document)
33
+ expect(html).to eq('<p>This is <strong>bold</strong> and <em>italic</em> text.</p>')
34
+ end
35
+
36
+ it 'renders headings with mixed content correctly' do
37
+ document = Prosereflect::Document.new
38
+ heading = Prosereflect::Heading.new
39
+ heading.level = 1
40
+ heading.add_text('Title with ')
41
+
42
+ bold_text = Prosereflect::Text.new(text: 'bold')
43
+ bold_text.marks = [Prosereflect::Mark::Bold.new]
44
+ heading.add_child(bold_text)
45
+
46
+ heading.add_text(' and ')
47
+
48
+ link_text = Prosereflect::Text.new(text: 'link')
49
+ link_mark = Prosereflect::Mark::Link.new
50
+ link_mark.attrs = { 'href' => 'https://example.com' }
51
+ link_text.marks = [link_mark]
52
+ heading.add_child(link_text)
53
+
54
+ document.add_child(heading)
55
+
56
+ html = described_class.convert(document)
57
+ expect(html).to eq('<h1>Title with <strong>bold</strong> and <a href="https://example.com">link</a></h1>')
58
+ end
59
+
60
+ it 'renders lists with nested content correctly' do
61
+ document = Prosereflect::Document.new
62
+ list = Prosereflect::BulletList.new
63
+
64
+ # First item with emphasis
65
+ item1 = Prosereflect::ListItem.new
66
+ para1 = item1.add_paragraph
67
+ para1.add_text('First item with ')
68
+ em_text = Prosereflect::Text.new(text: 'emphasis')
69
+ em_text.marks = [Prosereflect::Mark::Italic.new]
70
+ para1.add_child(em_text)
71
+ list.add_child(item1)
72
+
73
+ # Second item with code
74
+ item2 = Prosereflect::ListItem.new
75
+ para2 = item2.add_paragraph
76
+ para2.add_text('Second item with ')
77
+ code_text = Prosereflect::Text.new(text: 'code')
78
+ code_text.marks = [Prosereflect::Mark::Code.new]
79
+ para2.add_child(code_text)
80
+ list.add_child(item2)
81
+
82
+ document.add_child(list)
83
+
84
+ html = described_class.convert(document)
85
+ expect(html).to eq('<ul><li><p>First item with <em>emphasis</em></p></li><li><p>Second item with <code>code</code></p></li></ul>')
86
+ end
87
+
88
+ it 'renders blockquotes with citations correctly' do
89
+ document = Prosereflect::Document.new
90
+ quote = Prosereflect::Blockquote.new
91
+ quote.citation = 'https://example.com'
92
+
93
+ para = quote.add_paragraph('A quote with ')
94
+ bold_text = Prosereflect::Text.new(text: 'bold')
95
+ bold_text.marks = [Prosereflect::Mark::Bold.new]
96
+ para.add_child(bold_text)
97
+ para.add_text(' text')
98
+
99
+ document.add_child(quote)
100
+
101
+ html = described_class.convert(document)
102
+ expect(html).to eq('<blockquote cite="https://example.com"><p>A quote with <strong>bold</strong> text</p></blockquote>')
103
+ end
104
+
105
+ it 'renders code blocks with language correctly' do
106
+ document = Prosereflect::Document.new
107
+ wrapper = Prosereflect::CodeBlockWrapper.new
108
+
109
+ code_block = wrapper.add_code_block
110
+ code_block.language = 'ruby'
111
+ code_block.content = "def example\n puts \"Hello\"\nend"
112
+
113
+ document.add_child(wrapper)
114
+
115
+ expected = <<~HTML.strip
116
+ <pre><code class="language-ruby">def example
117
+ puts "Hello"
118
+ end</code></pre>
119
+ HTML
120
+
121
+ html = described_class.convert(document)
122
+ expect(html).to eq(expected.gsub(/^ {10}/, ' '))
123
+ end
124
+
125
+ it 'renders images with attributes correctly' do
126
+ document = Prosereflect::Document.new
127
+ image = Prosereflect::Image.new
128
+ image.src = 'test.jpg'
129
+ image.alt = 'Test image'
130
+ image.title = 'Test title'
131
+ image.width = 800
132
+ image.height = 600
133
+
134
+ document.add_child(image)
135
+
136
+ html = described_class.convert(document)
137
+ expect(html).to eq('<img src="test.jpg" alt="Test image" title="Test title" width="800" height="600">')
138
+ end
139
+
140
+ it 'renders horizontal rules with styles correctly' do
141
+ document = Prosereflect::Document.new
142
+ hr = Prosereflect::HorizontalRule.new
143
+ hr.style = 'dashed'
144
+ hr.width = '80%'
145
+ hr.thickness = 2
146
+
147
+ document.add_child(hr)
148
+
149
+ html = described_class.convert(document)
150
+ expect(html).to eq('<hr style="border-style: dashed; width: 80%; border-width: 2px">')
151
+ end
152
+
153
+ it 'renders complex nested structures correctly' do
154
+ document = Prosereflect::Document.new
155
+
156
+ # Add heading
157
+ heading = Prosereflect::Heading.new
158
+ heading.level = 2
159
+ heading.add_text('Features')
160
+ document.add_child(heading)
161
+
162
+ # Add list with nested content
163
+ list = Prosereflect::BulletList.new
164
+
165
+ # List item with blockquote
166
+ item1 = Prosereflect::ListItem.new
167
+ para1 = item1.add_paragraph
168
+ para1.add_text('Quote: ')
169
+ list.add_child(item1)
170
+
171
+ quote = Prosereflect::Blockquote.new
172
+ quote.citation = 'https://example.com'
173
+ quote.add_paragraph('Nested quote')
174
+ list.add_child(quote)
175
+
176
+ # List item with code
177
+ item2 = Prosereflect::ListItem.new
178
+ para2 = item2.add_paragraph
179
+ para2.add_text('Code: ')
180
+ list.add_child(item2)
181
+
182
+ wrapper = Prosereflect::CodeBlockWrapper.new
183
+ code_block = wrapper.add_code_block
184
+ code_block.language = 'ruby'
185
+ code_block.content = 'puts "Hello"'
186
+ list.add_child(wrapper)
187
+
188
+ document.add_child(list)
189
+
190
+ html = described_class.convert(document)
191
+ expected = '<h2>Features</h2>' \
192
+ '<ul>' \
193
+ '<li><p>Quote: </p></li>' \
194
+ '<li><blockquote cite="https://example.com"><p>Nested quote</p></blockquote></li>' \
195
+ '<li><p>Code: </p></li>' \
196
+ '<li><pre><code class="language-ruby">puts "Hello"</code></pre></li>' \
197
+ '</ul>'
198
+ expect(html).to eq(expected)
199
+ end
200
+
201
+ it 'converts tables to HTML' do
202
+ document = Prosereflect::Document.new
203
+ table = document.add_table
204
+
205
+ # Add header row
206
+ table.add_header(['Header 1', 'Header 2'])
207
+
208
+ # Add data rows
209
+ table.add_row(['Row 1, Cell 1', 'Row 1, Cell 2'])
210
+ table.add_row(['Row 2, Cell 1', 'Row 2, Cell 2'])
211
+
212
+ html = described_class.convert(document)
213
+ expected = '<table>' \
214
+ '<thead><tr><th>Header 1</th><th>Header 2</th></tr></thead>' \
215
+ '<tbody>' \
216
+ '<tr><td>Row 1, Cell 1</td><td>Row 1, Cell 2</td></tr>' \
217
+ '<tr><td>Row 2, Cell 1</td><td>Row 2, Cell 2</td></tr>' \
218
+ '</tbody>' \
219
+ '</table>'
220
+ expect(html).to eq(expected)
221
+ end
222
+
223
+ it 'converts complex table headers' do
224
+ document = Prosereflect::Document.new
225
+ table = document.add_table
226
+
227
+ # Add header row with complex cells
228
+ header = table.add_header(['Product'])
229
+ header.cells.first.scope = 'col'
230
+ header.cells.first.abbr = 'Prod'
231
+ header.cells.first.colspan = 2
232
+
233
+ html = described_class.convert(document)
234
+ expected = '<table><thead><tr><th scope="col" abbr="Prod" colspan="2">Product</th></tr></thead><tbody></tbody></table>'
235
+ expect(html).to eq(expected)
236
+ end
237
+
238
+ it 'converts text with all mark types' do
239
+ document = Prosereflect::Document.new
240
+ paragraph = document.add_paragraph
241
+
242
+ paragraph.add_text('struck', [Prosereflect::Mark::Strike.new])
243
+ paragraph.add_text(' and ')
244
+ paragraph.add_text('underlined', [Prosereflect::Mark::Underline.new])
245
+ paragraph.add_text(' and ')
246
+ paragraph.add_text('sub', [Prosereflect::Mark::Subscript.new])
247
+ paragraph.add_text(' and ')
248
+ paragraph.add_text('super', [Prosereflect::Mark::Superscript.new])
249
+
250
+ html = described_class.convert(document)
251
+ expect(html).to eq('<p><del>struck</del> and <u>underlined</u> and <sub>sub</sub> and <sup>super</sup></p>')
252
+ end
253
+
254
+ it 'converts images with all attributes' do
255
+ document = Prosereflect::Document.new
256
+ image = document.add_image('example.jpg', 'Example image')
257
+ image.title = 'Image tooltip'
258
+ image.width = 800
259
+ image.height = 600
260
+
261
+ html = described_class.convert(document)
262
+ expect(html).to eq('<img src="example.jpg" alt="Example image" title="Image tooltip" width="800" height="600">')
263
+ end
264
+
265
+ it 'converts code blocks with wrapper attributes' do
266
+ document = Prosereflect::Document.new
267
+ wrapper = document.add_code_block_wrapper
268
+ wrapper.line_numbers = true
269
+
270
+ code_block = wrapper.add_code_block
271
+ code_block.language = 'ruby'
272
+ code_block.content = "def example\n puts 'Hello'\nend"
273
+
274
+ html = described_class.convert(document)
275
+ expected = '<pre data-line-numbers="true"><code class="language-ruby">def example
276
+ puts \'Hello\'
277
+ end</code></pre>'
278
+ expect(html).to eq(expected)
279
+ end
280
+
281
+ it 'handles complex nested structures with tables' do
282
+ document = Prosereflect::Document.new
283
+
284
+ # Add a paragraph with styled text
285
+ document.add_paragraph('Paragraph 1')
286
+
287
+ # Add a paragraph with mixed styling
288
+ p2 = document.add_paragraph
289
+ p2.add_text('Paragraph ')
290
+ p2.add_text('2', [Prosereflect::Mark::Bold.new])
291
+
292
+ # Add a table
293
+ table = document.add_table
294
+ table.add_row(['Cell 1', 'Cell 2'])
295
+
296
+ html = described_class.convert(document)
297
+ expected = '<p>Paragraph 1</p><p>Paragraph <strong>2</strong></p><table><tbody><tr><td>Cell 1</td><td>Cell 2</td></tr></tbody></table>'
298
+ expect(html).to eq(expected)
299
+ end
300
+
301
+ it 'renders user mentions correctly' do
302
+ document = Prosereflect::Document.new
303
+ user = Prosereflect::User.new
304
+ user.id = '123'
305
+ document.add_child(user)
306
+
307
+ html = described_class.convert(document)
308
+ expect(html).to eq('<user-mention data-id="123"></user-mention>')
309
+ end
310
+
311
+ it 'renders user mentions in paragraphs' do
312
+ document = Prosereflect::Document.new
313
+ paragraph = document.add_paragraph
314
+ paragraph.add_text('Hello ')
315
+
316
+ user = Prosereflect::User.new
317
+ user.id = '123'
318
+ paragraph.add_child(user)
319
+
320
+ paragraph.add_text('!')
321
+
322
+ html = described_class.convert(document)
323
+ expect(html).to eq('<p>Hello <user-mention data-id="123"></user-mention>!</p>')
324
+ end
325
+
326
+ it 'renders multiple user mentions' do
327
+ document = Prosereflect::Document.new
328
+ paragraph = document.add_paragraph
329
+ paragraph.add_text('Mentioned: ')
330
+
331
+ user1 = Prosereflect::User.new
332
+ user1.id = '123'
333
+ paragraph.add_child(user1)
334
+
335
+ paragraph.add_text(' and ')
336
+
337
+ user2 = Prosereflect::User.new
338
+ user2.id = '456'
339
+ paragraph.add_child(user2)
340
+
341
+ html = described_class.convert(document)
342
+ expect(html).to eq('<p>Mentioned: <user-mention data-id="123"></user-mention> and <user-mention data-id="456"></user-mention></p>')
343
+ end
344
+
345
+ it 'renders user mentions with other marks' do
346
+ document = Prosereflect::Document.new
347
+ paragraph = document.add_paragraph
348
+
349
+ bold_text = Prosereflect::Text.new(text: 'Bold')
350
+ bold_text.marks = [Prosereflect::Mark::Bold.new]
351
+ paragraph.add_child(bold_text)
352
+
353
+ paragraph.add_text(' and ')
354
+
355
+ user = Prosereflect::User.new
356
+ user.id = '123'
357
+ paragraph.add_child(user)
358
+
359
+ paragraph.add_text(' and ')
360
+
361
+ italic_text = Prosereflect::Text.new(text: 'italic')
362
+ italic_text.marks = [Prosereflect::Mark::Italic.new]
363
+ paragraph.add_child(italic_text)
364
+
365
+ html = described_class.convert(document)
366
+ expect(html).to eq('<p><strong>Bold</strong> and <user-mention data-id="123"></user-mention> and <em>italic</em></p>')
367
+ end
368
+ end
369
+ end