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
@@ -0,0 +1,273 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Prosereflect::Fragment do
6
+ describe "creation" do
7
+ it "creates empty fragment" do
8
+ frag = described_class.new
9
+ expect(frag.empty?).to be true
10
+ end
11
+
12
+ it "creates fragment with content" do
13
+ node = Prosereflect::Paragraph.new
14
+ frag = described_class.new([node])
15
+ expect(frag.empty?).to be false
16
+ expect(frag.length).to eq(1)
17
+ end
18
+
19
+ it "creates fragment from single node" do
20
+ node = Prosereflect::Text.new(text: "hello")
21
+ frag = described_class.new(node)
22
+ expect(frag.length).to eq(1)
23
+ end
24
+ end
25
+
26
+ describe ".empty" do
27
+ it "returns a shared empty fragment" do
28
+ frag1 = described_class.empty
29
+ frag2 = described_class.empty
30
+ expect(frag1).to equal(frag2)
31
+ expect(frag1.empty?).to be true
32
+ end
33
+ end
34
+
35
+ describe ".from" do
36
+ it "returns same fragment when given a fragment" do
37
+ frag = described_class.new
38
+ expect(described_class.from(frag)).to equal(frag)
39
+ end
40
+
41
+ it "wraps array in fragment" do
42
+ node = Prosereflect::Text.new(text: "x")
43
+ frag = described_class.from([node])
44
+ expect(frag).to be_a(described_class)
45
+ expect(frag.length).to eq(1)
46
+ end
47
+
48
+ it "wraps single node in fragment" do
49
+ node = Prosereflect::Text.new(text: "x")
50
+ frag = described_class.from(node)
51
+ expect(frag.length).to eq(1)
52
+ end
53
+ end
54
+
55
+ describe "size" do
56
+ it "returns 0 for empty fragment" do
57
+ expect(described_class.new.size).to eq(0)
58
+ end
59
+
60
+ it "returns total node sizes" do
61
+ text = Prosereflect::Text.new(text: "hello")
62
+ frag = described_class.new([text])
63
+ # text "hello" has node_size = 5 + 1 = 6
64
+ expect(frag.size).to eq(6)
65
+ end
66
+
67
+ it "sums multiple nodes" do
68
+ t1 = Prosereflect::Text.new(text: "ab")
69
+ t2 = Prosereflect::Text.new(text: "cd")
70
+ frag = described_class.new([t1, t2])
71
+ # 3 + 3 = 6
72
+ expect(frag.size).to eq(6)
73
+ end
74
+ end
75
+
76
+ describe "append" do
77
+ it "appends another fragment" do
78
+ t1 = Prosereflect::Text.new(text: "a")
79
+ t2 = Prosereflect::Text.new(text: "b")
80
+ frag1 = described_class.new([t1])
81
+ frag2 = described_class.new([t2])
82
+ result = frag1.append(frag2)
83
+ expect(result.length).to eq(2)
84
+ end
85
+
86
+ it "appends a single node" do
87
+ t1 = Prosereflect::Text.new(text: "a")
88
+ t2 = Prosereflect::Text.new(text: "b")
89
+ frag = described_class.new([t1])
90
+ result = frag.append(t2)
91
+ expect(result.length).to eq(2)
92
+ end
93
+
94
+ it "does not mutate original" do
95
+ t1 = Prosereflect::Text.new(text: "a")
96
+ frag = described_class.new([t1])
97
+ frag.append(described_class.new)
98
+ expect(frag.length).to eq(1)
99
+ end
100
+ end
101
+
102
+ describe "cut" do
103
+ it "returns empty fragment for empty cut" do
104
+ frag = described_class.new
105
+ result = frag.cut(0, 0)
106
+ expect(result).to be_a(described_class)
107
+ expect(result.empty?).to be true
108
+ end
109
+
110
+ it "returns empty when from >= to" do
111
+ text = Prosereflect::Text.new(text: "hello")
112
+ frag = described_class.new([text])
113
+ result = frag.cut(3, 3)
114
+ expect(result.empty?).to be true
115
+ end
116
+ end
117
+
118
+ describe "replace_child" do
119
+ it "replaces child at index" do
120
+ t1 = Prosereflect::Text.new(text: "a")
121
+ t2 = Prosereflect::Text.new(text: "b")
122
+ replacement = Prosereflect::Text.new(text: "c")
123
+ frag = described_class.new([t1, t2])
124
+ result = frag.replace_child(0, replacement)
125
+ expect(result[0]).to eq(replacement)
126
+ expect(result[1]).to eq(t2)
127
+ end
128
+
129
+ it "does not mutate original" do
130
+ t1 = Prosereflect::Text.new(text: "a")
131
+ replacement = Prosereflect::Text.new(text: "c")
132
+ frag = described_class.new([t1])
133
+ frag.replace_child(0, replacement)
134
+ expect(frag[0]).to eq(t1)
135
+ end
136
+ end
137
+
138
+ describe "index access" do
139
+ it "returns nil for out of bounds" do
140
+ frag = described_class.new
141
+ expect(frag[0]).to be_nil
142
+ end
143
+
144
+ it "returns node at index" do
145
+ node = Prosereflect::Text.new(text: "x")
146
+ frag = described_class.new([node])
147
+ expect(frag[0]).to eq(node)
148
+ end
149
+ end
150
+
151
+ describe "iteration" do
152
+ it "iterates over content" do
153
+ node = Prosereflect::Paragraph.new
154
+ frag = described_class.new([node])
155
+ count = 0
156
+ frag.each { count += 1 }
157
+ expect(count).to eq(1)
158
+ end
159
+
160
+ it "returns count" do
161
+ frag = described_class.new([Prosereflect::Text.new(text: "a"),
162
+ Prosereflect::Text.new(text: "b")])
163
+ expect(frag.count).to eq(2)
164
+ expect(frag.length).to eq(2)
165
+ end
166
+ end
167
+
168
+ describe "equality" do
169
+ it "compares equal fragments" do
170
+ frag1 = described_class.new
171
+ frag2 = described_class.new
172
+ expect(frag1).to eq(frag2)
173
+ end
174
+
175
+ it "compares unequal fragments" do
176
+ t1 = Prosereflect::Text.new(text: "a")
177
+ t2 = Prosereflect::Text.new(text: "b")
178
+ frag1 = described_class.new([t1])
179
+ frag2 = described_class.new([t2])
180
+ expect(frag1).not_to eq(frag2)
181
+ end
182
+ end
183
+
184
+ describe "text_between" do
185
+ it "extracts text from nodes in range" do
186
+ text = Prosereflect::Text.new(text: "hello")
187
+ frag = described_class.new([text])
188
+ expect(frag.text_between(0, 5)).to eq("hello")
189
+ end
190
+
191
+ it "joins multiple nodes" do
192
+ t1 = Prosereflect::Text.new(text: "hello")
193
+ t2 = Prosereflect::Text.new(text: "world")
194
+ frag = described_class.new([t1, t2])
195
+ expect(frag.text_between(0, 10)).to eq("helloworld")
196
+ end
197
+
198
+ it "joins with separator" do
199
+ t1 = Prosereflect::Text.new(text: "hello")
200
+ t2 = Prosereflect::Text.new(text: "world")
201
+ frag = described_class.new([t1, t2])
202
+ expect(frag.text_between(0, 10, " ")).to eq("hello world")
203
+ end
204
+ end
205
+
206
+ describe "find_diff_start" do
207
+ it "returns nil for identical fragments" do
208
+ t1 = Prosereflect::Text.new(text: "hello")
209
+ t2 = Prosereflect::Text.new(text: "hello")
210
+ frag1 = described_class.new([t1])
211
+ frag2 = described_class.new([t2])
212
+ expect(frag1.find_diff_start(frag2)).to be_nil
213
+ end
214
+
215
+ it "returns 0 for completely different first nodes" do
216
+ t1 = Prosereflect::Text.new(text: "a")
217
+ t2 = Prosereflect::Text.new(text: "b")
218
+ frag1 = described_class.new([t1])
219
+ frag2 = described_class.new([t2])
220
+ expect(frag1.find_diff_start(frag2)).to eq(0)
221
+ end
222
+
223
+ it "returns position where fragments differ" do
224
+ t1 = Prosereflect::Text.new(text: "hello")
225
+ t2 = Prosereflect::Text.new(text: "hello")
226
+ t3 = Prosereflect::Text.new(text: "world")
227
+ frag1 = described_class.new([t1])
228
+ frag2 = described_class.new([t2, t3])
229
+ # Same first node, but different lengths
230
+ expect(frag1.find_diff_start(frag2)).to eq(6) # node_size of "hello"
231
+ end
232
+ end
233
+
234
+ describe "find_diff_end" do
235
+ it "returns nil for identical fragments" do
236
+ t1 = Prosereflect::Text.new(text: "hello")
237
+ t2 = Prosereflect::Text.new(text: "hello")
238
+ frag1 = described_class.new([t1])
239
+ frag2 = described_class.new([t2])
240
+ expect(frag1.find_diff_end(frag2)).to be_nil
241
+ end
242
+
243
+ it "returns position where trailing content differs" do
244
+ t1 = Prosereflect::Text.new(text: "ab")
245
+ t2 = Prosereflect::Text.new(text: "cd")
246
+ t3 = Prosereflect::Text.new(text: "ef")
247
+ frag1 = described_class.new([t1, t2])
248
+ frag2 = described_class.new([t3, t2])
249
+ # The last nodes ("cd") are the same, first nodes differ
250
+ # find_diff_end walks backward from the end and returns where they differ
251
+ result = frag1.find_diff_end(frag2)
252
+ expect(result).not_to be_nil
253
+ end
254
+ end
255
+
256
+ describe "to_a" do
257
+ it "returns a copy of the content array" do
258
+ node = Prosereflect::Text.new(text: "x")
259
+ frag = described_class.new([node])
260
+ arr = frag.to_a
261
+ expect(arr).to eq([node])
262
+ expect(arr).not_to equal(frag.content)
263
+ end
264
+ end
265
+
266
+ describe "to_s / inspect" do
267
+ it "returns string representation" do
268
+ frag = described_class.new([Prosereflect::Text.new(text: "x")])
269
+ expect(frag.to_s).to include("Fragment")
270
+ expect(frag.inspect).to eq(frag.to_s)
271
+ end
272
+ end
273
+ end
@@ -1,247 +1,247 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  RSpec.describe Prosereflect::HardBreak do
6
- describe 'initialization' do
7
- it 'initializes as a hard_break node' do
8
- break_node = described_class.new({ 'type' => 'hard_break' })
9
- expect(break_node.type).to eq('hard_break')
6
+ describe "initialization" do
7
+ it "initializes as a hard_break node" do
8
+ break_node = described_class.new({ "type" => "hard_break" })
9
+ expect(break_node.type).to eq("hard_break")
10
10
  end
