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
|
@@ -1,423 +1,424 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "spec_helper"
|
|
4
4
|
|
|
5
5
|
RSpec.describe Prosereflect::Paragraph do
|
|
6
|
-
describe
|
|
7
|
-
it
|
|
8
|
-
paragraph = described_class.new({
|
|
9
|
-
expect(paragraph.type).to eq(
|
|
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
|
|
13
|
-
text_node = Prosereflect::Text.new(text:
|
|
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(
|
|
18
|
+
expect(paragraph.content.first.text).to eq("Hello")
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
it
|
|
21
|
+
it "initializes with complex content" do
|
|
22
22
|
content = [
|
|
23
|
-
{
|
|
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(
|
|
28
|
+
expect(paragraph.content[0].raw_marks.first.type).to eq("bold")
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
describe
|
|
32
|
-
it
|
|
32
|
+
describe ".create" do
|
|
33
|
+
it "creates a simple paragraph with text" do
|
|
33
34
|
paragraph = described_class.new
|
|
34
|
-
paragraph.add_text(
|
|
35
|
+
paragraph.add_text("This is a test paragraph.")
|
|
35
36
|
|
|
36
37
|
expected = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
48
|
+
it "creates a paragraph with styled text" do
|
|
48
49
|
paragraph = described_class.new
|
|
49
|
-
paragraph.add_text(
|
|
50
|
-
paragraph.add_text(
|
|
51
|
-
paragraph.add_text(
|
|
52
|
-
paragraph.add_text(
|
|
53
|
-
paragraph.add_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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
"type" => "paragraph",
|
|
58
|
+
"content" => [{
|
|
59
|
+
"type" => "text",
|
|
60
|
+
"text" => "This is ",
|
|
60
61
|
}, {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}]
|
|
62
|
+
"type" => "text",
|
|
63
|
+
"text" => "bold",
|
|
64
|
+
"marks" => [{
|
|
65
|
+
"type" => "bold",
|
|
66
|
+
}],
|
|
66
67
|
}, {
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
"type" => "text",
|
|
69
|
+
"text" => " and ",
|
|
69
70
|
}, {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}]
|
|
71
|
+
"type" => "text",
|
|
72
|
+
"text" => "italic",
|
|
73
|
+
"marks" => [{
|
|
74
|
+
"type" => "italic",
|
|
75
|
+
}],
|
|
75
76
|
}, {
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
85
|
+
it "creates a paragraph with multiple text styles" do
|
|
85
86
|
paragraph = described_class.new
|
|
86
|
-
paragraph.add_text(
|
|
87
|
-
paragraph.add_text(
|
|
88
|
-
paragraph.add_text(
|
|
89
|
-
paragraph.add_text(
|
|
90
|
-
paragraph.add_text(
|
|
91
|
-
paragraph.add_text(
|
|
92
|
-
paragraph.add_text(
|
|
93
|
-
paragraph.add_text(
|
|
94
|
-
paragraph.add_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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
"type" => "paragraph",
|
|
99
|
+
"content" => [{
|
|
100
|
+
"type" => "text",
|
|
101
|
+
"text" => "This is ",
|
|
101
102
|
}, {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}]
|
|
103
|
+
"type" => "text",
|
|
104
|
+
"text" => "struck",
|
|
105
|
+
"marks" => [{
|
|
106
|
+
"type" => "strike",
|
|
107
|
+
}],
|
|
107
108
|
}, {
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
"type" => "text",
|
|
110
|
+
"text" => " and ",
|
|
110
111
|
}, {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}]
|
|
112
|
+
"type" => "text",
|
|
113
|
+
"text" => "underlined",
|
|
114
|
+
"marks" => [{
|
|
115
|
+
"type" => "underline",
|
|
116
|
+
}],
|
|
116
117
|
}, {
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
"type" => "text",
|
|
119
|
+
"text" => " and ",
|
|
119
120
|
}, {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}]
|
|
121
|
+
"type" => "text",
|
|
122
|
+
"text" => "sub",
|
|
123
|
+
"marks" => [{
|
|
124
|
+
"type" => "subscript",
|
|
125
|
+
}],
|
|
125
126
|
}, {
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
"type" => "text",
|
|
128
|
+
"text" => " and ",
|
|
128
129
|
}, {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}]
|
|
130
|
+
"type" => "text",
|
|
131
|
+
"text" => "super",
|
|
132
|
+
"marks" => [{
|
|
133
|
+
"type" => "superscript",
|
|
134
|
+
}],
|
|
134
135
|
}, {
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
144
|
+
it "creates a paragraph with mixed text styles" do
|
|
144
145
|
paragraph = described_class.new
|
|
145
|
-
paragraph.add_text(
|
|
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(
|
|
150
|
-
paragraph.add_text(
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
"type" => "paragraph",
|
|
158
|
+
"content" => [{
|
|
159
|
+
"type" => "text",
|
|
160
|
+
"text" => "Bold and italic",
|
|
161
|
+
"marks" => [{
|
|
162
|
+
"type" => "bold",
|
|
162
163
|
}, {
|
|
163
|
-
|
|
164
|
-
}]
|
|
164
|
+
"type" => "italic",
|
|
165
|
+
}],
|
|
165
166
|
}, {
|
|
166
|
-
|
|
167
|
-
|
|
167
|
+
"type" => "text",
|
|
168
|
+
"text" => " and ",
|
|
168
169
|
}, {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
"type" => "text",
|
|
171
|
+
"text" => "underlined and struck",
|
|
172
|
+
"marks" => [{
|
|
173
|
+
"type" => "underline",
|
|
173
174
|
}, {
|
|
174
|
-
|
|
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
|
|
183
|
+
it "creates a paragraph with line breaks" do
|
|
183
184
|
paragraph = described_class.new
|
|
184
|
-
paragraph.add_text(
|
|
185
|
+
paragraph.add_text("First line")
|
|
185
186
|
paragraph.add_hard_break
|
|
186
|
-
paragraph.add_text(
|
|
187
|
+
paragraph.add_text("Second line")
|
|
187
188
|
paragraph.add_hard_break
|
|
188
|
-
paragraph.add_text(
|
|
189
|
+
paragraph.add_text("Third line")
|
|
189
190
|
|
|
190
191
|
expected = {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
"type" => "paragraph",
|
|
193
|
+
"content" => [{
|
|
194
|
+
"type" => "text",
|
|
195
|
+
"text" => "First line",
|
|
195
196
|
}, {
|
|
196
|
-
|
|
197
|
+
"type" => "hard_break",
|
|
197
198
|
}, {
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
"type" => "text",
|
|
200
|
+
"text" => "Second line",
|
|
200
201
|
}, {
|
|
201
|
-
|
|
202
|
+
"type" => "hard_break",
|
|
202
203
|
}, {
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
212
|
-
paragraph = described_class.new(attrs: {
|
|
213
|
-
paragraph.add_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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
"type" => "paragraph",
|
|
218
|
+
"attrs" => {
|
|
219
|
+
"align" => "center",
|
|
219
220
|
},
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
230
|
+
it "creates a paragraph with complex content" do
|
|
230
231
|
paragraph = described_class.new
|
|
231
|
-
paragraph.add_text(
|
|
232
|
-
paragraph.add_text(
|
|
233
|
-
paragraph.add_text(
|
|
234
|
-
paragraph.add_text(
|
|
235
|
-
paragraph.add_text(
|
|
236
|
-
paragraph.add_text(
|
|
237
|
-
paragraph.add_text(
|
|
238
|
-
paragraph.add_text(
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
"type" => "paragraph",
|
|
243
|
+
"content" => [{
|
|
244
|
+
"type" => "text",
|
|
245
|
+
"text" => "A formula: ",
|
|
245
246
|
}, {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}]
|
|
247
|
+
"type" => "text",
|
|
248
|
+
"text" => "H",
|
|
249
|
+
"marks" => [{
|
|
250
|
+
"type" => "bold",
|
|
251
|
+
}],
|
|
251
252
|
}, {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}]
|
|
253
|
+
"type" => "text",
|
|
254
|
+
"text" => "2",
|
|
255
|
+
"marks" => [{
|
|
256
|
+
"type" => "subscript",
|
|
257
|
+
}],
|
|
257
258
|
}, {
|
|
258
|
-
|
|
259
|
-
|
|
259
|
+
"type" => "text",
|
|
260
|
+
"text" => "O + 2H",
|
|
260
261
|
}, {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}]
|
|
262
|
+
"type" => "text",
|
|
263
|
+
"text" => "2",
|
|
264
|
+
"marks" => [{
|
|
265
|
+
"type" => "subscript",
|
|
266
|
+
}],
|
|
266
267
|
}, {
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
"type" => "text",
|
|
269
|
+
"text" => " → 2H",
|
|
269
270
|
}, {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}]
|
|
271
|
+
"type" => "text",
|
|
272
|
+
"text" => "2",
|
|
273
|
+
"marks" => [{
|
|
274
|
+
"type" => "subscript",
|
|
275
|
+
}],
|
|
275
276
|
}, {
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
285
|
+
it "creates a paragraph with mathematical expressions" do
|
|
285
286
|
paragraph = described_class.new
|
|
286
|
-
paragraph.add_text(
|
|
287
|
-
paragraph.add_text(
|
|
288
|
-
paragraph.add_text(
|
|
289
|
-
paragraph.add_text(
|
|
290
|
-
paragraph.add_text(
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
294
|
+
"type" => "paragraph",
|
|
295
|
+
"content" => [{
|
|
296
|
+
"type" => "text",
|
|
297
|
+
"text" => "The quadratic formula: x = -b ± ",
|
|
297
298
|
}, {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}]
|
|
299
|
+
"type" => "text",
|
|
300
|
+
"text" => "√",
|
|
301
|
+
"marks" => [{
|
|
302
|
+
"type" => "bold",
|
|
303
|
+
}],
|
|
303
304
|
}, {
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
"type" => "text",
|
|
306
|
+
"text" => "(b",
|
|
306
307
|
}, {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}]
|
|
308
|
+
"type" => "text",
|
|
309
|
+
"text" => "2",
|
|
310
|
+
"marks" => [{
|
|
311
|
+
"type" => "superscript",
|
|
312
|
+
}],
|
|
312
313
|
}, {
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
323
|
-
it
|
|
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(
|
|
326
|
-
paragraph.add_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
|
|
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
|
|
338
|
+
it "returns text nodes with marks" do
|
|
338
339
|
paragraph = described_class.new
|
|
339
|
-
paragraph.add_text(
|
|
340
|
+
paragraph.add_text("Hello", [Prosereflect::Mark::Bold.new])
|
|
340
341
|
|
|
341
|
-
expect(paragraph.text_nodes[0].raw_marks.first.type).to eq(
|
|
342
|
+
expect(paragraph.text_nodes[0].raw_marks.first.type).to eq("bold")
|
|
342
343
|
end
|
|
343
344
|
|
|
344
|
-
it
|
|
345
|
+
it "ignores non-text nodes" do
|
|
345
346
|
paragraph = described_class.new
|
|
346
|
-
paragraph.add_text(
|
|
347
|
+
paragraph.add_text("Text")
|
|
347
348
|
paragraph.add_hard_break
|
|
348
|
-
paragraph.add_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([
|
|
352
|
+
expect(paragraph.text_nodes.map(&:text)).to eq(["Text", "More text"])
|
|
352
353
|
end
|
|
353
354
|
end
|
|
354
355
|
|
|
355
|
-
describe
|
|
356
|
-
it
|
|
356
|
+
describe "#text_content" do
|
|
357
|
+
it "returns plain text content without marks" do
|
|
357
358
|
paragraph = described_class.new
|
|
358
|
-
paragraph.add_text(
|
|
359
|
-
paragraph.add_text(
|
|
360
|
-
paragraph.add_text(
|
|
361
|
-
paragraph.add_text(
|
|
362
|
-
paragraph.add_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(
|
|
365
|
+
expect(paragraph.text_content).to eq("This is bold and italic text.")
|
|
365
366
|
end
|
|
366
367
|
|
|
367
|
-
it
|
|
368
|
+
it "handles line breaks in text content" do
|
|
368
369
|
paragraph = described_class.new
|
|
369
|
-
paragraph.add_text(
|
|
370
|
+
paragraph.add_text("First line")
|
|
370
371
|
paragraph.add_hard_break
|
|
371
|
-
paragraph.add_text(
|
|
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
|
|
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
|
|
383
|
-
it
|
|
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(
|
|
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(
|
|
390
|
+
expect(text_node.text).to eq("Hello world")
|
|
390
391
|
end
|
|
391
392
|
|
|
392
|
-
it
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
429
|
+
it "preserves existing content when adding text" do
|
|
429
430
|
paragraph = described_class.new
|
|
430
|
-
paragraph.add_text(
|
|
431
|
+
paragraph.add_text("First")
|
|
431
432
|
paragraph.add_hard_break
|
|
432
|
-
paragraph.add_text(
|
|
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
|
|
440
|
-
it
|
|
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
|
|
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
|
|
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
|
|
467
|
+
it "preserves existing content when adding hard breaks" do
|
|
467
468
|
paragraph = described_class.new
|
|
468
|
-
paragraph.add_text(
|
|
469
|
+
paragraph.add_text("Text")
|
|
469
470
|
paragraph.add_hard_break
|
|
470
|
-
paragraph.add_text(
|
|
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
|
|
478
|
-
it
|
|
478
|
+
describe "serialization" do
|
|
479
|
+
it "converts to hash representation" do
|
|
479
480
|
paragraph = described_class.new
|
|
480
|
-
paragraph.add_text(
|
|
481
|
+
paragraph.add_text("Hello")
|
|
481
482
|
paragraph.add_hard_break
|
|
482
|
-
paragraph.add_text(
|
|
483
|
+
paragraph.add_text("World")
|
|
483
484
|
|
|
484
485
|
hash = paragraph.to_h
|
|
485
|
-
expect(hash[
|
|
486
|
-
expect(hash[
|
|
487
|
-
expect(hash[
|
|
488
|
-
expect(hash[
|
|
489
|
-
expect(hash[
|
|
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
|
|
493
|
+
it "converts to hash with marks" do
|
|
493
494
|
paragraph = described_class.new
|
|
494
|
-
paragraph.add_text(
|
|
495
|
-
paragraph.add_text(
|
|
496
|
-
paragraph.add_text(
|
|
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[
|
|
500
|
-
expect(hash[
|
|
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
|
|
504
|
-
paragraph = described_class.new(attrs: {
|
|
505
|
-
paragraph.add_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[
|
|
509
|
+
expect(hash["attrs"]).to eq({ "align" => "center" })
|
|
509
510
|
end
|
|
510
511
|
|
|
511
|
-
it
|
|
512
|
+
it "converts complex content to hash" do
|
|
512
513
|
paragraph = described_class.new
|
|
513
|
-
paragraph.add_text(
|
|
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(
|
|
519
|
+
paragraph.add_text("Styles")
|
|
519
520
|
|
|
520
521
|
hash = paragraph.to_h
|
|
521
|
-
expect(hash[
|
|
522
|
-
expect(hash[
|
|
523
|
-
expect(hash[
|
|
524
|
-
expect(hash[
|
|
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
|