prosereflect 0.2.0 → 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 (110) 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/.gitignore +4 -0
  5. data/.rubocop_todo.yml +61 -75
  6. data/README.adoc +2 -0
  7. data/docs/Gemfile +10 -0
  8. data/docs/INDEX.adoc +45 -0
  9. data/docs/_advanced/index.adoc +15 -0
  10. data/docs/_advanced/schema.adoc +112 -0
  11. data/docs/_advanced/step-map.adoc +66 -0
  12. data/docs/_advanced/steps.adoc +88 -0
  13. data/docs/_advanced/test-builder.adoc +61 -0
  14. data/docs/_advanced/transform.adoc +92 -0
  15. data/docs/_config.yml +174 -0
  16. data/docs/_features/html-input.adoc +69 -0
  17. data/docs/_features/html-output.adoc +45 -0
  18. data/docs/_features/index.adoc +15 -0
  19. data/docs/_features/marks.adoc +86 -0
  20. data/docs/_features/node-types.adoc +124 -0
  21. data/docs/_features/user-mentions.adoc +47 -0
  22. data/docs/_guides/custom-nodes.adoc +107 -0
  23. data/docs/_guides/index.adoc +13 -0
  24. data/docs/_guides/round-trip-html.adoc +91 -0
  25. data/docs/_guides/serialization.adoc +109 -0
  26. data/docs/_pages/index.adoc +67 -0
  27. data/docs/_reference/document-api.adoc +49 -0
  28. data/docs/_reference/index.adoc +14 -0
  29. data/docs/_reference/node-api.adoc +79 -0
  30. data/docs/_reference/schema-api.adoc +95 -0
  31. data/docs/_reference/transform-api.adoc +77 -0
  32. data/docs/_understanding/document-model.adoc +65 -0
  33. data/docs/_understanding/fragment.adoc +52 -0
  34. data/docs/_understanding/index.adoc +14 -0
  35. data/docs/_understanding/resolved-position.adoc +53 -0
  36. data/docs/_understanding/slice.adoc +54 -0
  37. data/docs/lychee.toml +63 -0
  38. data/lib/prosereflect/blockquote.rb +9 -0
  39. data/lib/prosereflect/bullet_list.rb +25 -19
  40. data/lib/prosereflect/code_block.rb +1 -5
  41. data/lib/prosereflect/fragment.rb +249 -0
  42. data/lib/prosereflect/horizontal_rule.rb +9 -0
  43. data/lib/prosereflect/image.rb +9 -0
  44. data/lib/prosereflect/input/html.rb +96 -0
  45. data/lib/prosereflect/node.rb +141 -3
  46. data/lib/prosereflect/ordered_list.rb +2 -0
  47. data/lib/prosereflect/output/html.rb +227 -0
  48. data/lib/prosereflect/parser.rb +9 -0
  49. data/lib/prosereflect/resolved_pos.rb +256 -0
  50. data/lib/prosereflect/schema/attribute.rb +57 -0
  51. data/lib/prosereflect/schema/content_match.rb +656 -0
  52. data/lib/prosereflect/schema/fragment.rb +166 -0
  53. data/lib/prosereflect/schema/mark.rb +121 -0
  54. data/lib/prosereflect/schema/mark_type.rb +130 -0
  55. data/lib/prosereflect/schema/node.rb +236 -0
  56. data/lib/prosereflect/schema/node_type.rb +274 -0
  57. data/lib/prosereflect/schema/schema_main.rb +190 -0
  58. data/lib/prosereflect/schema/spec.rb +92 -0
  59. data/lib/prosereflect/schema.rb +39 -0
  60. data/lib/prosereflect/text.rb +24 -0
  61. data/lib/prosereflect/transform/attr_step.rb +157 -0
  62. data/lib/prosereflect/transform/insert_step.rb +115 -0
  63. data/lib/prosereflect/transform/mapping.rb +82 -0
  64. data/lib/prosereflect/transform/mark_step.rb +269 -0
  65. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  66. data/lib/prosereflect/transform/replace_step.rb +157 -0
  67. data/lib/prosereflect/transform/slice.rb +91 -0
  68. data/lib/prosereflect/transform/step.rb +89 -0
  69. data/lib/prosereflect/transform/step_map.rb +126 -0
  70. data/lib/prosereflect/transform/structure.rb +120 -0
  71. data/lib/prosereflect/transform/transform.rb +341 -0
  72. data/lib/prosereflect/transform.rb +26 -0
  73. data/lib/prosereflect/version.rb +1 -1
  74. data/lib/prosereflect.rb +3 -0
  75. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  76. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  77. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  78. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  79. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  80. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  81. data/spec/fixtures/documents/table_doc.yaml +32 -0
  82. data/spec/fixtures/documents/transform_test.yaml +14 -0
  83. data/spec/fixtures/schema/custom_schema.rb +37 -0
  84. data/spec/fixtures/schema/test_schema.rb +46 -0
  85. data/spec/fixtures/test_builder/helpers.rb +212 -0
  86. data/spec/prosereflect/document_spec.rb +1 -1
  87. data/spec/prosereflect/fragment_spec.rb +273 -0
  88. data/spec/prosereflect/input/html_spec.rb +197 -1
  89. data/spec/prosereflect/node_spec.rb +128 -0
  90. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  91. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  92. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  93. data/spec/prosereflect/schema/conftest.rb +68 -0
  94. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  95. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  96. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  97. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  98. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  99. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  100. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  101. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  102. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  103. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  104. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  105. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  106. data/spec/prosereflect/transform/step_spec.rb +211 -0
  107. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  108. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  109. data/spec/spec_helper.rb +1 -0
  110. metadata +90 -2
