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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +97 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +19 -1
- data/.rubocop_todo.yml +119 -183
- data/CLAUDE.md +78 -0
- data/Gemfile +8 -4
- data/README.adoc +2 -0
- data/Rakefile +3 -3
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +45 -0
- data/docs/_advanced/index.adoc +15 -0
- data/docs/_advanced/schema.adoc +112 -0
- data/docs/_advanced/step-map.adoc +66 -0
- data/docs/_advanced/steps.adoc +88 -0
- data/docs/_advanced/test-builder.adoc +61 -0
- data/docs/_advanced/transform.adoc +92 -0
- data/docs/_config.yml +174 -0
- data/docs/_features/html-input.adoc +69 -0
- data/docs/_features/html-output.adoc +45 -0
- data/docs/_features/index.adoc +15 -0
- data/docs/_features/marks.adoc +86 -0
- data/docs/_features/node-types.adoc +124 -0
- data/docs/_features/user-mentions.adoc +47 -0
- data/docs/_guides/custom-nodes.adoc +107 -0
- data/docs/_guides/index.adoc +13 -0
- data/docs/_guides/round-trip-html.adoc +91 -0
- data/docs/_guides/serialization.adoc +109 -0
- data/docs/_pages/index.adoc +67 -0
- data/docs/_reference/document-api.adoc +49 -0
- data/docs/_reference/index.adoc +14 -0
- data/docs/_reference/node-api.adoc +79 -0
- data/docs/_reference/schema-api.adoc +95 -0
- data/docs/_reference/transform-api.adoc +77 -0
- data/docs/_understanding/document-model.adoc +65 -0
- data/docs/_understanding/fragment.adoc +52 -0
- data/docs/_understanding/index.adoc +14 -0
- data/docs/_understanding/resolved-position.adoc +53 -0
- data/docs/_understanding/slice.adoc +54 -0
- data/docs/lychee.toml +63 -0
- data/lib/prosereflect/attribute/base.rb +4 -6
- data/lib/prosereflect/attribute/bold.rb +2 -4
- data/lib/prosereflect/attribute/href.rb +1 -3
- data/lib/prosereflect/attribute/id.rb +7 -7
- data/lib/prosereflect/attribute.rb +4 -7
- data/lib/prosereflect/blockquote.rb +19 -11
- data/lib/prosereflect/bullet_list.rb +36 -29
- data/lib/prosereflect/code_block.rb +23 -27
- data/lib/prosereflect/code_block_wrapper.rb +12 -13
- data/lib/prosereflect/document.rb +14 -22
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/hard_break.rb +6 -6
- data/lib/prosereflect/heading.rb +14 -15
- data/lib/prosereflect/horizontal_rule.rb +23 -14
- data/lib/prosereflect/image.rb +32 -23
- data/lib/prosereflect/input/html.rb +179 -104
- data/lib/prosereflect/input.rb +7 -0
- data/lib/prosereflect/list_item.rb +11 -12
- data/lib/prosereflect/mark/base.rb +9 -11
- data/lib/prosereflect/mark/bold.rb +1 -3
- data/lib/prosereflect/mark/code.rb +1 -3
- data/lib/prosereflect/mark/italic.rb +1 -3
- data/lib/prosereflect/mark/link.rb +1 -3
- data/lib/prosereflect/mark/strike.rb +1 -3
- data/lib/prosereflect/mark/subscript.rb +1 -3
- data/lib/prosereflect/mark/superscript.rb +1 -3
- data/lib/prosereflect/mark/underline.rb +1 -3
- data/lib/prosereflect/mark.rb +9 -5
- data/lib/prosereflect/node.rb +171 -33
- data/lib/prosereflect/ordered_list.rb +17 -14
- data/lib/prosereflect/output/html.rb +279 -50
- data/lib/prosereflect/output.rb +7 -0
- data/lib/prosereflect/paragraph.rb +11 -13
- data/lib/prosereflect/parser.rb +56 -66
- data/lib/prosereflect/resolved_pos.rb +256 -0
- data/lib/prosereflect/schema/attribute.rb +57 -0
- data/lib/prosereflect/schema/content_match.rb +656 -0
- data/lib/prosereflect/schema/fragment.rb +166 -0
- data/lib/prosereflect/schema/mark.rb +121 -0
- data/lib/prosereflect/schema/mark_type.rb +130 -0
- data/lib/prosereflect/schema/node.rb +236 -0
- data/lib/prosereflect/schema/node_type.rb +274 -0
- data/lib/prosereflect/schema/schema_main.rb +190 -0
- data/lib/prosereflect/schema/spec.rb +92 -0
- data/lib/prosereflect/schema.rb +39 -0
- data/lib/prosereflect/table.rb +12 -13
- data/lib/prosereflect/table_cell.rb +13 -13
- data/lib/prosereflect/table_header.rb +17 -17
- data/lib/prosereflect/table_row.rb +12 -12
- data/lib/prosereflect/text.rb +35 -11
- data/lib/prosereflect/transform/attr_step.rb +157 -0
- data/lib/prosereflect/transform/insert_step.rb +115 -0
- data/lib/prosereflect/transform/mapping.rb +82 -0
- data/lib/prosereflect/transform/mark_step.rb +269 -0
- data/lib/prosereflect/transform/replace_around_step.rb +181 -0
- data/lib/prosereflect/transform/replace_step.rb +157 -0
- data/lib/prosereflect/transform/slice.rb +91 -0
- data/lib/prosereflect/transform/step.rb +89 -0
- data/lib/prosereflect/transform/step_map.rb +126 -0
- data/lib/prosereflect/transform/structure.rb +120 -0
- data/lib/prosereflect/transform/transform.rb +341 -0
- data/lib/prosereflect/transform.rb +26 -0
- data/lib/prosereflect/user.rb +15 -15
- data/lib/prosereflect/version.rb +1 -1
- data/lib/prosereflect.rb +30 -17
- data/prosereflect.gemspec +17 -16
- data/spec/fixtures/documents/formatted_text.yaml +14 -0
- data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
- data/spec/fixtures/documents/lists_doc.yaml +32 -0
- data/spec/fixtures/documents/mixed_content.yaml +40 -0
- data/spec/fixtures/documents/nested_doc.yaml +20 -0
- data/spec/fixtures/documents/simple_doc.yaml +6 -0
- data/spec/fixtures/documents/table_doc.yaml +32 -0
- data/spec/fixtures/documents/transform_test.yaml +14 -0
- data/spec/fixtures/schema/custom_schema.rb +37 -0
- data/spec/fixtures/schema/test_schema.rb +46 -0
- data/spec/fixtures/test_builder/helpers.rb +212 -0
- data/spec/prosereflect/document_spec.rb +332 -330
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/hard_break_spec.rb +125 -125
- data/spec/prosereflect/input/html_spec.rb +718 -522
- data/spec/prosereflect/node_spec.rb +311 -182
- data/spec/prosereflect/output/html_spec.rb +105 -105
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/paragraph_spec.rb +275 -274
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- data/spec/prosereflect/parser_spec.rb +185 -180
- data/spec/prosereflect/resolved_pos_spec.rb +74 -0
- data/spec/prosereflect/schema/conftest.rb +68 -0
- data/spec/prosereflect/schema/content_match_spec.rb +237 -0
- data/spec/prosereflect/schema/mark_spec.rb +274 -0
- data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
- data/spec/prosereflect/schema/node_type_spec.rb +142 -0
- data/spec/prosereflect/schema/schema_spec.rb +194 -0
- data/spec/prosereflect/table_cell_spec.rb +183 -183
- data/spec/prosereflect/table_row_spec.rb +149 -149
- data/spec/prosereflect/table_spec.rb +320 -318
- data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
- data/spec/prosereflect/text_spec.rb +133 -132
- data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
- data/spec/prosereflect/transform/mapping_spec.rb +226 -0
- data/spec/prosereflect/transform/replace_spec.rb +832 -0
- data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
- data/spec/prosereflect/transform/slice_spec.rb +48 -0
- data/spec/prosereflect/transform/step_map_spec.rb +70 -0
- data/spec/prosereflect/transform/step_spec.rb +211 -0
- data/spec/prosereflect/transform/structure_spec.rb +98 -0
- data/spec/prosereflect/transform/transform_spec.rb +238 -0
- data/spec/prosereflect/user_spec.rb +31 -28
- data/spec/prosereflect_spec.rb +28 -26
- data/spec/spec_helper.rb +7 -6
- data/spec/support/matchers.rb +6 -6
- data/spec/support/shared_examples.rb +49 -49
- metadata +96 -5
- 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
|
|
3
|
+
require "spec_helper"
|
|
4
4
|
|
|
5
5
|
RSpec.describe Prosereflect::HardBreak do
|
|
6
|
-
describe
|
|
7
|
-
it
|
|
8
|
-
break_node = described_class.new({
|
|
9
|
-
expect(break_node.type).to eq(
|
|
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
|
|
14
|
-
it
|
|
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
|
-
|
|
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
|
|
24
|
+
it "creates a hard break with a single mark" do
|
|
25
25
|
break_node = described_class.create
|
|
26
|
-
break_node.marks = [{
|
|
26
|
+
break_node.marks = [{ "type" => "bold" }]
|
|
27
27
|
|
|
28
28
|
expected = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
38
|
+
it "creates a hard break with multiple marks" do
|
|
39
39
|
break_node = described_class.create
|
|
40
40
|
break_node.marks = [
|
|
41
|
-
{
|
|
42
|
-
{
|
|
43
|
-
{
|
|
41
|
+
{ "type" => "bold" },
|
|
42
|
+
{ "type" => "italic" },
|
|
43
|
+
{ "type" => "strike" },
|
|
44
44
|
]
|
|
45
45
|
|
|
46
46
|
expected = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
"type" => "hard_break",
|
|
48
|
+
"marks" => [{
|
|
49
|
+
"type" => "bold",
|
|
50
50
|
}, {
|
|
51
|
-
|
|
51
|
+
"type" => "italic",
|
|
52
52
|
}, {
|
|
53
|
-
|
|
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
|
|
62
|
-
it
|
|
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(
|
|
64
|
+
paragraph.add_text("First line")
|
|
65
65
|
paragraph.add_hard_break
|
|
66
|
-
paragraph.add_text(
|
|
67
|
-
paragraph.add_text(
|
|
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(
|
|
69
|
+
paragraph.add_text("Third line")
|
|
70
70
|
|
|
71
71
|
expected = {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
"type" => "paragraph",
|
|
73
|
+
"content" => [{
|
|
74
|
+
"type" => "text",
|
|
75
|
+
"text" => "First line",
|
|
76
76
|
}, {
|
|
77
|
-
|
|
77
|
+
"type" => "hard_break",
|
|
78
78
|
}, {
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
"type" => "text",
|
|
80
|
+
"text" => "Second line with ",
|
|
81
81
|
}, {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}]
|
|
82
|
+
"type" => "text",
|
|
83
|
+
"text" => "bold",
|
|
84
|
+
"marks" => [{
|
|
85
|
+
"type" => "bold",
|
|
86
|
+
}],
|
|
87
87
|
}, {
|
|
88
|
-
|
|
88
|
+
"type" => "hard_break",
|
|
89
89
|
}, {
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
98
|
+
it "works in a list item with multiple breaks" do
|
|
99
99
|
list = Prosereflect::BulletList.new
|
|
100
|
-
item = list.add_item(
|
|
101
|
-
item.add_hard_break([{
|
|
102
|
-
item.add_text(
|
|
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(
|
|
104
|
+
item.add_text("Third part")
|
|
105
105
|
|
|
106
106
|
expected = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
"type" => "bullet_list",
|
|
108
|
+
"attrs" => {
|
|
109
|
+
"bullet_style" => nil,
|
|
110
110
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
"content" => [{
|
|
112
|
+
"type" => "list_item",
|
|
113
|
+
"content" => [{
|
|
114
|
+
"type" => "paragraph",
|
|
115
|
+
"content" => [{
|
|
116
|
+
"type" => "text",
|
|
117
|
+
"text" => "First part",
|
|
118
118
|
}, {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}]
|
|
119
|
+
"type" => "hard_break",
|
|
120
|
+
"marks" => [{
|
|
121
|
+
"type" => "strike",
|
|
122
|
+
}],
|
|
123
123
|
}, {
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
"type" => "text",
|
|
125
|
+
"text" => "Second part",
|
|
126
126
|
}, {
|
|
127
|
-
|
|
127
|
+
"type" => "hard_break",
|
|
128
128
|
}, {
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
139
|
+
it "works in a table cell with multiple paragraphs" do
|
|
140
140
|
cell = Prosereflect::TableCell.new
|
|
141
|
-
para1 = cell.add_paragraph(
|
|
141
|
+
para1 = cell.add_paragraph("First paragraph")
|
|
142
142
|
para1.add_hard_break
|
|
143
|
-
para1.add_text(
|
|
143
|
+
para1.add_text("continues here")
|
|
144
144
|
|
|
145
|
-
para2 = cell.add_paragraph(
|
|
145
|
+
para2 = cell.add_paragraph("Second paragraph")
|
|
146
146
|
para2.add_hard_break([Prosereflect::Mark::Italic.new])
|
|
147
|
-
para2.add_text(
|
|
147
|
+
para2.add_text("with style")
|
|
148
148
|
|
|
149
149
|
expected = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
"type" => "table_cell",
|
|
151
|
+
"content" => [{
|
|
152
|
+
"type" => "paragraph",
|
|
153
|
+
"content" => [{
|
|
154
|
+
"type" => "text",
|
|
155
|
+
"text" => "First paragraph",
|
|
156
156
|
}, {
|
|
157
|
-
|
|
157
|
+
"type" => "hard_break",
|
|
158
158
|
}, {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}]
|
|
159
|
+
"type" => "text",
|
|
160
|
+
"text" => "continues here",
|
|
161
|
+
}],
|
|
162
162
|
}, {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
"type" => "paragraph",
|
|
164
|
+
"content" => [{
|
|
165
|
+
"type" => "text",
|
|
166
|
+
"text" => "Second paragraph",
|
|
167
167
|
}, {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}]
|
|
168
|
+
"type" => "hard_break",
|
|
169
|
+
"marks" => [{
|
|
170
|
+
"type" => "italic",
|
|
171
|
+
}],
|
|
172
172
|
}, {
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
184
|
-
it
|
|
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
|
|
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
|
|
197
|
+
it "properly formats text in a paragraph with multiple breaks" do
|
|
198
198
|
paragraph = Prosereflect::Paragraph.new
|
|
199
|
-
paragraph.add_text(
|
|
199
|
+
paragraph.add_text("Line 1")
|
|
200
200
|
paragraph.add_hard_break
|
|
201
|
-
paragraph.add_text(
|
|
201
|
+
paragraph.add_text("Line 2")
|
|
202
202
|
paragraph.add_hard_break
|
|
203
|
-
paragraph.add_text(
|
|
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
|
|
210
|
-
it
|
|
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
|
-
{
|
|
214
|
-
{
|
|
215
|
-
{
|
|
216
|
-
{
|
|
213
|
+
{ "type" => "bold" },
|
|
214
|
+
{ "type" => "italic" },
|
|
215
|
+
{ "type" => "strike" },
|
|
216
|
+
{ "type" => "underline" },
|
|
217
217
|
]
|
|
218
218
|
|
|
219
219
|
expected = {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
"type" => "hard_break",
|
|
221
|
+
"marks" => [{
|
|
222
|
+
"type" => "bold",
|
|
223
223
|
}, {
|
|
224
|
-
|
|
224
|
+
"type" => "italic",
|
|
225
225
|
}, {
|
|
226
|
-
|
|
226
|
+
"type" => "strike",
|
|
227
227
|
}, {
|
|
228
|
-
|
|
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
|
|
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({
|
|
238
|
+
expect(break_node.to_h).to eq({ "type" => "hard_break" })
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
-
it
|
|
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({
|
|
244
|
+
expect(break_node.to_h).to eq({ "type" => "hard_break" })
|
|
245
245
|
end
|
|
246
246
|
end
|
|
247
247
|
end
|