prosereflect 0.1.1 → 0.3.0

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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +97 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +19 -1
  8. data/.rubocop_todo.yml +119 -183
  9. data/CLAUDE.md +78 -0
  10. data/Gemfile +8 -4
  11. data/README.adoc +2 -0
  12. data/Rakefile +3 -3
  13. data/docs/Gemfile +10 -0
  14. data/docs/INDEX.adoc +45 -0
  15. data/docs/_advanced/index.adoc +15 -0
  16. data/docs/_advanced/schema.adoc +112 -0
  17. data/docs/_advanced/step-map.adoc +66 -0
  18. data/docs/_advanced/steps.adoc +88 -0
  19. data/docs/_advanced/test-builder.adoc +61 -0
  20. data/docs/_advanced/transform.adoc +92 -0
  21. data/docs/_config.yml +174 -0
  22. data/docs/_features/html-input.adoc +69 -0
  23. data/docs/_features/html-output.adoc +45 -0
  24. data/docs/_features/index.adoc +15 -0
  25. data/docs/_features/marks.adoc +86 -0
  26. data/docs/_features/node-types.adoc +124 -0
  27. data/docs/_features/user-mentions.adoc +47 -0
  28. data/docs/_guides/custom-nodes.adoc +107 -0
  29. data/docs/_guides/index.adoc +13 -0
  30. data/docs/_guides/round-trip-html.adoc +91 -0
  31. data/docs/_guides/serialization.adoc +109 -0
  32. data/docs/_pages/index.adoc +67 -0
  33. data/docs/_reference/document-api.adoc +49 -0
  34. data/docs/_reference/index.adoc +14 -0
  35. data/docs/_reference/node-api.adoc +79 -0
  36. data/docs/_reference/schema-api.adoc +95 -0
  37. data/docs/_reference/transform-api.adoc +77 -0
  38. data/docs/_understanding/document-model.adoc +65 -0
  39. data/docs/_understanding/fragment.adoc +52 -0
  40. data/docs/_understanding/index.adoc +14 -0
  41. data/docs/_understanding/resolved-position.adoc +53 -0
  42. data/docs/_understanding/slice.adoc +54 -0
  43. data/docs/lychee.toml +63 -0
  44. data/lib/prosereflect/attribute/base.rb +4 -6
  45. data/lib/prosereflect/attribute/bold.rb +2 -4
  46. data/lib/prosereflect/attribute/href.rb +1 -3
  47. data/lib/prosereflect/attribute/id.rb +7 -7
  48. data/lib/prosereflect/attribute.rb +4 -7
  49. data/lib/prosereflect/blockquote.rb +19 -11
  50. data/lib/prosereflect/bullet_list.rb +36 -29
  51. data/lib/prosereflect/code_block.rb +23 -27
  52. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  53. data/lib/prosereflect/document.rb +14 -22
  54. data/lib/prosereflect/fragment.rb +249 -0
  55. data/lib/prosereflect/hard_break.rb +6 -6
  56. data/lib/prosereflect/heading.rb +14 -15
  57. data/lib/prosereflect/horizontal_rule.rb +23 -14
  58. data/lib/prosereflect/image.rb +32 -23
  59. data/lib/prosereflect/input/html.rb +179 -104
  60. data/lib/prosereflect/input.rb +7 -0
  61. data/lib/prosereflect/list_item.rb +11 -12
  62. data/lib/prosereflect/mark/base.rb +9 -11
  63. data/lib/prosereflect/mark/bold.rb +1 -3
  64. data/lib/prosereflect/mark/code.rb +1 -3
  65. data/lib/prosereflect/mark/italic.rb +1 -3
  66. data/lib/prosereflect/mark/link.rb +1 -3
  67. data/lib/prosereflect/mark/strike.rb +1 -3
  68. data/lib/prosereflect/mark/subscript.rb +1 -3
  69. data/lib/prosereflect/mark/superscript.rb +1 -3
  70. data/lib/prosereflect/mark/underline.rb +1 -3
  71. data/lib/prosereflect/mark.rb +9 -5
  72. data/lib/prosereflect/node.rb +171 -33
  73. data/lib/prosereflect/ordered_list.rb +17 -14
  74. data/lib/prosereflect/output/html.rb +279 -50
  75. data/lib/prosereflect/output.rb +7 -0
  76. data/lib/prosereflect/paragraph.rb +11 -13
  77. data/lib/prosereflect/parser.rb +56 -66
  78. data/lib/prosereflect/resolved_pos.rb +256 -0
  79. data/lib/prosereflect/schema/attribute.rb +57 -0
  80. data/lib/prosereflect/schema/content_match.rb +656 -0
  81. data/lib/prosereflect/schema/fragment.rb +166 -0
  82. data/lib/prosereflect/schema/mark.rb +121 -0
  83. data/lib/prosereflect/schema/mark_type.rb +130 -0
  84. data/lib/prosereflect/schema/node.rb +236 -0
  85. data/lib/prosereflect/schema/node_type.rb +274 -0
  86. data/lib/prosereflect/schema/schema_main.rb +190 -0
  87. data/lib/prosereflect/schema/spec.rb +92 -0
  88. data/lib/prosereflect/schema.rb +39 -0
  89. data/lib/prosereflect/table.rb +12 -13
  90. data/lib/prosereflect/table_cell.rb +13 -13
  91. data/lib/prosereflect/table_header.rb +17 -17
  92. data/lib/prosereflect/table_row.rb +12 -12
  93. data/lib/prosereflect/text.rb +35 -11
  94. data/lib/prosereflect/transform/attr_step.rb +157 -0
  95. data/lib/prosereflect/transform/insert_step.rb +115 -0
  96. data/lib/prosereflect/transform/mapping.rb +82 -0
  97. data/lib/prosereflect/transform/mark_step.rb +269 -0
  98. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  99. data/lib/prosereflect/transform/replace_step.rb +157 -0
  100. data/lib/prosereflect/transform/slice.rb +91 -0
  101. data/lib/prosereflect/transform/step.rb +89 -0
  102. data/lib/prosereflect/transform/step_map.rb +126 -0
  103. data/lib/prosereflect/transform/structure.rb +120 -0
  104. data/lib/prosereflect/transform/transform.rb +341 -0
  105. data/lib/prosereflect/transform.rb +26 -0
  106. data/lib/prosereflect/user.rb +15 -15
  107. data/lib/prosereflect/version.rb +1 -1
  108. data/lib/prosereflect.rb +30 -17
  109. data/prosereflect.gemspec +17 -16
  110. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  111. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  112. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  113. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  114. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  115. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  116. data/spec/fixtures/documents/table_doc.yaml +32 -0
  117. data/spec/fixtures/documents/transform_test.yaml +14 -0
  118. data/spec/fixtures/schema/custom_schema.rb +37 -0
  119. data/spec/fixtures/schema/test_schema.rb +46 -0
  120. data/spec/fixtures/test_builder/helpers.rb +212 -0
  121. data/spec/prosereflect/document_spec.rb +332 -330
  122. data/spec/prosereflect/fragment_spec.rb +273 -0
  123. data/spec/prosereflect/hard_break_spec.rb +125 -125
  124. data/spec/prosereflect/input/html_spec.rb +718 -522
  125. data/spec/prosereflect/node_spec.rb +311 -182
  126. data/spec/prosereflect/output/html_spec.rb +105 -105
  127. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  128. data/spec/prosereflect/paragraph_spec.rb +275 -274
  129. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  130. data/spec/prosereflect/parser_spec.rb +185 -180
  131. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  132. data/spec/prosereflect/schema/conftest.rb +68 -0
  133. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  134. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  135. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  136. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  137. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  138. data/spec/prosereflect/table_cell_spec.rb +183 -183
  139. data/spec/prosereflect/table_row_spec.rb +149 -149
  140. data/spec/prosereflect/table_spec.rb +320 -318
  141. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  142. data/spec/prosereflect/text_spec.rb +133 -132
  143. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  144. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  145. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  146. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  147. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  148. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  149. data/spec/prosereflect/transform/step_spec.rb +211 -0
  150. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  151. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  152. data/spec/prosereflect/user_spec.rb +31 -28
  153. data/spec/prosereflect_spec.rb +28 -26
  154. data/spec/spec_helper.rb +7 -6
  155. data/spec/support/matchers.rb +6 -6
  156. data/spec/support/shared_examples.rb +49 -49
  157. metadata +96 -5
  158. data/spec/prosereflect/version_spec.rb +0 -11
