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,68 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  RSpec.describe Prosereflect::Parser do
6
- let(:fixtures_path) { File.join(__dir__, '..', 'fixtures') }
6
+ let(:fixtures_path) { File.join(__dir__, "..", "fixtures") }
7
7
 
8
- describe 'document parsing' do
9
- describe '.parse_document' do
10
- it 'parses a simple document' do
8
+ describe "document parsing" do
9
+ describe ".parse_document" do
10
+ it "parses a simple document" do
11
11
  data = {
12
- 'type' => 'doc',
13
- 'content' => [
12
+ "type" => "doc",
13
+ "content" => [
14
14
  {
15
- 'type' => 'paragraph',
16
- 'content' => [
17
- { 'type' => 'text', 'text' => 'Hello world' }
18
- ]
19
- }
20
- ]
15
+ "type" => "paragraph",
16
+ "content" => [
17
+ { "type" => "text", "text" => "Hello world" },
18
+ ],
19
+ },
20
+ ],
21
21
  }
22
22
 
23
23
  document = described_class.parse_document(data)
24
24
  expect(document).to be_a(Prosereflect::Document)
25
- expect(document.text_content).to eq('Hello world')
25
+ expect(document.text_content).to eq("Hello world")
26
26
  end
27
27
 
28
- it 'parses a complex document with multiple blocks' do
28
+ it "parses a complex document with multiple blocks" do
29
29
  data = {
30
- 'type' => 'doc',
31
- 'content' => [
30
+ "type" => "doc",
31
+ "content" => [
32
32
  {
33
- 'type' => 'paragraph',
34
- 'content' => [
35
- { 'type' => 'text', 'text' => 'First paragraph' }
36
- ]
33
+ "type" => "paragraph",
34
+ "content" => [
35
+ { "type" => "text", "text" => "First paragraph" },
36
+ ],
37
37
  },
38
38
  {
39
- 'type' => 'bullet_list',
40
- 'content' => [
39
+ "type" => "bullet_list",
40
+ "content" => [
41
41
  {
42
- 'type' => 'list_item',
43
- 'content' => [
42
+ "type" => "list_item",
43
+ "content" => [
44
44
  {
45
- 'type' => 'paragraph',
46
- 'content' => [
47
- { 'type' => 'text', 'text' => 'List item' }
48
- ]
49
- }
50
- ]
51
- }
52
- ]
45
+ "type" => "paragraph",
46
+ "content" => [
47
+ { "type" => "text", "text" => "List item" },
48
+ ],
49
+ },
50
+ ],
51
+ },
52
+ ],
53
53
  },
54
54
  {
55
- 'type' => 'blockquote',
56
- 'content' => [
55
+ "type" => "blockquote",
56
+ "content" => [
57
57
  {
58
- 'type' => 'paragraph',
59
- 'content' => [
60
- { 'type' => 'text', 'text' => 'Quote text' }
61
- ]
62
- }
63
- ]
64
- }
65
- ]
58
+ "type" => "paragraph",
59
+ "content" => [
60
+ { "type" => "text", "text" => "Quote text" },
61
+ ],
62
+ },
63
+ ],
64
+ },
65
+ ],
66
66
  }
67
67
 
68
68
  document = described_class.parse_document(data)
@@ -73,17 +73,17 @@ RSpec.describe Prosereflect::Parser do
73
73
  expect(document.content[2]).to be_a(Prosereflect::Blockquote)
74
74
  end
75
75
 
76
- context 'with fixtures' do
77
- it 'parses all YAML fixtures successfully' do
78
- Dir.glob(File.join(fixtures_path, '*/*.yaml')).each do |yaml_file|
79
- data = YAML.safe_load(File.read(yaml_file))
76
+ context "with fixtures" do
77
+ it "parses all YAML fixtures successfully" do
78
+ Dir.glob(File.join(fixtures_path, "*/*.yaml")).each do |yaml_file|
79
+ data = YAML.safe_load_file(yaml_file)
80
80
  document = described_class.parse_document(data)
81
81
  expect(document).to be_a(Prosereflect::Document)