11
11
  end
12
12
 
13
- describe '.create' do
14
- it 'creates a simple hard break' do
13
+ describe ".create" do
14
+ it "creates a simple hard break" do
15
15
  break_node = described_class.create
16
16
 
17
17
  expected = {
18
- 'type' => 'hard_break'
18
+ "type" => "hard_break",
19
19
  }
20
20
 
21
21
  expect(break_node.to_h).to eq(expected)
22
22
  end
23
23
 
24
- it 'creates a hard break with a single mark' do
24
+ it "creates a hard break with a single mark" do
25
25
  break_node = described_class.create
26
- break_node.marks = [{ 'type' => 'bold' }]
26
+ break_node.marks = [{ "type" => "bold" }]
27
27
 
28
28
  expected = {
29
- 'type' => 'hard_break',
30
- 'marks' => [{
31
- 'type' => 'bold'
32
- }]
29
+ "type" => "hard_break",
30
+ "marks" => [{
31
+ "type" => "bold",
32
+ }],
33
33
  }
34
34
 
35
35
  expect(break_node.to_h).to eq(expected)
36
36
  end
37
37
 
38
- it 'creates a hard break with multiple marks' do
38
+ it "creates a hard break with multiple marks" do
39
39
  break_node = described_class.create
40
40
  break_node.marks = [
41
- { 'type' => 'bold' },
42
- { 'type' => 'italic' },
43
- { 'type' => 'strike' }
41
+ { "type" => "bold" },
42
+ { "type" => "italic" },
43
+ { "type" => "strike" },
44
44
  ]