@@ -1,423 +1,424 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  RSpec.describe Prosereflect::Paragraph do
6
- describe 'initialization' do
7
- it 'initializes as a paragraph node' do
8
- paragraph = described_class.new({ 'type' => 'paragraph' })
9
- expect(paragraph.type).to eq('paragraph')
6
+ describe "initialization" do
7
+ it "initializes as a paragraph node" do
8
+ paragraph = described_class.new({ "type" => "paragraph" })
9
+ expect(paragraph.type).to eq("paragraph")
10
10
  end
11
11
 
12
- it 'initializes with content' do
13
- text_node = Prosereflect::Text.new(text: 'Hello')
12
+ it "initializes with content" do
13
+ text_node = Prosereflect::Text.new(text: "Hello")
14
14
  paragraph = described_class.new
15
15
  paragraph.add_child(text_node)
16
16
 
17
17
  expect(paragraph.content.first).to be_a(Prosereflect::Text)
18
- expect(paragraph.content.first.text).to eq('Hello')
18
+ expect(paragraph.content.first.text).to eq("Hello")
19
19
  end
20
20
 
21
- it 'initializes with complex content' do
21
+ it "initializes with complex content" do
22
22
  content = [
23
- { 'type' => 'text', 'text' => 'Hello', 'marks' => [{ 'type' => 'bold' }] }
23
+ { "type" => "text", "text" => "Hello",
24
+ "marks" => [{ "type" => "bold" }] },
24
25
  ]