82
82
  end
83
83
  end
84
84
 
85
- it 'parses all JSON fixtures successfully' do
86
- Dir.glob(File.join(fixtures_path, '*/*.json')).each do |json_file|
85
+ it "parses all JSON fixtures successfully" do
86
+ Dir.glob(File.join(fixtures_path, "*/*.json")).each do |json_file|
87
87
  data = JSON.parse(File.read(json_file))
88
88
  document = described_class.parse_document(data)
89
89
  expect(document).to be_a(Prosereflect::Document)
@@ -91,60 +91,64 @@ RSpec.describe Prosereflect::Parser do
91
91
  end
92
92
  end
93
93
 
94
- context 'with invalid input' do
95
- it 'raises an error for nil input' do
96
- expect { described_class.parse_document(nil) }.to raise_error(ArgumentError)
94
+ context "with invalid input" do
95
+ it "raises an error for nil input" do
96
+ expect do
97
+ described_class.parse_document(nil)
98
+ end.to raise_error(ArgumentError)
97
99
  end
98
100
 
99
- it 'raises an error for non-hash input' do
100
- expect { described_class.parse_document('not a hash') }.to raise_error(ArgumentError)
101
+ it "raises an error for non-hash input" do
102
+ expect do
103
+ described_class.parse_document("not a hash")
104
+ end.to raise_error(ArgumentError)
101
105
  end
102
106
 
103
- it 'wraps non-document nodes in a document' do
104
- data = { 'type' => 'paragraph', 'content' => [] }
107
+ it "wraps non-document nodes in a document" do
108
+ data = { "type" => "paragraph", "content" => [] }
105
109
  document = described_class.parse_document(data)
106
110
  expect(document).to be_a(Prosereflect::Document)
107
111
  expect(document.content.first).to be_a(Prosereflect::Paragraph)
108
112
  end
109
113
 
110
- it 'wraps generic nodes in a document' do
111
- document = described_class.parse_document({ 'content' => [] })
114
+ it "wraps generic nodes in a document" do
115
+ document = described_class.parse_document({ "content" => [] })
112
116
  expect(document).to be_a(Prosereflect::Document)
113
117
  expect(document.content.size).to eq(1)
114
118
  expect(document.content.first).to be_a(Prosereflect::Node)
115
- expect(document.content.first.type).to eq('node')
119
+ expect(document.content.first.type).to eq("node")
116
120
  end
117
121
  end
118
122
  end
119
123
  end
120
124
 
121
- describe 'node parsing' do
122
- describe '.parse_node' do
123
- context 'basic nodes' do
124
- it 'parses text nodes' do
125
+ describe "node parsing" do
126
+ describe ".parse_node" do
127
+ context "basic nodes" do
128
+ it "parses text nodes" do
125
129
  data = {
126
- 'type' => 'text',
127
- 'text' => 'Hello world',
128
- 'marks' => [
129
- { 'type' => 'bold' },
130
- { 'type' => 'italic' }
131
- ]
130
+ "type" => "text",
131
+ "text" => "Hello world",
132
+ "marks" => [
133
+ { "type" => "bold" },
134
+ { "type" => "italic" },
135
+ ],
132
136
  }
133
137
 
134
138
  node = described_class.parse_node(data)
135
139
  expect(node).to be_a(Prosereflect::Text)
136
- expect(node.text).to eq('Hello world')
137
- expect(node.marks.map { |m| m['type'] }).to eq(%w[bold italic])
140
+ expect(node.text).to eq("Hello world")
141
+ expect(node.marks.map { |m| m["type"] }).to eq(%w[bold italic])
138
142
  end
139
143
 
140
- it 'parses paragraph nodes' do
144
+ it "parses paragraph nodes" do
141
145
  data = {
142
- 'type' => 'paragraph',
143
- 'content' => [
144
- { 'type' => 'text', 'text' => 'First' },
145
- { 'type' => 'hard_break' },
146
- { 'type' => 'text', 'text' => 'Second' }
147
- ]
146
+ "type" => "paragraph",
147
+ "content" => [
148
+ { "type" => "text", "text" => "First" },
149
+ { "type" => "hard_break" },
150
+ { "type" => "text", "text" => "Second" },
151
+ ],
148
152
  }