@@ -0,0 +1,472 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Prosereflect::Parser, ".round_trip" do
6
+ # Helper to create a document from YAML
7
+ def parse_doc(yaml_string)
8
+ Prosereflect::Parser.parse_document(YAML.safe_load(yaml_string))
9
+ end
10
+
11
+ describe "Document serialization round-trip" do
12
+ it "round-trips simple paragraph" do
13
+ doc = parse_doc({
14
+ "type" => "doc",
15
+ "content" => [
16
+ {
17
+ "type" => "paragraph",
18
+ "content" => [
19
+ { "type" => "text", "text" => "Hello World" },
20
+ ],
21
+ },
22
+ ],
23
+ }.to_yaml)
24
+
25
+ ruby_json = doc.to_h
26
+
27
+ # Parse again and verify same structure
28
+ doc2 = described_class.parse_document(ruby_json)
29
+ expect(doc2.to_h).to eq(ruby_json)
30
+ end
31
+
32
+ it "round-trips document with formatted text" do
33
+ doc = parse_doc({
34
+ "type" => "doc",
35
+ "content" => [
36
+ {
37
+ "type" => "paragraph",
38
+ "content" => [
39
+ {
40
+ "type" => "text",
41
+ "text" => "Hello",
42
+ "marks" => [{ "type" => "bold" }],
43
+ },
44
+ {
45
+ "type" => "text",
46
+ "text" => " World",
47
+ },
48
+ ],
49
+ },
50
+ ],
51
+ }.to_yaml)
52
+
53
+ ruby_json = doc.to_h
54
+
55
+ doc2 = described_class.parse_document(ruby_json)
56
+ expect(doc2.to_h).to eq(ruby_json)
57
+ end
58
+
59
+ it "round-trips heading" do
60
+ doc = parse_doc({
61
+ "type" => "doc",
62
+ "content" => [
63
+ {
64
+ "type" => "heading",
65
+ "attrs" => { "level" => 1 },
66
+ "content" => [
67
+ { "type" => "text", "text" => "Title" },
68
+ ],
69
+ },
70
+ ],
71
+ }.to_yaml)
72
+
73
+ ruby_json = doc.to_h
74
+
75
+ doc2 = described_class.parse_document(ruby_json)
76
+ expect(doc2.to_h).to eq(ruby_json)
77
+ end
78
+
79
+ it "round-trips blockquote" do
80
+ doc = parse_doc({
81
+ "type" => "doc",
82
+ "content" => [
83
+ {
84
+ "type" => "blockquote",
85
+ "content" => [
86
+ {
87
+ "type" => "paragraph",
88
+ "content" => [
89
+ { "type" => "text", "text" => "Quote text" },
90
+ ],
91
+ },
92
+ ],
93
+ },
94
+ ],
95
+ }.to_yaml)
96
+
97
+ ruby_json = doc.to_h
98
+
99
+ doc2 = described_class.parse_document(ruby_json)
100
+ expect(doc2.to_h).to eq(ruby_json)
101
+ end
102
+
103
+ it "round-trips bullet list" do
104
+ doc = parse_doc({
105
+ "type" => "doc",
106
+ "content" => [
107
+ {
108
+ "type" => "bullet_list",
109
+ "content" => [
110
+ {
111
+ "type" => "list_item",
112
+ "content" => [
113
+ {
114
+ "type" => "paragraph",
115
+ "content" => [
116
+ { "type" => "text", "text" => "Item 1" },
117
+ ],
118
+ },
119
+ ],
120
+ },
121
+ ],
122
+ },
123
+ ],
124
+ }.to_yaml)
125
+
126
+ ruby_json = doc.to_h
127
+
128
+ doc2 = described_class.parse_document(ruby_json)
129
+ expect(doc2.to_h).to eq(ruby_json)
130
+ end
131
+
132
+ it "round-trips ordered list" do
133
+ doc = parse_doc({
134
+ "type" => "doc",
135
+ "content" => [
136
+ {
137
+ "type" => "ordered_list",
138
+ "attrs" => { "order" => 1 },
139
+ "content" => [
140
+ {
141
+ "type" => "list_item",
142
+ "content" => [
143
+ {
144
+ "type" => "paragraph",
145
+ "content" => [
146
+ { "type" => "text", "text" => "Item 1" },
147
+ ],
148
+ },
149
+ ],
150
+ },
151
+ ],
152
+ },
153
+ ],
154
+ }.to_yaml)
155
+
156
+ ruby_json = doc.to_h
157
+
158
+ doc2 = described_class.parse_document(ruby_json)
159
+ expect(doc2.to_h).to eq(ruby_json)
160
+ end
161
+
162
+ it "round-trips table" do
163
+ doc = parse_doc({
164
+ "type" => "doc",
165
+ "content" => [
166
+ {
167
+ "type" => "table",
168
+ "content" => [
169
+ {
170
+ "type" => "table_row",
171
+ "content" => [
172
+ {
173
+ "type" => "table_cell",
174
+ "content" => [
175
+ {
176
+ "type" => "paragraph",
177
+ "content" => [
178
+ { "type" => "text", "text" => "Cell 1" },
179
+ ],
180
+ },
181
+ ],
182
+ },
183
+ ],
184
+ },
185
+ ],
186
+ },
187
+ ],
188
+ }.to_yaml)
189
+
190
+ ruby_json = doc.to_h
191
+
192
+ doc2 = described_class.parse_document(ruby_json)
193
+ expect(doc2.to_h).to eq(ruby_json)
194
+ end
195
+
196
+ it "round-trips hard_break" do
197
+ doc = parse_doc({
198
+ "type" => "doc",
199
+ "content" => [
200
+ {
201
+ "type" => "paragraph",
202
+ "content" => [
203
+ { "type" => "text", "text" => "Line 1" },
204
+ { "type" => "hard_break" },
205
+ { "type" => "text", "text" => "Line 2" },
206
+ ],
207
+ },
208
+ ],
209
+ }.to_yaml)
210
+
211
+ ruby_json = doc.to_h
212
+
213
+ doc2 = described_class.parse_document(ruby_json)
214
+ expect(doc2.to_h).to eq(ruby_json)
215
+ end
216
+
217
+ it "round-trips horizontal rule" do
218
+ doc = parse_doc({
219
+ "type" => "doc",
220
+ "content" => [
221
+ { "type" => "horizontal_rule" },
222
+ ],
223
+ }.to_yaml)
224
+
225
+ ruby_json = doc.to_h
226
+
227
+ doc2 = described_class.parse_document(ruby_json)
228
+ expect(doc2.to_h).to eq(ruby_json)
229
+ end
230
+
231
+ it "round-trips code block" do
232
+ doc = parse_doc({
233
+ "type" => "doc",
234
+ "content" => [
235
+ {
236
+ "type" => "code_block",
237
+ "attrs" => { "language" => "ruby" },
238
+ "content" => [
239
+ { "type" => "text", "text" => "puts 'hello'" },
240
+ ],
241
+ },
242
+ ],
243
+ }.to_yaml)
244
+
245
+ ruby_json = doc.to_h
246
+
247
+ doc2 = described_class.parse_document(ruby_json)
248
+ # Verify structure is preserved - code block type and language
249
+ code_block = doc2.find_first("code_block")
250
+ expect(code_block).to be_a(Prosereflect::CodeBlock)
251
+ expect(code_block.language).to eq("ruby")
252
+ end
253
+ end
254
+
255
+ describe "Node equality" do
256
+ it "same nodes are equal" do
257
+ doc1 = parse_doc({
258
+ "type" => "doc",
259
+ "content" => [
260
+ {
261
+ "type" => "paragraph",
262
+ "content" => [
263
+ { "type" => "text", "text" => "Hello" },
264
+ ],
265
+ },
266
+ ],
267
+ }.to_yaml)
268
+
269
+ doc2 = parse_doc({
270
+ "type" => "doc",
271
+ "content" => [
272
+ {
273
+ "type" => "paragraph",
274
+ "content" => [
275
+ { "type" => "text", "text" => "Hello" },
276
+ ],
277
+ },
278
+ ],
279
+ }.to_yaml)
280
+
281
+ # Both should have same structure
282
+ expect(doc1.to_h).to eq(doc2.to_h)
283
+ end
284
+
285
+ it "different nodes are not equal" do
286
+ doc1 = parse_doc({
287
+ "type" => "doc",
288
+ "content" => [
289
+ {
290
+ "type" => "paragraph",
291
+ "content" => [
292
+ { "type" => "text", "text" => "Hello" },
293
+ ],
294
+ },
295
+ ],
296
+ }.to_yaml)
297
+
298
+ doc2 = parse_doc({
299
+ "type" => "doc",
300
+ "content" => [
301
+ {
302
+ "type" => "paragraph",
303
+ "content" => [
304
+ { "type" => "text", "text" => "World" },
305
+ ],
306
+ },
307
+ ],
308
+ }.to_yaml)
309
+
310
+ expect(doc1.to_h).not_to eq(doc2.to_h)
311
+ end
312
+ end
313
+
314
+ describe "Text content extraction" do
315
+ it "extracts text from simple document" do
316
+ doc = parse_doc({
317
+ "type" => "doc",
318
+ "content" => [
319
+ {
320
+ "type" => "paragraph",
321
+ "content" => [
322
+ { "type" => "text", "text" => "Hello World" },
323
+ ],
324
+ },
325
+ ],
326
+ }.to_yaml)
327
+
328
+ expect(doc.text_content).to eq("Hello World")
329
+ end
330
+
331
+ it "extracts text from nested structure" do
332
+ doc = parse_doc({
333
+ "type" => "doc",
334
+ "content" => [
335
+ {
336
+ "type" => "blockquote",
337
+ "content" => [
338
+ {
339
+ "type" => "paragraph",
340
+ "content" => [
341
+ { "type" => "text", "text" => "Quote text" },
342
+ ],
343
+ },
344
+ ],
345
+ },
346
+ ],
347
+ }.to_yaml)
348
+
349
+ expect(doc.text_content).to eq("Quote text")
350
+ end
351
+
352
+ it "extracts concatenated text from multiple paragraphs" do
353
+ doc = parse_doc({
354
+ "type" => "doc",
355
+ "content" => [
356
+ {
357
+ "type" => "paragraph",
358
+ "content" => [
359
+ { "type" => "text", "text" => "First" },
360
+ ],
361
+ },
362
+ {
363
+ "type" => "paragraph",
364
+ "content" => [
365
+ { "type" => "text", "text" => "Second" },
366
+ ],
367
+ },
368
+ ],
369
+ }.to_yaml)
370
+
371
+ # text_content returns text with newlines between block elements
372
+ expect(doc.text_content).to include("First")
373
+ expect(doc.text_content).to include("Second")
374
+ end
375
+ end
376
+
377
+ describe "Mark preservation" do
378
+ it "preserves bold mark" do
379
+ doc = parse_doc({
380
+ "type" => "doc",
381
+ "content" => [
382
+ {
383
+ "type" => "paragraph",
384
+ "content" => [
385
+ {
386
+ "type" => "text",
387
+ "text" => "bold text",
388
+ "marks" => [{ "type" => "bold" }],
389
+ },
390
+ ],
391
+ },
392
+ ],
393
+ }.to_yaml)
394
+
395
+ para = doc.find_first("paragraph")
396
+ text = para.content.first
397
+ expect(text.marks).to include("type" => "bold")
398
+ end
399
+
400
+ it "preserves italic mark" do
401
+ doc = parse_doc({
402
+ "type" => "doc",
403
+ "content" => [
404
+ {
405
+ "type" => "paragraph",
406
+ "content" => [
407
+ {
408
+ "type" => "text",
409
+ "text" => "italic text",
410
+ "marks" => [{ "type" => "italic" }],
411
+ },
412
+ ],
413
+ },
414
+ ],
415
+ }.to_yaml)
416
+
417
+ para = doc.find_first("paragraph")
418
+ text = para.content.first
419
+ expect(text.marks).to include("type" => "italic")
420
+ end
421
+
422
+ it "preserves multiple marks" do
423
+ doc = parse_doc({
424
+ "type" => "doc",
425
+ "content" => [
426
+ {
427
+ "type" => "paragraph",
428
+ "content" => [
429
+ {
430
+ "type" => "text",
431
+ "text" => "bold and italic",
432
+ "marks" => [
433
+ { "type" => "bold" },
434
+ { "type" => "italic" },
435
+ ],
436
+ },
437
+ ],
438
+ },
439
+ ],
440
+ }.to_yaml)
441
+
442
+ para = doc.find_first("paragraph")
443
+ text = para.content.first
444
+ expect(text.marks).to include("type" => "bold")
445
+ expect(text.marks).to include("type" => "italic")
446
+ end
447
+
448
+ it "preserves link mark with attrs" do
449
+ doc = parse_doc({
450
+ "type" => "doc",
451
+ "content" => [
452
+ {
453
+ "type" => "paragraph",
454
+ "content" => [
455
+ {
456
+ "type" => "text",
457
+ "text" => "link text",
458
+ "marks" => [
459
+ { "type" => "link", "attrs" => { "href" => "https://example.com" } },
460
+ ],
461
+ },
462
+ ],
463
+ },
464
+ ],
465
+ }.to_yaml)
466
+
467
+ para = doc.find_first("paragraph")
468
+ text = para.content.first
469
+ expect(text.marks).to include("type" => "link", "attrs" => { "href" => "https://example.com" })
470
+ end
471
+ end
472
+ end
@@ -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
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "prosereflect"
5
+
6
+ RSpec.configure do |config|
7
+ config.example_status_persistence_file_path = ".rspec_status"
8
+ config.disable_monkey_patching!
9
+ config.expect_with :rspec do |c|
10
+ c.syntax = :expect
11
+ end
12
+ end
13
+
14
+ # Standard test schema matching prosemirror-py test_builder
15
+ RSpec.shared_context "test_schema" do
16
+ let(:test_schema) do
17
+ Prosereflect::Schema.new(
18
+ nodes_spec: {
19
+ "doc" => { content: "block+" },
20
+ "paragraph" => { content: "inline*", group: "block" },
21
+ "heading" => { content: "inline*",
22
+ attrs: { "level" => { default: 1 } }, group: "block" },
23
+ "blockquote" => { content: "block+", group: "block" },
24
+ "code_block" => { content: "text*", marks: "", code: true,
25
+ group: "block" },
26
+ "horizontal_rule" => { group: "block" },
27
+ "text" => { group: "inline" },
28
+ "image" => { inline: true, group: "inline", atom: true,
29
+ attrs: { "src" => {}, "alt" => { default: "" }, "title" => { default: "" } } },
30
+ "hard_break" => { inline: true, group: "inline", atom: true },
31
+ "ordered_list" => { content: "list_item+", group: "block",
32
+ attrs: { "order" => { default: 1 } } },
33
+ "bullet_list" => { content: "list_item+", group: "block" },
34
+ "list_item" => { content: "paragraph block*", defining: true },
35
+ },
36
+ marks_spec: {
37
+ "link" => { attrs: { "href" => { default: "" }, "title" => { default: "" } },
38
+ inclusive: false },
39
+ "em" => { group: "mark" },
40
+ "strong" => { group: "mark" },
41
+ "code" => { group: "mark" },
42
+ },
43
+ )
44
+ end
45
+
46
+ let(:schema) { test_schema }
47
+ end
48
+
49
+ RSpec.shared_context "custom_schema" do
50
+ let(:custom_schema) do
51
+ Prosereflect::Schema.new(
52
+ nodes_spec: {
53
+ "doc" => { content: "block+" },
54
+ "paragraph" => { content: "inline*", group: "block" },
55
+ "text" => { group: "inline" },
56
+ },
57
+ marks_spec: {
58
+ "bold" => {},
59
+ "italic" => {},
60
+ "link" => { attrs: { "href" => {} }, inclusive: false,
61
+ excludes: "emoji" },
62
+ "emoji" => { group: "mark" },
63
+ },
64
+ )
65
+ end
66
+
67
+ let(:schema) { custom_schema }
68
+ end