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.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +97 -0
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +61 -75
- data/README.adoc +2 -0
- 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/blockquote.rb +9 -0
- data/lib/prosereflect/bullet_list.rb +25 -19
- data/lib/prosereflect/code_block.rb +1 -5
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/horizontal_rule.rb +9 -0
- data/lib/prosereflect/image.rb +9 -0
- data/lib/prosereflect/input/html.rb +96 -0
- data/lib/prosereflect/node.rb +141 -3
- data/lib/prosereflect/ordered_list.rb +2 -0
- data/lib/prosereflect/output/html.rb +227 -0
- data/lib/prosereflect/parser.rb +9 -0
- 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/text.rb +24 -0
- 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/version.rb +1 -1
- data/lib/prosereflect.rb +3 -0
- 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 +1 -1
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/input/html_spec.rb +197 -1
- data/spec/prosereflect/node_spec.rb +128 -0
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- 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/test_builder/marks_spec.rb +127 -0
- 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/spec_helper.rb +1 -0
- metadata +90 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "conftest"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Prosereflect::Schema::NodeType do
|
|
7
|
+
include_context "test_schema"
|
|
8
|
+
|
|
9
|
+
describe "is_block?" do
|
|
10
|
+
it "returns true for block nodes" do
|
|
11
|
+
expect(schema.node_type("paragraph").is_block?).to be true
|
|
12
|
+
expect(schema.node_type("heading").is_block?).to be true
|
|
13
|
+
expect(schema.node_type("blockquote").is_block?).to be true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "returns false for inline nodes" do
|
|
17
|
+
expect(schema.node_type("text").is_block?).to be false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "is_inline?" do
|
|
22
|
+
it "returns true for inline nodes" do
|
|
23
|
+
expect(schema.node_type("text").is_inline?).to be true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "returns false for block nodes" do
|
|
27
|
+
expect(schema.node_type("paragraph").is_inline?).to be false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "text?" do
|
|
32
|
+
it "returns true only for text type" do
|
|
33
|
+
expect(schema.node_type("text").text?).to be true
|
|
34
|
+
expect(schema.node_type("paragraph").text?).to be false
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "is_leaf?" do
|
|
39
|
+
it "returns true for nodes with empty content expression" do
|
|
40
|
+
expect(schema.node_type("text").is_leaf?).to be true
|
|
41
|
+
expect(schema.node_type("hard_break").is_leaf?).to be true
|
|
42
|
+
expect(schema.node_type("image").is_leaf?).to be true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "returns false for nodes with content" do
|
|
46
|
+
expect(schema.node_type("paragraph").is_leaf?).to be false
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "has_required_attrs?" do
|
|
51
|
+
it "returns true when node has required attributes" do
|
|
52
|
+
expect(schema.node_type("image").has_required_attrs?).to be true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "returns false when all attrs have defaults" do
|
|
56
|
+
expect(schema.node_type("heading").has_required_attrs?).to be false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "default_attrs" do
|
|
61
|
+
it "returns defaults for all attrs" do
|
|
62
|
+
defaults = schema.node_type("heading").default_attrs
|
|
63
|
+
expect(defaults).to eq({ "level" => 1 })
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "returns nil if any required attr has no default" do
|
|
67
|
+
defaults = schema.node_type("image").default_attrs
|
|
68
|
+
expect(defaults).to be_nil
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "compute_attrs" do
|
|
73
|
+
it "uses defaults when attrs not provided" do
|
|
74
|
+
attrs = schema.node_type("heading").compute_attrs(nil)
|
|
75
|
+
expect(attrs).to eq({ "level" => 1 })
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "overrides defaults with provided values" do
|
|
79
|
+
attrs = schema.node_type("heading").compute_attrs({ "level" => 3 })
|
|
80
|
+
expect(attrs).to eq({ "level" => 3 })
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "raises error for missing required attrs" do
|
|
84
|
+
expect do
|
|
85
|
+
schema.node_type("image").compute_attrs({})
|
|
86
|
+
end.to raise_error(Prosereflect::Schema::ValidationError)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "create" do
|
|
91
|
+
it "creates a node with computed attrs" do
|
|
92
|
+
node = schema.node_type("heading").create({ "level" => 2 }, nil, [])
|
|
93
|
+
expect(node.attrs["level"]).to eq(2)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "raises error for text node" do
|
|
97
|
+
expect do
|
|
98
|
+
schema.node_type("text").create({}, nil, [])
|
|
99
|
+
end.to raise_error(Prosereflect::Schema::Error)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
describe "valid_content?" do
|
|
104
|
+
it "returns true for valid content" do
|
|
105
|
+
frag = Prosereflect::Schema::Fragment.new([
|
|
106
|
+
schema.node_type("paragraph").create(
|
|
107
|
+
nil, [], []
|
|
108
|
+
),
|
|
109
|
+
])
|
|
110
|
+
expect(schema.node_type("doc").valid_content?(frag)).to be true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "returns false for invalid content" do
|
|
114
|
+
frag = Prosereflect::Schema::Fragment.new([
|
|
115
|
+
schema.text(""),
|
|
116
|
+
])
|
|
117
|
+
expect(schema.node_type("doc").valid_content?(frag)).to be false
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "check_content" do
|
|
122
|
+
it "raises error for invalid content" do
|
|
123
|
+
frag = Prosereflect::Schema::Fragment.new([
|
|
124
|
+
schema.text(""),
|
|
125
|
+
])
|
|
126
|
+
expect do
|
|
127
|
+
schema.node_type("doc").check_content(frag)
|
|
128
|
+
end.to raise_error(Prosereflect::Schema::ValidationError)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe "allows_mark_type?" do
|
|
133
|
+
it "returns true when mark is in mark_set" do
|
|
134
|
+
expect(schema.node_type("paragraph").allows_mark_type?(schema.mark_type("em"))).to be true
|
|
135
|
+
expect(schema.node_type("paragraph").allows_mark_type?(schema.mark_type("strong"))).to be true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it "returns false when mark is not in mark_set" do
|
|
139
|
+
expect(schema.node_type("code_block").allows_mark_type?(schema.mark_type("em"))).to be false
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "conftest"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Prosereflect::Schema do
|
|
7
|
+
include_context "test_schema"
|
|
8
|
+
|
|
9
|
+
describe "initialization" do
|
|
10
|
+
it "creates a schema with nodes and marks" do
|
|
11
|
+
expect(schema.nodes).to be_a(Hash)
|
|
12
|
+
expect(schema.marks).to be_a(Hash)
|
|
13
|
+
expect(schema.nodes.keys).to include("doc", "paragraph", "text")
|
|
14
|
+
expect(schema.marks.keys).to include("em", "strong", "link")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "raises error for missing top node" do
|
|
18
|
+
expect do
|
|
19
|
+
described_class.new(
|
|
20
|
+
nodes_spec: { "text" => {} },
|
|
21
|
+
marks_spec: {},
|
|
22
|
+
)
|
|
23
|
+
end.to raise_error(Prosereflect::Schema::ValidationError,
|
|
24
|
+
/missing its top node/)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "raises error for missing text type" do
|
|
28
|
+
expect do
|
|
29
|
+
described_class.new(
|
|
30
|
+
nodes_spec: { "doc" => { content: "block+" } },
|
|
31
|
+
marks_spec: {},
|
|
32
|
+
)
|
|
33
|
+
end.to raise_error(Prosereflect::Schema::ValidationError,
|
|
34
|
+
/every schema needs a 'text' type/)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "raises error if text has attrs" do
|
|
38
|
+
expect do
|
|
39
|
+
described_class.new(
|
|
40
|
+
nodes_spec: {
|
|
41
|
+
"doc" => { content: "block+" },
|
|
42
|
+
"text" => { attrs: { "something" => {} } },
|
|
43
|
+
},
|
|
44
|
+
marks_spec: {},
|
|
45
|
+
)
|
|
46
|
+
end.to raise_error(Prosereflect::Schema::ValidationError,
|
|
47
|
+
/text node type should not have attributes/)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "raises error if node and mark share same name" do
|
|
51
|
+
expect do
|
|
52
|
+
described_class.new(
|
|
53
|
+
nodes_spec: {
|
|
54
|
+
"doc" => { content: "block+" },
|
|
55
|
+
"text" => {},
|
|
56
|
+
"bold" => {},
|
|
57
|
+
},
|
|
58
|
+
marks_spec: {
|
|
59
|
+
"bold" => {},
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
end.to raise_error(Prosereflect::Schema::ValidationError,
|
|
63
|
+
/can not be both a node and a mark/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "node_type" do
|
|
68
|
+
it "returns node type by name" do
|
|
69
|
+
node_type = schema.node_type("paragraph")
|
|
70
|
+
expect(node_type.name).to eq("paragraph")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "raises error for unknown node type" do
|
|
74
|
+
expect do
|
|
75
|
+
schema.node_type("unknown")
|
|
76
|
+
end.to raise_error(Prosereflect::Schema::ValidationError,
|
|
77
|
+
/Unknown node type/)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe "mark_type" do
|
|
82
|
+
it "returns mark type by name" do
|
|
83
|
+
mark_type = schema.mark_type("em")
|
|
84
|
+
expect(mark_type.name).to eq("em")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "raises error for unknown mark type" do
|
|
88
|
+
expect do
|
|
89
|
+
schema.mark_type("unknown")
|
|
90
|
+
end.to raise_error(Prosereflect::Schema::ValidationError,
|
|
91
|
+
/Unknown mark type/)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe "node" do
|
|
96
|
+
it "creates a node by type name" do
|
|
97
|
+
node = schema.node("paragraph")
|
|
98
|
+
expect(node.type.name).to eq("paragraph")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "creates a node with attrs" do
|
|
102
|
+
node = schema.node("heading", { "level" => 2 })
|
|
103
|
+
expect(node.attrs["level"]).to eq(2)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "creates a node with content" do
|
|
107
|
+
text = schema.text("Hello")
|
|
108
|
+
para = schema.node("paragraph", nil, [text])
|
|
109
|
+
expect(para.content.content.first.text).to eq("Hello")
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "validates content" do
|
|
113
|
+
text = schema.text("Hello")
|
|
114
|
+
expect do
|
|
115
|
+
# doc requires block+, not inline content directly
|
|
116
|
+
schema.node("doc", nil, [text])
|
|
117
|
+
end.to raise_error(Prosereflect::Schema::ValidationError)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "text" do
|
|
122
|
+
it "creates a text node" do
|
|
123
|
+
text_node = schema.text("Hello")
|
|
124
|
+
expect(text_node.text).to eq("Hello")
|
|
125
|
+
expect(text_node.type.name).to eq("text")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "creates text with marks" do
|
|
129
|
+
em = schema.mark("em")
|
|
130
|
+
text_node = schema.text("Hello", [em])
|
|
131
|
+
expect(text_node.marks.length).to eq(1)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe "mark" do
|
|
136
|
+
it "creates a mark by name" do
|
|
137
|
+
mark = schema.mark("em")
|
|
138
|
+
expect(mark.type.name).to eq("em")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "creates a mark with attrs" do
|
|
142
|
+
mark = schema.mark("link", { "href" => "http://example.com" })
|
|
143
|
+
expect(mark.attrs["href"]).to eq("http://example.com")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe "node_from_json" do
|
|
148
|
+
it "deserializes a node from JSON" do
|
|
149
|
+
json = {
|
|
150
|
+
"type" => "paragraph",
|
|
151
|
+
"content" => [
|
|
152
|
+
{ "type" => "text", "text" => "Hello" },
|
|
153
|
+
],
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
node = schema.node_from_json(json)
|
|
157
|
+
expect(node.type.name).to eq("paragraph")
|
|
158
|
+
expect(node.content.content.first.text).to eq("Hello")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "deserializes marks" do
|
|
162
|
+
json = {
|
|
163
|
+
"type" => "paragraph",
|
|
164
|
+
"content" => [
|
|
165
|
+
{ "type" => "text", "text" => "Hello",
|
|
166
|
+
"marks" => [{ "type" => "em" }] },
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
node = schema.node_from_json(json)
|
|
171
|
+
expect(node.content.content.first.marks.length).to eq(1)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
describe "mark_from_json" do
|
|
176
|
+
it "deserializes a mark from JSON" do
|
|
177
|
+
json = { "type" => "em" }
|
|
178
|
+
mark = schema.mark_from_json(json)
|
|
179
|
+
expect(mark.type.name).to eq("em")
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "deserializes mark with attrs" do
|
|
183
|
+
json = { "type" => "link", "attrs" => { "href" => "http://example.com" } }
|
|
184
|
+
mark = schema.mark_from_json(json)
|
|
185
|
+
expect(mark.attrs["href"]).to eq("http://example.com")
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
describe "top_node_type" do
|
|
190
|
+
it "returns the top node type" do
|
|
191
|
+
expect(schema.top_node_type.name).to eq("doc")
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "../../fixtures/test_builder/helpers"
|
|
5
|
+
|
|
6
|
+
RSpec.describe TestBuilder do # rubocop:disable RSpec/SpecFilePathFormat
|
|
7
|
+
describe ".extract_markers" do
|
|
8
|
+
it "extracts an anchor marker and its position" do
|
|
9
|
+
_content, positions = described_class.extract_markers('doc(p("hello<|a> world"))')
|
|
10
|
+
expect(positions).to include("a")
|
|
11
|
+
expect(positions["a"]).to eq(12)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "extracts a cursor position marker" do
|
|
15
|
+
_content, positions = described_class.extract_markers('doc(p("hello<|> world"))')
|
|
16
|
+
expect(positions).to include(:cursor)
|
|
17
|
+
expect(positions[:cursor]).to eq(12)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "extracts a numbered position marker" do
|
|
21
|
+
_content, positions = described_class.extract_markers('doc(p("hello<1> world"))')
|
|
22
|
+
expect(positions).to include(1)
|
|
23
|
+
expect(positions[1]).to eq(12)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "extracts multiple markers from a single string" do
|
|
27
|
+
_content, positions = described_class.extract_markers('doc(p("hello<|a> world<|b>"))')
|
|
28
|
+
expect(positions).to include("a", "b")
|
|
29
|
+
expect(positions["a"]).to be < positions["b"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "returns an empty positions hash when no markers are present" do
|
|
33
|
+
_content, positions = described_class.extract_markers('doc(p("hello world"))')
|
|
34
|
+
expect(positions).to be_empty
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns the content string with all markers removed" do
|
|
38
|
+
content, _positions = described_class.extract_markers('doc(p("hello<|a> world<1>"))')
|
|
39
|
+
expect(content).to eq('doc(p("hello world"))')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "removes a cursor marker from the content string" do
|
|
43
|
+
content, _positions = described_class.extract_markers('doc(p("hello<|> world"))')
|
|
44
|
+
expect(content).to eq('doc(p("hello world"))')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "returns a two-element array of [cleaned_string, positions_hash]" do
|
|
48
|
+
result = described_class.extract_markers('doc(p("hello<|> world"))')
|
|
49
|
+
expect(result).to be_a(Array)
|
|
50
|
+
expect(result.size).to eq(2)
|
|
51
|
+
expect(result[0]).to be_a(String)
|
|
52
|
+
expect(result[1]).to be_a(Hash)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe ".parse" do
|
|
57
|
+
it "delegates to Builder#parse" do
|
|
58
|
+
builder = described_class.for_schema(nil)
|
|
59
|
+
expect(builder).to be_a(TestBuilder::Builder)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "returns nil when extract_content returns nil" do
|
|
63
|
+
# For strings where extract_content cannot find matching outer parens,
|
|
64
|
+
# parse returns nil.
|
|
65
|
+
doc = described_class.parse("hello")
|
|
66
|
+
expect(doc).to be_nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe ".for_schema" do
|
|
71
|
+
it "returns a Builder instance" do
|
|
72
|
+
builder = described_class.for_schema(nil)
|
|
73
|
+
expect(builder).to be_a(TestBuilder::Builder)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "assigns the schema to the builder" do
|
|
77
|
+
schema = Prosereflect::Schema.new(
|
|
78
|
+
nodes_spec: {
|
|
79
|
+
"doc" => { content: "block+" },
|
|
80
|
+
"paragraph" => { content: "inline*", group: "block" },
|
|
81
|
+
"text" => { group: "inline" },
|
|
82
|
+
},
|
|
83
|
+
marks_spec: {
|
|
84
|
+
"em" => { group: "mark" },
|
|
85
|
+
"strong" => { group: "mark" },
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
builder = described_class.for_schema(schema)
|
|
89
|
+
expect(builder.schema).to eq(schema)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "creates a builder with nil schema by default" do
|
|
93
|
+
builder = described_class.for_schema(nil)
|
|
94
|
+
expect(builder.schema).to be_nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe TestBuilder::Builder do
|
|
99
|
+
describe "#parse_content" do
|
|
100
|
+
it "parses a bare string literal into a string" do
|
|
101
|
+
builder = described_class.new
|
|
102
|
+
result = builder.parse_content('"hello"')
|
|
103
|
+
expect(result).to eq("hello")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe "#schema" do
|
|
108
|
+
it "is nil by default" do
|
|
109
|
+
builder = described_class.new
|
|
110
|
+
expect(builder.schema).to be_nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "stores the provided schema" do
|
|
114
|
+
schema = Prosereflect::Schema.new(
|
|
115
|
+
nodes_spec: {
|
|
116
|
+
"doc" => { content: "block+" },
|
|
117
|
+
"paragraph" => { content: "inline*", group: "block" },
|
|
118
|
+
"text" => { group: "inline" },
|
|
119
|
+
},
|
|
120
|
+
marks_spec: {},
|
|
121
|
+
)
|
|
122
|
+
builder = described_class.new(schema: schema)
|
|
123
|
+
expect(builder.schema).to eq(schema)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|