149
153
 
150
154
  node = described_class.parse_node(data)
@@ -153,103 +157,104 @@ RSpec.describe Prosereflect::Parser do
153
157
  expect(node.text_content).to eq("First\nSecond")
154
158
  end
155
159
 
156
- it 'parses hard break nodes' do
157
- data = { 'type' => 'hard_break' }
160
+ it "parses hard break nodes" do
161
+ data = { "type" => "hard_break" }
158
162
  node = described_class.parse_node(data)
159
163
  expect(node).to be_a(Prosereflect::HardBreak)
160
164
  end
161
165
 
162
- it 'parses horizontal rule nodes' do
166
+ it "parses horizontal rule nodes" do
163
167
  data = {
164
- 'type' => 'horizontal_rule',
165
- 'attrs' => {
166
- 'border_style' => 'dashed',
167
- 'width' => '80%'
168
- }
168
+ "type" => "horizontal_rule",
169
+ "attrs" => {
170
+ "border_style" => "dashed",
171
+ "width" => "80%",
172
+ },
169
173
  }
170
174
 
171
175
  node = described_class.parse_node(data)
172
176
  expect(node).to be_a(Prosereflect::HorizontalRule)
173
- expect(node.style).to eq('dashed')
174
- expect(node.width).to eq('80%')
177
+ expect(node.style).to eq("dashed")
178
+ expect(node.width).to eq("80%")
175
179
  end
176
180
  end
177
181
 
178
- context 'list nodes' do
179
- it 'parses bullet lists' do
182
+ context "list nodes" do
183
+ it "parses bullet lists" do
180
184
  data = {
181
- 'type' => 'bullet_list',
182
- 'attrs' => { 'bullet_style' => 'square' },
183
- 'content' => [
185
+ "type" => "bullet_list",
186
+ "attrs" => { "bullet_style" => "square" },
187
+ "content" => [
184
188
  {
185
- 'type' => 'list_item',
186
- 'content' => [
189
+ "type" => "list_item",
190
+ "content" => [
187
191
  {
188
- 'type' => 'paragraph',
189
- 'content' => [{ 'type' => 'text', 'text' => 'Item 1' }]
190
- }
191
- ]
192
- }
193
- ]
192
+ "type" => "paragraph",
193
+ "content" => [{ "type" => "text", "text" => "Item 1" }],
194
+ },
195
+ ],
196
+ },
197
+ ],
194
198
  }
195
199
 
196
200
  node = described_class.parse_node(data)
197
201
  expect(node).to be_a(Prosereflect::BulletList)
198
- expect(node.bullet_style).to eq('square')
199
- expect(node.items.first.text_content).to eq('Item 1')
202
+ expect(node.bullet_style).to eq("square")
203
+ expect(node.items.first.text_content).to eq("Item 1")
200
204
  end
201
205
 
202
- it 'parses ordered lists' do
206
+ it "parses ordered lists" do
203
207
  data = {
204
- 'type' => 'ordered_list',
205
- 'attrs' => { 'start' => 3 },
206
- 'content' => [
208
+ "type" => "ordered_list",
209
+ "attrs" => { "start" => 3 },
210
+ "content" => [
207
211
  {
208
- 'type' => 'list_item',
209
- 'content' => [
212
+ "type" => "list_item",
213
+ "content" => [
210
214
  {
211
- 'type' => 'paragraph',
212
- 'content' => [{ 'type' => 'text', 'text' => 'Item 3' }]
213
- }
214
- ]
215
- }
216
- ]
215
+ "type" => "paragraph",
216
+ "content" => [{ "type" => "text", "text" => "Item 3" }],
217
+ },
218
+ ],
219
+ },
220
+ ],
217
221
  }
218
222
 
219
223
  node = described_class.parse_node(data)
220
224
  expect(node).to be_a(Prosereflect::OrderedList)
221
225
  expect(node.start).to eq(3)