25
26
  paragraph = described_class.new(content: content)
26
27
 
27
- expect(paragraph.content[0].raw_marks.first.type).to eq('bold')
28
+ expect(paragraph.content[0].raw_marks.first.type).to eq("bold")
28
29
  end
29
30
  end
30
31
 
31
- describe '.create' do
32
- it 'creates a simple paragraph with text' do
32
+ describe ".create" do
33
+ it "creates a simple paragraph with text" do
33
34
  paragraph = described_class.new
34
- paragraph.add_text('This is a test paragraph.')
35
+ paragraph.add_text("This is a test paragraph.")
35
36
 
36
37
  expected = {
37
- 'type' => 'paragraph',
38
- 'content' => [{
39
- 'type' => 'text',
40
- 'text' => 'This is a test paragraph.'
41
- }]
38
+ "type" => "paragraph",
39
+ "content" => [{
40
+ "type" => "text",
41
+ "text" => "This is a test paragraph.",
42
+ }],
42
43
  }
43
44
 
44
45
  expect(paragraph.to_h).to eq(expected)
45
46
  end
46
47
 
47
- it 'creates a paragraph with styled text' do
48
+ it "creates a paragraph with styled text" do
48
49
  paragraph = described_class.new
49
- paragraph.add_text('This is ')
50
- paragraph.add_text('bold', [Prosereflect::Mark::Bold.new])
51
- paragraph.add_text(' and ')
52
- paragraph.add_text('italic', [Prosereflect::Mark::Italic.new])
53
- paragraph.add_text(' text.')
50
+ paragraph.add_text("This is ")
51
+ paragraph.add_text("bold", [Prosereflect::Mark::Bold.new])
52
+ paragraph.add_text(" and ")
53
+ paragraph.add_text("italic", [Prosereflect::Mark::Italic.new])
54
+ paragraph.add_text(" text.")
54
55
 
55
56
  expected = {
56
- 'type' => 'paragraph',
57
- 'content' => [{
58
- 'type' => 'text',
59
- 'text' => 'This is '
57
+ "type" => "paragraph",
58
+ "content" => [{
59
+ "type" => "text",
60
+ "text" => "This is ",
60
61
  }, {
61
- 'type' => 'text',
62
- 'text' => 'bold',
63
- 'marks' => [{
64
- 'type' => 'bold'
65
- }]
62
+ "type" => "text",
63
+ "text" => "bold",
64
+ "marks" => [{
65
+ "type" => "bold",
66
+ }],
66
67
  }, {
67
- 'type' => 'text',
68
- 'text' => ' and '
68
+ "type" => "text",
69
+ "text" => " and ",
69
70
  }, {
70
- 'type' => 'text',
71
- 'text' => 'italic',
72
- 'marks' => [{
73
- 'type' => 'italic'
74
- }]
71
+ "type" => "text",
72
+ "text" => "italic",
73
+ "marks" => [{
74
+ "type" => "italic",
75
+ }],
75
76
  }, {
76
- 'type' => 'text',
77
- 'text' => ' text.'
78
- }]
77
+ "type" => "text",
78
+ "text" => " text.",
79
+ }],
79
80
  }
80
81
 
81
82
  expect(paragraph.to_h).to eq(expected)
82
83
  end
83
84
 
84
- it 'creates a paragraph with multiple text styles' do
85
+ it "creates a paragraph with multiple text styles" do
85
86
  paragraph = described_class.new
86
- paragraph.add_text('This is ')
87
- paragraph.add_text('struck', [Prosereflect::Mark::Strike.new])
88
- paragraph.add_text(' and ')
89
- paragraph.add_text('underlined', [Prosereflect::Mark::Underline.new])
90
- paragraph.add_text(' and ')
91
- paragraph.add_text('sub', [Prosereflect::Mark::Subscript.new])
92
- paragraph.add_text(' and ')
93
- paragraph.add_text('super', [Prosereflect::Mark::Superscript.new])
94
- paragraph.add_text(' text.')
87
+ paragraph.add_text("This is ")
88
+ paragraph.add_text("struck", [Prosereflect::Mark::Strike.new])
89
+ paragraph.add_text(" and ")
90
+ paragraph.add_text("underlined", [Prosereflect::Mark::Underline.new])
91
+ paragraph.add_text(" and ")
92
+ paragraph.add_text("sub", [Prosereflect::Mark::Subscript.new])
93
+ paragraph.add_text(" and ")
94
+ paragraph.add_text("super", [Prosereflect::Mark::Superscript.new])
95
+ paragraph.add_text(" text.")
95
96
 