45
45
 
46
46
  expected = {
47
- 'type' => 'hard_break',
48
- 'marks' => [{
49
- 'type' => 'bold'
47
+ "type" => "hard_break",
48
+ "marks" => [{
49
+ "type" => "bold",
50
50
  }, {
51
- 'type' => 'italic'
51
+ "type" => "italic",
52
52
  }, {
53
- 'type' => 'strike'
54
- }]
53
+ "type" => "strike",
54
+ }],
55
55
  }
56
56
 
57
57
  expect(break_node.to_h).to eq(expected)
58
58
  end
59
59
  end
60
60
 
61
- describe 'in document context' do
62
- it 'works in a paragraph with mixed content' do
61
+ describe "in document context" do
62
+ it "works in a paragraph with mixed content" do
63
63
  paragraph = Prosereflect::Paragraph.new
64
- paragraph.add_text('First line')
64
+ paragraph.add_text("First line")
65
65
  paragraph.add_hard_break
66
- paragraph.add_text('Second line with ')
67
- paragraph.add_text('bold', [Prosereflect::Mark::Bold.new])
66
+ paragraph.add_text("Second line with ")
67
+ paragraph.add_text("bold", [Prosereflect::Mark::Bold.new])
68
68
  paragraph.add_hard_break
69
- paragraph.add_text('Third line')
69
+ paragraph.add_text("Third line")
70
70
 