222
- expect(node.items.first.text_content).to eq('Item 3')
226
+ expect(node.items.first.text_content).to eq("Item 3")
223
227
  end
224
228
 
225
- it 'parses nested lists' do
229
+ it "parses nested lists" do
226
230
  data = {
227
- 'type' => 'bullet_list',
228
- 'content' => [
231
+ "type" => "bullet_list",
232
+ "content" => [
229
233
  {
230
- 'type' => 'list_item',
231
- 'content' => [
234
+ "type" => "list_item",
235
+ "content" => [
232
236
  {
233
- 'type' => 'paragraph',
234
- 'content' => [{ 'type' => 'text', 'text' => 'Level 1' }]
237
+ "type" => "paragraph",
238
+ "content" => [{ "type" => "text", "text" => "Level 1" }],
235
239
  },
236
240
  {
237
- 'type' => 'ordered_list',
238
- 'content' => [
241
+ "type" => "ordered_list",
242
+ "content" => [
239
243
  {
240
- 'type' => 'list_item',
241
- 'content' => [
244
+ "type" => "list_item",
245
+ "content" => [
242
246
  {
243
- 'type' => 'paragraph',
244
- 'content' => [{ 'type' => 'text', 'text' => 'Level 2' }]
245
- }
246
- ]
247
- }
248
- ]
249
- }
250
- ]
251
- }
252
- ]
247
+ "type" => "paragraph",
248
+ "content" => [{ "type" => "text",
249
+ "text" => "Level 2" }],
250
+ },
251
+ ],
252
+ },
253
+ ],
254
+ },
255
+ ],
256
+ },
257
+ ],
253
258
  }
254
259
 
255
260
  node = described_class.parse_node(data)
@@ -259,79 +264,79 @@ RSpec.describe Prosereflect::Parser do
259
264
  end
260
265
  end
261
266
 
262
- context 'table nodes' do
263
- it 'parses tables with complex content' do
267
+ context "table nodes" do
268
+ it "parses tables with complex content" do
264
269
  data = {
265
- 'type' => 'table',
266
- 'content' => [
270
+ "type" => "table",
271
+ "content" => [
267
272
  {
268
- 'type' => 'table_row',
269
- 'content' => [
273
+ "type" => "table_row",
274
+ "content" => [
270
275
  {
271
- 'type' => 'table_cell',
272
- 'attrs' => { 'colspan' => 2 },
273
- 'content' => [
276
+ "type" => "table_cell",
277
+ "attrs" => { "colspan" => 2 },
278
+ "content" => [
274
279
  {
275
- 'type' => 'paragraph',
276
- 'content' => [
280
+ "type" => "paragraph",
281
+ "content" => [
277
282
  {
278
- 'type' => 'text',
279
- 'text' => 'Header',
280
- 'marks' => [{ 'type' => 'bold' }]
281
- }
282
- ]
283
- }
284
- ]
285
- }
286
- ]
287
- }
288
- ]
283
+ "type" => "text",
284
+ "text" => "Header",
285
+ "marks" => [{ "type" => "bold" }],
286
+ },
287
+ ],
288
+ },
289
+ ],
290
+ },
291
+ ],
292
+ },
293
+ ],
289
294
  }
290
295
 
291
296
  node = described_class.parse_node(data)
292
297
  expect(node).to be_a(Prosereflect::Table)
293
- expect(node.rows.first.cells.first.attrs['colspan']).to eq(2)
294
- expect(node.rows.first.cells.first.text_content).to eq('Header')
298
+ expect(node.rows.first.cells.first.attrs["colspan"]).to eq(2)
299
+ expect(node.rows.first.cells.first.text_content).to eq("Header")
295
300
  end
296
301
  end
297
302
 