96
97
  expected = {
97
- 'type' => 'paragraph',
98
- 'content' => [{
99
- 'type' => 'text',
100
- 'text' => 'This is '
98
+ "type" => "paragraph",
99
+ "content" => [{
100
+ "type" => "text",
101
+ "text" => "This is ",
101
102
  }, {
102
- 'type' => 'text',
103
- 'text' => 'struck',
104
- 'marks' => [{
105
- 'type' => 'strike'
106
- }]
103
+ "type" => "text",
104
+ "text" => "struck",
105
+ "marks" => [{
106
+ "type" => "strike",
107
+ }],
107
108
  }, {
108
- 'type' => 'text',
109
- 'text' => ' and '
109
+ "type" => "text",
110
+ "text" => " and ",
110
111
  }, {
111
- 'type' => 'text',
112
- 'text' => 'underlined',
113
- 'marks' => [{
114
- 'type' => 'underline'
115
- }]
112
+ "type" => "text",
113
+ "text" => "underlined",
114
+ "marks" => [{
115
+ "type" => "underline",
116
+ }],
116
117
  }, {
117
- 'type' => 'text',
118
- 'text' => ' and '
118
+ "type" => "text",
119
+ "text" => " and ",
119
120
  }, {
120
- 'type' => 'text',
121
- 'text' => 'sub',
122
- 'marks' => [{
123
- 'type' => 'subscript'
124
- }]
121
+ "type" => "text",
122
+ "text" => "sub",
123
+ "marks" => [{
124
+ "type" => "subscript",
125
+ }],
125
126
  }, {
126
- 'type' => 'text',
127
- 'text' => ' and '
127
+ "type" => "text",
128
+ "text" => " and ",
128
129
  }, {
129
- 'type' => 'text',
130
- 'text' => 'super',
131
- 'marks' => [{
132
- 'type' => 'superscript'
133
- }]
130
+ "type" => "text",
131
+ "text" => "super",
132
+ "marks" => [{
133
+ "type" => "superscript",
134
+ }],
134
135
  }, {
135
- 'type' => 'text',
136
- 'text' => ' text.'
137
- }]
136
+ "type" => "text",
137
+ "text" => " text.",
138
+ }],
138
139
  }
139
140
 
140
141
  expect(paragraph.to_h).to eq(expected)
141
142
  end
142
143
 
143
- it 'creates a paragraph with mixed text styles' do
144
+ it "creates a paragraph with mixed text styles" do
144
145
  paragraph = described_class.new