71
71
  expected = {
72
- 'type' => 'paragraph',
73
- 'content' => [{
74
- 'type' => 'text',
75
- 'text' => 'First line'
72
+ "type" => "paragraph",
73
+ "content" => [{
74
+ "type" => "text",
75
+ "text" => "First line",
76
76
  }, {
77
- 'type' => 'hard_break'
77
+ "type" => "hard_break",
78
78
  }, {
79
- 'type' => 'text',
80
- 'text' => 'Second line with '
79
+ "type" => "text",
80
+ "text" => "Second line with ",
81
81
  }, {
82
- 'type' => 'text',
83
- 'text' => 'bold',
84
- 'marks' => [{
85
- 'type' => 'bold'
86
- }]
82
+ "type" => "text",
83
+ "text" => "bold",
84
+ "marks" => [{
85
+ "type" => "bold",
86
+ }],
87
87
  }, {
88
- 'type' => 'hard_break'
88
+ "type" => "hard_break",
89
89
  }, {
90
- 'type' => 'text',
91
- 'text' => 'Third line'
92
- }]
90
+ "type" => "text",
91
+ "text" => "Third line",
92
+ }],
93
93
  }
94
94
 
95
95
  expect(paragraph.to_h).to eq(expected)
96
96
  end
97
97
 
98
- it 'works in a list item with multiple breaks' do
98
+ it "works in a list item with multiple breaks" do
99
99
  list = Prosereflect::BulletList.new