298
- context 'blockquote nodes' do
299
- it 'parses blockquotes with citation' do
303
+ context "blockquote nodes" do
304
+ it "parses blockquotes with citation" do
300
305
  data = {
301
- 'type' => 'blockquote',
302
- 'attrs' => { 'cite' => 'Author' },
303
- 'content' => [
306
+ "type" => "blockquote",
307
+ "attrs" => { "cite" => "Author" },
308
+ "content" => [
304
309
  {
305
- 'type' => 'paragraph',
306
- 'content' => [{ 'type' => 'text', 'text' => 'Quote' }]
307
- }
308
- ]
310
+ "type" => "paragraph",
311
+ "content" => [{ "type" => "text", "text" => "Quote" }],
312
+ },
313
+ ],
309
314
  }
310
315
 
311
316
  node = described_class.parse_node(data)
312
317
  expect(node).to be_a(Prosereflect::Blockquote)
313
- expect(node.citation).to eq('Author')
314
- expect(node.text_content).to eq('Quote')
318
+ expect(node.citation).to eq("Author")
319
+ expect(node.text_content).to eq("Quote")
315
320
  end
316
321
  end
317
322
 
318
- context 'with invalid input' do
319
- it 'returns generic node for unknown types' do
320
- data = { 'type' => 'unknown_type' }
323
+ context "with invalid input" do
324
+ it "returns generic node for unknown types" do
325
+ data = { "type" => "unknown_type" }
321
326
  node = described_class.parse_node(data)
322
327
  expect(node).to be_a(Prosereflect::Node)
323
- expect(node.type).to eq('node')
328
+ expect(node.type).to eq("node")
324
329
  end
325
330
 
326
- it 'handles missing content gracefully' do
327
- data = { 'type' => 'paragraph' }
331
+ it "handles missing content gracefully" do
332
+ data = { "type" => "paragraph" }
328
333
  node = described_class.parse_node(data)
329
334
  expect(node).to be_a(Prosereflect::Paragraph)
330
335
  expect(node.content).to eq([])
331
336
  end
332
337
 
333
- it 'handles missing attributes gracefully' do
334
- data = { 'type' => 'table_cell' }
338
+ it "handles missing attributes gracefully" do
339
+ data = { "type" => "table_cell" }
335
340
  node = described_class.parse_node(data)
336
341
  expect(node).to be_a(Prosereflect::TableCell)
337
342
  expect(node.attrs).to be_nil
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Prosereflect::ResolvedPos do
6
+ let(:doc) { Prosereflect::Document.new }
7
+ let(:resolved) { doc.resolve(0) }
8
+
9
+ describe "creation" do
10
+ it "creates resolved position" do
11
+ expect(resolved).to be_a(described_class)
12
+ end
13
+
14
+ it "has position" do
15
+ expect(resolved.pos).to eq(0)
16
+ end
17
+ end
18
+
19
+ describe "parent" do
20
+ it "returns parent node" do
21
+ expect(resolved.parent).to be_a(Prosereflect::Document)
22
+ end
23
+ end
24
+
25
+ describe "depth" do
26
+ it "returns depth" do
27
+ expect(resolved.depth).to be >= 0
28
+ end
29
+ end
30
+
31
+ describe "index" do
32
+ it "returns index within parent" do
33
+ expect(resolved.index).to eq(0)
34
+ end
35
+ end
36
+
37
+ describe "start" do
38
+ it "returns start position" do
39
+ expect(resolved.start).to eq(0)
40
+ end
41
+ end
42
+
43
+ describe "parent_offset" do
44
+ it "calculates offset within parent" do
45
+ expect(resolved.parent_offset).to eq(0)
46
+ end
47
+ end
48
+
49
+ describe "block?" do
50
+ it "checks if at block level" do
51
+ expect(resolved.block?).to be(false)
52
+ end
53
+ end
54
+
55
+ describe "inline?" do
56
+ it "checks if at inline level" do
57
+ expect(resolved.inline?).to be(true)
58
+ end
59
+ end
60
+
61
+ describe "equality" do
62
+ it "compares equal positions" do
63
+ other = doc.resolve(0)
64
+ expect(resolved).to eq(other)
65
+ end
66
+ end
67
+
68
+ describe "shared_depth" do
69
+ it "returns shared depth with another position" do
70
+ other = doc.resolve(0)
71
+ expect(resolved.shared_depth(other)).to be >= 0
72
+ end
73
+ end
74
+ end