145
- paragraph.add_text('Bold and italic', [
146
+ paragraph.add_text("Bold and italic", [
146
147
  Prosereflect::Mark::Bold.new,
147
- Prosereflect::Mark::Italic.new
148
+ Prosereflect::Mark::Italic.new,
148
149
  ])
149
- paragraph.add_text(' and ')
150
- paragraph.add_text('underlined and struck', [
150
+ paragraph.add_text(" and ")
151
+ paragraph.add_text("underlined and struck", [
151
152
  Prosereflect::Mark::Underline.new,
152
- Prosereflect::Mark::Strike.new
153
+ Prosereflect::Mark::Strike.new,
153
154
  ])
154
155
 
155
156
  expected = {
156
- 'type' => 'paragraph',
157
- 'content' => [{
158
- 'type' => 'text',
159
- 'text' => 'Bold and italic',
160
- 'marks' => [{
161
- 'type' => 'bold'
157
+ "type" => "paragraph",
158
+ "content" => [{
159
+ "type" => "text",
160
+ "text" => "Bold and italic",
161
+ "marks" => [{
162
+ "type" => "bold",
162
163
  }, {
163
- 'type' => 'italic'
164
- }]
164
+ "type" => "italic",
165
+ }],
165
166
  }, {
166
- 'type' => 'text',
167
- 'text' => ' and '
167
+ "type" => "text",
168
+ "text" => " and ",
168
169
  }, {
169
- 'type' => 'text',
170
- 'text' => 'underlined and struck',
171
- 'marks' => [{
172
- 'type' => 'underline'
170
+ "type" => "text",
171
+ "text" => "underlined and struck",
172
+ "marks" => [{
173
+ "type" => "underline",
173
174
  }, {
174
- 'type' => 'strike'
175
- }]
176
- }]
175
+ "type" => "strike",
176
+ }],
177
+ }],
177
178
  }
178
179
 
179
180
  expect(paragraph.to_h).to eq(expected)
180
181
  end
181
182
 
182
- it 'creates a paragraph with line breaks' do
183
+ it "creates a paragraph with line breaks" do
183
184
  paragraph = described_class.new
184
- paragraph.add_text('First line')
185
+ paragraph.add_text("First line")
185
186
  paragraph.add_hard_break
186
- paragraph.add_text('Second line')
187
+ paragraph.add_text("Second line")
187
188
  paragraph.add_hard_break
188
- paragraph.add_text('Third line')
189
+ paragraph.add_text("Third line")
189
190
 
190
191
  expected = {
191
- 'type' => 'paragraph',
192
- 'content' => [{
193
- 'type' => 'text',
194
- 'text' => 'First line'
192
+ "type" => "paragraph",
193
+ "content" => [{
194
+ "type" => "text",
195
+ "text" => "First line",
195
196
  }, {
196
- 'type' => 'hard_break'
197
+ "type" => "hard_break",
197
198
  }, {
198
- 'type' => 'text',
199
- 'text' => 'Second line'
199
+ "type" => "text",
200
+ "text" => "Second line",
200
201
  }, {
201
- 'type' => 'hard_break'
202
+ "type" => "hard_break",
202
203
  }, {
203
- 'type' => 'text',
204
- 'text' => 'Third line'
205
- }]
204
+ "type" => "text",
205
+ "text" => "Third line",
206
+ }],
206
207
  }
207
208
 
208
209
  expect(paragraph.to_h).to eq(expected)
209
210
  end
210
211
 
211
- it 'creates a paragraph with alignment' do
212
- paragraph = described_class.new(attrs: { 'align' => 'center' })
213
- paragraph.add_text('Centered text')
212
+ it "creates a paragraph with alignment" do
213
+ paragraph = described_class.new(attrs: { "align" => "center" })
214
+ paragraph.add_text("Centered text")
214
215
 
215
216
  expected = {
216
- 'type' => 'paragraph',
217
- 'attrs' => {
218
- 'align' => 'center'
217
+ "type" => "paragraph",
218
+ "attrs" => {
219
+ "align" => "center",
219
220
  },
220
- 'content' => [{
221
- 'type' => 'text',
222
- 'text' => 'Centered text'
223
- }]
221
+ "content" => [{
222
+ "type" => "text",
223
+ "text" => "Centered text",
224
+ }],
224
225
  }
225
226
 
226
227
  expect(paragraph.to_h).to eq(expected)
227
228
  end
228
229
 
229
- it 'creates a paragraph with complex content' do
230
+ it "creates a paragraph with complex content" do
230
231
  paragraph = described_class.new
231
- paragraph.add_text('A formula: ')
232
- paragraph.add_text('H', [Prosereflect::Mark::Bold.new])
233
- paragraph.add_text('2', [Prosereflect::Mark::Subscript.new])
234
- paragraph.add_text('O + 2H')
235
- paragraph.add_text('2', [Prosereflect::Mark::Subscript.new])
236
- paragraph.add_text(' → 2H')
237
- paragraph.add_text('2', [Prosereflect::Mark::Subscript.new])
238
- paragraph.add_text('O')
232
+ paragraph.add_text("A formula: ")
233
+ paragraph.add_text("H", [Prosereflect::Mark::Bold.new])
234
+ paragraph.add_text("2", [Prosereflect::Mark::Subscript.new])
235
+ paragraph.add_text("O + 2H")
236
+ paragraph.add_text("2", [Prosereflect::Mark::Subscript.new])
237
+ paragraph.add_text(" → 2H")
238
+ paragraph.add_text("2", [Prosereflect::Mark::Subscript.new])
239
+ paragraph.add_text("O")
239
240
 
240
241
  expected = {
241
- 'type' => 'paragraph',
242
- 'content' => [{
243
- 'type' => 'text',
244
- 'text' => 'A formula: '
242
+ "type" => "paragraph",
243
+ "content" => [{
244
+ "type" => "text",
245
+ "text" => "A formula: ",
245
246
  }, {
246
- 'type' => 'text',
247
- 'text' => 'H',
248
- 'marks' => [{
249
- 'type' => 'bold'
250
- }]
247
+ "type" => "text",
248
+ "text" => "H",
249
+ "marks" => [{
250
+ "type" => "bold",
251
+ }],
251
252
  }, {
252
- 'type' => 'text',
253
- 'text' => '2',
254
- 'marks' => [{
255
- 'type' => 'subscript'
256
- }]
253
+ "type" => "text",
254
+ "text" => "2",
255
+ "marks" => [{
256
+ "type" => "subscript",
257
+ }],
257
258
  }, {
258
- 'type' => 'text',
259
- 'text' => 'O + 2H'
259
+ "type" => "text",
260
+ "text" => "O + 2H",
260
261
  }, {
261
- 'type' => 'text',
262
- 'text' => '2',
263
- 'marks' => [{
264
- 'type' => 'subscript'
265
- }]
262
+ "type" => "text",
263
+ "text" => "2",
264
+ "marks" => [{
265
+ "type" => "subscript",
266
+ }],
266
267
  }, {
267
- 'type' => 'text',
268
- 'text' => ' → 2H'
268
+ "type" => "text",
269
+ "text" => " → 2H",
269
270
  }, {
270
- 'type' => 'text',
271
- 'text' => '2',
272
- 'marks' => [{
273
- 'type' => 'subscript'
274
- }]
271
+ "type" => "text",
272
+ "text" => "2",
273
+ "marks" => [{
274
+ "type" => "subscript",
275
+ }],
275
276
  }, {
276
- 'type' => 'text',
277
- 'text' => 'O'
278
- }]
277
+ "type" => "text",
278
+ "text" => "O",
279
+ }],
279
280
  }
280
281
 
281
282
  expect(paragraph.to_h).to eq(expected)
282
283
  end
283
284
 
284
- it 'creates a paragraph with mathematical expressions' do
285
+ it "creates a paragraph with mathematical expressions" do
285
286
  paragraph = described_class.new
286
- paragraph.add_text('The quadratic formula: x = -b ± ')
287
- paragraph.add_text('', [Prosereflect::Mark::Bold.new])
288
- paragraph.add_text('(b')
289
- paragraph.add_text('2', [Prosereflect::Mark::Superscript.new])
290
- paragraph.add_text(' - 4ac) / 2a')
287
+ paragraph.add_text("The quadratic formula: x = -b ± ")
288
+ paragraph.add_text("", [Prosereflect::Mark::Bold.new])
289
+ paragraph.add_text("(b")
290
+ paragraph.add_text("2", [Prosereflect::Mark::Superscript.new])
291
+ paragraph.add_text(" - 4ac) / 2a")
291
292
 
292
293
  expected = {
293
- 'type' => 'paragraph',
294
- 'content' => [{
295
- 'type' => 'text',
296
- 'text' => 'The quadratic formula: x = -b ± '
294
+ "type" => "paragraph",
295
+ "content" => [{
296
+ "type" => "text",
297
+ "text" => "The quadratic formula: x = -b ± ",
297
298
  }, {
298
- 'type' => 'text',
299
- 'text' => '',
300
- 'marks' => [{
301
- 'type' => 'bold'
302
- }]
299
+ "type" => "text",
300
+ "text" => "",
301
+ "marks" => [{
302
+ "type" => "bold",
303
+ }],
303
304
  }, {
304
- 'type' => 'text',
305
- 'text' => '(b'
305
+ "type" => "text",
306
+ "text" => "(b",
306
307
  }, {
307
- 'type' => 'text',
308
- 'text' => '2',
309
- 'marks' => [{
310
- 'type' => 'superscript'
311
- }]
308
+ "type" => "text",
309
+ "text" => "2",
310
+ "marks" => [{
311
+ "type" => "superscript",
312
+ }],
312
313
  }, {
313
- 'type' => 'text',
314
- 'text' => ' - 4ac) / 2a'
315
- }]
314
+ "type" => "text",
315
+ "text" => " - 4ac) / 2a",
316
+ }],
316
317
  }
317
318
 
318
319
  expect(paragraph.to_h).to eq(expected)
319
320
  end
320
321
  end
321
322
 
322
- describe '#text_nodes' do
323
- it 'returns all text nodes in the paragraph' do
323
+ describe "#text_nodes" do
324
+ it "returns all text nodes in the paragraph" do
324
325
  paragraph = described_class.new
325
- paragraph.add_text('First text')
326
- paragraph.add_text('Second text')
326
+ paragraph.add_text("First text")
327
+ paragraph.add_text("Second text")
327
328
 
328
329
  expect(paragraph.text_nodes.size).to eq(2)
329
330
  expect(paragraph.text_nodes).to all(be_a(Prosereflect::Text))
330
331
  end
331
332
 
332
- it 'returns empty array for paragraph with no text nodes' do
333
+ it "returns empty array for paragraph with no text nodes" do
333
334
  paragraph = described_class.new
334
335
  expect(paragraph.text_nodes).to eq([])
335
336
  end
336
337
 
337
- it 'returns text nodes with marks' do
338
+ it "returns text nodes with marks" do
338
339
  paragraph = described_class.new
339
- paragraph.add_text('Hello', [Prosereflect::Mark::Bold.new])
340
+ paragraph.add_text("Hello", [Prosereflect::Mark::Bold.new])
340
341
 
341
- expect(paragraph.text_nodes[0].raw_marks.first.type).to eq('bold')
342
+ expect(paragraph.text_nodes[0].raw_marks.first.type).to eq("bold")
342
343
  end
343
344
 
344
- it 'ignores non-text nodes' do
345
+ it "ignores non-text nodes" do
345
346
  paragraph = described_class.new
346
- paragraph.add_text('Text')
347
+ paragraph.add_text("Text")
347
348
  paragraph.add_hard_break
348
- paragraph.add_text('More text')
349
+ paragraph.add_text("More text")
349
350
 
350
351
  expect(paragraph.text_nodes.size).to eq(2)
351
- expect(paragraph.text_nodes.map(&:text)).to eq(['Text', 'More text'])
352
+ expect(paragraph.text_nodes.map(&:text)).to eq(["Text", "More text"])
352
353
  end
353
354
  end
354
355
 
355
- describe '#text_content' do
356
- it 'returns plain text content without marks' do
356
+ describe "#text_content" do
357
+ it "returns plain text content without marks" do
357
358
  paragraph = described_class.new
358
- paragraph.add_text('This is ')
359
- paragraph.add_text('bold', [Prosereflect::Mark::Bold.new])
360
- paragraph.add_text(' and ')
361
- paragraph.add_text('italic', [Prosereflect::Mark::Italic.new])
362
- paragraph.add_text(' text.')
359
+ paragraph.add_text("This is ")
360
+ paragraph.add_text("bold", [Prosereflect::Mark::Bold.new])
361
+ paragraph.add_text(" and ")
362
+ paragraph.add_text("italic", [Prosereflect::Mark::Italic.new])
363
+ paragraph.add_text(" text.")
363
364
 
364
- expect(paragraph.text_content).to eq('This is bold and italic text.')
365
+ expect(paragraph.text_content).to eq("This is bold and italic text.")
365
366
  end
366
367
 
367
- it 'handles line breaks in text content' do
368
+ it "handles line breaks in text content" do
368
369
  paragraph = described_class.new
369
- paragraph.add_text('First line')
370
+ paragraph.add_text("First line")
370
371
  paragraph.add_hard_break
371
- paragraph.add_text('Second line')
372
+ paragraph.add_text("Second line")
372
373
 
373
374
  expect(paragraph.text_content).to eq("First line\nSecond line")
374
375
  end
375
376
 
376
- it 'handles empty paragraphs' do
377
+ it "handles empty paragraphs" do
377
378
  paragraph = described_class.new
378
- expect(paragraph.text_content).to eq('')
379
+ expect(paragraph.text_content).to eq("")
379
380
  end
380
381
  end
381
382
 
382
- describe '#add_text' do
383
- it 'adds text to the paragraph' do
383
+ describe "#add_text" do
384
+ it "adds text to the paragraph" do
384
385
  paragraph = described_class.new
385
- text_node = paragraph.add_text('Hello world')
386
+ text_node = paragraph.add_text("Hello world")
386
387
 
387
388
  expect(paragraph.content.size).to eq(1)
388
389
  expect(text_node).to be_a(Prosereflect::Text)
389
- expect(text_node.text).to eq('Hello world')
390
+ expect(text_node.text).to eq("Hello world")
390
391
  end
391
392
 
392
- it 'adds text with marks' do
393
+ it "adds text with marks" do
393
394
  paragraph = described_class.new
394
395
  marks = [Prosereflect::Mark::Bold.new]
395
- text_node = paragraph.add_text('Hello', marks)
396
+ text_node = paragraph.add_text("Hello", marks)
396
397
 
397
398
  expect(text_node.raw_marks).to eq(marks)
398
399
  end
399
400
 
400
- it 'adds text with multiple marks' do
401
+ it "adds text with multiple marks" do
401
402
  paragraph = described_class.new
402
403
  marks = [
403
404
  Prosereflect::Mark::Bold.new,
404
405
  Prosereflect::Mark::Italic.new,
405
- Prosereflect::Mark::Underline.new
406
+ Prosereflect::Mark::Underline.new,
406
407
  ]
407
- text_node = paragraph.add_text('Hello', marks)
408
+ text_node = paragraph.add_text("Hello", marks)
408
409
 
409
410
  expect(text_node.raw_marks.map(&:type)).to eq(%w[bold italic underline])
410
411
  end
411
412
 
412
- it 'does not add empty text' do
413
+ it "does not add empty text" do
413
414
  paragraph = described_class.new
414
- result = paragraph.add_text('')
415
+ result = paragraph.add_text("")
415
416
 
416
417
  expect(paragraph.content).to be_empty
417
418
  expect(result).to be_nil
418
419
  end
419
420
 
420
- it 'does not add nil text' do
421
+ it "does not add nil text" do
421
422
  paragraph = described_class.new
422
423
  result = paragraph.add_text(nil)
423
424
 
@@ -425,19 +426,19 @@ RSpec.describe Prosereflect::Paragraph do
425
426
  expect(result).to be_nil
426
427
  end
427
428
 
428
- it 'preserves existing content when adding text' do
429
+ it "preserves existing content when adding text" do
429
430
  paragraph = described_class.new
430
- paragraph.add_text('First')
431
+ paragraph.add_text("First")
431
432
  paragraph.add_hard_break
432
- paragraph.add_text('Second')
433
+ paragraph.add_text("Second")
433
434
 
434
435
  expect(paragraph.content.size).to eq(3)
435
436
  expect(paragraph.text_content).to eq("First\nSecond")
436
437
  end
437
438
  end
438
439
 
439
- describe '#add_hard_break' do
440
- it 'adds a hard break to the paragraph' do
440
+ describe "#add_hard_break" do
441
+ it "adds a hard break to the paragraph" do
441
442
  paragraph = described_class.new
442
443
  hard_break = paragraph.add_hard_break
443
444
 
@@ -445,7 +446,7 @@ RSpec.describe Prosereflect::Paragraph do
445
446
  expect(hard_break).to be_a(Prosereflect::HardBreak)
446
447
  end
447
448
 
448
- it 'adds a hard break with marks' do
449
+ it "adds a hard break with marks" do
449
450
  paragraph = described_class.new
450
451
  marks = [Prosereflect::Mark::Italic.new]
451
452
  hard_break = paragraph.add_hard_break(marks)
@@ -453,7 +454,7 @@ RSpec.describe Prosereflect::Paragraph do
453
454
  expect(hard_break.raw_marks).to eq(marks)
454
455
  end
455
456
 
456
- it 'adds multiple hard breaks' do
457
+ it "adds multiple hard breaks" do
457
458
  paragraph = described_class.new
458
459
  paragraph.add_hard_break
459
460
  paragraph.add_hard_break
@@ -463,65 +464,65 @@ RSpec.describe Prosereflect::Paragraph do
463
464
  expect(paragraph.content).to all(be_a(Prosereflect::HardBreak))
464
465
  end
465
466
 
466
- it 'preserves existing content when adding hard breaks' do
467
+ it "preserves existing content when adding hard breaks" do
467
468
  paragraph = described_class.new
468
- paragraph.add_text('Text')
469
+ paragraph.add_text("Text")
469
470
  paragraph.add_hard_break
470
- paragraph.add_text('More')
471
+ paragraph.add_text("More")
471
472
 
472
473
  expect(paragraph.content.size).to eq(3)
473
474
  expect(paragraph.text_content).to eq("Text\nMore")
474
475
  end
475
476
  end
476
477
 
477
- describe 'serialization' do
478
- it 'converts to hash representation' do
478
+ describe "serialization" do
479
+ it "converts to hash representation" do
479
480
  paragraph = described_class.new
480
- paragraph.add_text('Hello')
481
+ paragraph.add_text("Hello")
481
482
  paragraph.add_hard_break
482
- paragraph.add_text('World')
483
+ paragraph.add_text("World")
483
484
 
484
485
  hash = paragraph.to_h
485
- expect(hash['type']).to eq('paragraph')
486
- expect(hash['content'].size).to eq(3)
487
- expect(hash['content'][0]['type']).to eq('text')
488
- expect(hash['content'][1]['type']).to eq('hard_break')
489
- expect(hash['content'][2]['type']).to eq('text')
486
+ expect(hash["type"]).to eq("paragraph")
487
+ expect(hash["content"].size).to eq(3)
488
+ expect(hash["content"][0]["type"]).to eq("text")
489
+ expect(hash["content"][1]["type"]).to eq("hard_break")
490
+ expect(hash["content"][2]["type"]).to eq("text")
490
491
  end
491
492
 
492
- it 'converts to hash with marks' do
493
+ it "converts to hash with marks" do
493
494
  paragraph = described_class.new
494
- paragraph.add_text('Bold', [Prosereflect::Mark::Bold.new])
495
- paragraph.add_text(' and ')
496
- paragraph.add_text('italic', [Prosereflect::Mark::Italic.new])
495
+ paragraph.add_text("Bold", [Prosereflect::Mark::Bold.new])
496
+ paragraph.add_text(" and ")
497
+ paragraph.add_text("italic", [Prosereflect::Mark::Italic.new])
497
498
 
498
499
  hash = paragraph.to_h
499
- expect(hash['content'][0]['marks'][0]['type']).to eq('bold')
500
- expect(hash['content'][2]['marks'][0]['type']).to eq('italic')
500
+ expect(hash["content"][0]["marks"][0]["type"]).to eq("bold")
501
+ expect(hash["content"][2]["marks"][0]["type"]).to eq("italic")
501
502
  end
502
503
 
503
- it 'converts to hash with attributes' do
504
- paragraph = described_class.new(attrs: { 'align' => 'center' })
505
- paragraph.add_text('Centered text')
504
+ it "converts to hash with attributes" do
505
+ paragraph = described_class.new(attrs: { "align" => "center" })
506
+ paragraph.add_text("Centered text")
506
507
 
507
508
  hash = paragraph.to_h
508
- expect(hash['attrs']).to eq({ 'align' => 'center' })
509
+ expect(hash["attrs"]).to eq({ "align" => "center" })
509
510
  end
510
511
 
511
- it 'converts complex content to hash' do
512
+ it "converts complex content to hash" do
512
513
  paragraph = described_class.new
513
- paragraph.add_text('Mixed', [
514
+ paragraph.add_text("Mixed", [
514
515
  Prosereflect::Mark::Bold.new,
515
- Prosereflect::Mark::Italic.new
516
+ Prosereflect::Mark::Italic.new,
516
517
  ])
517
518
  paragraph.add_hard_break([Prosereflect::Mark::Strike.new])
518
- paragraph.add_text('Styles')
519
+ paragraph.add_text("Styles")
519
520
 
520
521
  hash = paragraph.to_h
521
- expect(hash['content'].size).to eq(3)
522
- expect(hash['content'][0]['marks'].size).to eq(2)
523
- expect(hash['content'][1]['marks'].size).to eq(1)
524
- expect(hash['content'][2]['marks']).to be_nil
522
+ expect(hash["content"].size).to eq(3)
523
+ expect(hash["content"][0]["marks"].size).to eq(2)
524
+ expect(hash["content"][1]["marks"].size).to eq(1)
525
+ expect(hash["content"][2]["marks"]).to be_nil
525
526
  end
526
527
  end
527
528
  end