100
- item = list.add_item('First part')
101
- item.add_hard_break([{ 'type' => 'strike' }])
102
- item.add_text('Second part')
100
+ item = list.add_item("First part")
101
+ item.add_hard_break([{ "type" => "strike" }])
102
+ item.add_text("Second part")
103
103
  item.add_hard_break
104
- item.add_text('Third part')
104
+ item.add_text("Third part")
105
105
 
106
106
  expected = {
107
- 'type' => 'bullet_list',
108
- 'attrs' => {
109
- 'bullet_style' => nil
107
+ "type" => "bullet_list",
108
+ "attrs" => {
109
+ "bullet_style" => nil,
110
110
  },
111
- 'content' => [{
112
- 'type' => 'list_item',
113
- 'content' => [{
114
- 'type' => 'paragraph',
115
- 'content' => [{
116
- 'type' => 'text',
117
- 'text' => 'First part'
111
+ "content" => [{
112
+ "type" => "list_item",
113
+ "content" => [{
114
+ "type" => "paragraph",
115
+ "content" => [{
116
+ "type" => "text",
117
+ "text" => "First part",
118
118
  }, {
119
- 'type' => 'hard_break',
120
- 'marks' => [{
121
- 'type' => 'strike'
122
- }]
119
+ "type" => "hard_break",
120
+ "marks" => [{
121
+ "type" => "strike",
122
+ }],
123
123
  }, {
124
- 'type' => 'text',
125
- 'text' => 'Second part'
124
+ "type" => "text",
125
+ "text" => "Second part",
126
126
  }, {
127
- 'type' => 'hard_break'
127
+ "type" => "hard_break",
128
128
  }, {
129
- 'type' => 'text',
130
- 'text' => 'Third part'
131
- }]
132
- }]
133
- }]
129
+ "type" => "text",
130
+ "text" => "Third part",
131
+ }],
132
+ }],
133
+ }],
134
134
  }
135
135
 
136
136
  expect(list.to_h).to eq(expected)
137
137
  end
138
138
 
139
- it 'works in a table cell with multiple paragraphs' do
139
+ it "works in a table cell with multiple paragraphs" do
140
140
  cell = Prosereflect::TableCell.new
141
- para1 = cell.add_paragraph('First paragraph')
141
+ para1 = cell.add_paragraph("First paragraph")
142
142
  para1.add_hard_break
143
- para1.add_text('continues here')
143
+ para1.add_text("continues here")
144
144
 
145
- para2 = cell.add_paragraph('Second paragraph')
145
+ para2 = cell.add_paragraph("Second paragraph")
146
146
  para2.add_hard_break([Prosereflect::Mark::Italic.new])
147
- para2.add_text('with style')
147
+ para2.add_text("with style")
148
148
 
149
149
  expected = {
150
- 'type' => 'table_cell',
151
- 'content' => [{
152
- 'type' => 'paragraph',
153
- 'content' => [{
154
- 'type' => 'text',
155
- 'text' => 'First paragraph'
150
+ "type" => "table_cell",
151
+ "content" => [{
152
+ "type" => "paragraph",
153
+ "content" => [{
154
+ "type" => "text",
155
+ "text" => "First paragraph",
156
156
  }, {
157
- 'type' => 'hard_break'
157
+ "type" => "hard_break",
158
158
  }, {
159
- 'type' => 'text',
160
- 'text' => 'continues here'
161
- }]
159
+ "type" => "text",
160
+ "text" => "continues here",
161
+ }],
162
162
  }, {
163
- 'type' => 'paragraph',
164
- 'content' => [{
165
- 'type' => 'text',
166
- 'text' => 'Second paragraph'
163
+ "type" => "paragraph",
164
+ "content" => [{
165
+ "type" => "text",
166
+ "text" => "Second paragraph",
167
167
  }, {
168
- 'type' => 'hard_break',
169
- 'marks' => [{
170
- 'type' => 'italic'
171
- }]
168
+ "type" => "hard_break",
169
+ "marks" => [{
170
+ "type" => "italic",
171
+ }],
172
172
  }, {
173
- 'type' => 'text',
174
- 'text' => 'with style'
175
- }]
176
- }]
173
+ "type" => "text",
174
+ "text" => "with style",
175
+ }],
176
+ }],
177
177
  }
178
178
 
179
179
  expect(cell.to_h).to eq(expected)
180
180
  end
181
181
  end
182
182
 
183
- describe '#text_content' do
184
- it 'returns a newline character' do
183
+ describe "#text_content" do
184
+ it "returns a newline character" do
185
185
  break_node = described_class.new
186
186
  expect(break_node.text_content).to eq("\n")
187
187
  end
188
188
 
189
- it 'returns a newline character regardless of marks' do
189
+ it "returns a newline character regardless of marks" do
190
190
  break_node = described_class.create(marks: [
191
191
  Prosereflect::Mark::Bold.new,
192
- Prosereflect::Mark::Italic.new
192
+ Prosereflect::Mark::Italic.new,
193
193
  ])
194
194
  expect(break_node.text_content).to eq("\n")
195
195
  end
196
196
 
197
- it 'properly formats text in a paragraph with multiple breaks' do
197
+ it "properly formats text in a paragraph with multiple breaks" do
198
198
  paragraph = Prosereflect::Paragraph.new
199
- paragraph.add_text('Line 1')
199
+ paragraph.add_text("Line 1")
200
200
  paragraph.add_hard_break
201
- paragraph.add_text('Line 2')
201
+ paragraph.add_text("Line 2")
202
202
  paragraph.add_hard_break
203
- paragraph.add_text('Line 3')
203
+ paragraph.add_text("Line 3")
204
204
 
205
205
  expect(paragraph.text_content).to eq("Line 1\nLine 2\nLine 3")
206
206
  end
207
207
  end
208
208
 
209
- describe 'serialization' do
210
- it 'preserves mark order in serialization' do
209
+ describe "serialization" do
210
+ it "preserves mark order in serialization" do
211
211
  break_node = described_class.create
212
212
  break_node.marks = [
213
- { 'type' => 'bold' },
214
- { 'type' => 'italic' },
215
- { 'type' => 'strike' },
216
- { 'type' => 'underline' }
213
+ { "type" => "bold" },
214
+ { "type" => "italic" },
215
+ { "type" => "strike" },
216
+ { "type" => "underline" },
217
217
  ]
218
218
 
219
219
  expected = {
220
- 'type' => 'hard_break',
221
- 'marks' => [{
222
- 'type' => 'bold'
220
+ "type" => "hard_break",
221
+ "marks" => [{
222
+ "type" => "bold",
223
223
  }, {
224
- 'type' => 'italic'
224
+ "type" => "italic",
225
225
  }, {
226
- 'type' => 'strike'
226
+ "type" => "strike",
227
227
  }, {
228
- 'type' => 'underline'
229
- }]
228
+ "type" => "underline",
229
+ }],
230
230
  }
231
231
 
232
232
  expect(break_node.to_h).to eq(expected)
233
233
  end
234
234
 
235
- it 'handles empty marks array' do
235
+ it "handles empty marks array" do
236
236
  break_node = described_class.create
237
237
  break_node.marks = []
238
- expect(break_node.to_h).to eq({ 'type' => 'hard_break' })
238
+ expect(break_node.to_h).to eq({ "type" => "hard_break" })
239
239
  end
240
240
 
241
- it 'excludes marks when nil' do
241
+ it "excludes marks when nil" do
242
242
  break_node = described_class.create
243
243
  break_node.marks = nil
244
- expect(break_node.to_h).to eq({ 'type' => 'hard_break' })
244
+ expect(break_node.to_h).to eq({ "type" => "hard_break" })
245
245
  end
246
246
  end
247
247
  end