prosereflect 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +97 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +19 -1
- data/.rubocop_todo.yml +119 -183
- data/CLAUDE.md +78 -0
- data/Gemfile +8 -4
- data/README.adoc +2 -0
- data/Rakefile +3 -3
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +45 -0
- data/docs/_advanced/index.adoc +15 -0
- data/docs/_advanced/schema.adoc +112 -0
- data/docs/_advanced/step-map.adoc +66 -0
- data/docs/_advanced/steps.adoc +88 -0
- data/docs/_advanced/test-builder.adoc +61 -0
- data/docs/_advanced/transform.adoc +92 -0
- data/docs/_config.yml +174 -0
- data/docs/_features/html-input.adoc +69 -0
- data/docs/_features/html-output.adoc +45 -0
- data/docs/_features/index.adoc +15 -0
- data/docs/_features/marks.adoc +86 -0
- data/docs/_features/node-types.adoc +124 -0
- data/docs/_features/user-mentions.adoc +47 -0
- data/docs/_guides/custom-nodes.adoc +107 -0
- data/docs/_guides/index.adoc +13 -0
- data/docs/_guides/round-trip-html.adoc +91 -0
- data/docs/_guides/serialization.adoc +109 -0
- data/docs/_pages/index.adoc +67 -0
- data/docs/_reference/document-api.adoc +49 -0
- data/docs/_reference/index.adoc +14 -0
- data/docs/_reference/node-api.adoc +79 -0
- data/docs/_reference/schema-api.adoc +95 -0
- data/docs/_reference/transform-api.adoc +77 -0
- data/docs/_understanding/document-model.adoc +65 -0
- data/docs/_understanding/fragment.adoc +52 -0
- data/docs/_understanding/index.adoc +14 -0
- data/docs/_understanding/resolved-position.adoc +53 -0
- data/docs/_understanding/slice.adoc +54 -0
- data/docs/lychee.toml +63 -0
- data/lib/prosereflect/attribute/base.rb +4 -6
- data/lib/prosereflect/attribute/bold.rb +2 -4
- data/lib/prosereflect/attribute/href.rb +1 -3
- data/lib/prosereflect/attribute/id.rb +7 -7
- data/lib/prosereflect/attribute.rb +4 -7
- data/lib/prosereflect/blockquote.rb +19 -11
- data/lib/prosereflect/bullet_list.rb +36 -29
- data/lib/prosereflect/code_block.rb +23 -27
- data/lib/prosereflect/code_block_wrapper.rb +12 -13
- data/lib/prosereflect/document.rb +14 -22
- data/lib/prosereflect/fragment.rb +249 -0
- data/lib/prosereflect/hard_break.rb +6 -6
- data/lib/prosereflect/heading.rb +14 -15
- data/lib/prosereflect/horizontal_rule.rb +23 -14
- data/lib/prosereflect/image.rb +32 -23
- data/lib/prosereflect/input/html.rb +179 -104
- data/lib/prosereflect/input.rb +7 -0
- data/lib/prosereflect/list_item.rb +11 -12
- data/lib/prosereflect/mark/base.rb +9 -11
- data/lib/prosereflect/mark/bold.rb +1 -3
- data/lib/prosereflect/mark/code.rb +1 -3
- data/lib/prosereflect/mark/italic.rb +1 -3
- data/lib/prosereflect/mark/link.rb +1 -3
- data/lib/prosereflect/mark/strike.rb +1 -3
- data/lib/prosereflect/mark/subscript.rb +1 -3
- data/lib/prosereflect/mark/superscript.rb +1 -3
- data/lib/prosereflect/mark/underline.rb +1 -3
- data/lib/prosereflect/mark.rb +9 -5
- data/lib/prosereflect/node.rb +171 -33
- data/lib/prosereflect/ordered_list.rb +17 -14
- data/lib/prosereflect/output/html.rb +279 -50
- data/lib/prosereflect/output.rb +7 -0
- data/lib/prosereflect/paragraph.rb +11 -13
- data/lib/prosereflect/parser.rb +56 -66
- data/lib/prosereflect/resolved_pos.rb +256 -0
- data/lib/prosereflect/schema/attribute.rb +57 -0
- data/lib/prosereflect/schema/content_match.rb +656 -0
- data/lib/prosereflect/schema/fragment.rb +166 -0
- data/lib/prosereflect/schema/mark.rb +121 -0
- data/lib/prosereflect/schema/mark_type.rb +130 -0
- data/lib/prosereflect/schema/node.rb +236 -0
- data/lib/prosereflect/schema/node_type.rb +274 -0
- data/lib/prosereflect/schema/schema_main.rb +190 -0
- data/lib/prosereflect/schema/spec.rb +92 -0
- data/lib/prosereflect/schema.rb +39 -0
- data/lib/prosereflect/table.rb +12 -13
- data/lib/prosereflect/table_cell.rb +13 -13
- data/lib/prosereflect/table_header.rb +17 -17
- data/lib/prosereflect/table_row.rb +12 -12
- data/lib/prosereflect/text.rb +35 -11
- data/lib/prosereflect/transform/attr_step.rb +157 -0
- data/lib/prosereflect/transform/insert_step.rb +115 -0
- data/lib/prosereflect/transform/mapping.rb +82 -0
- data/lib/prosereflect/transform/mark_step.rb +269 -0
- data/lib/prosereflect/transform/replace_around_step.rb +181 -0
- data/lib/prosereflect/transform/replace_step.rb +157 -0
- data/lib/prosereflect/transform/slice.rb +91 -0
- data/lib/prosereflect/transform/step.rb +89 -0
- data/lib/prosereflect/transform/step_map.rb +126 -0
- data/lib/prosereflect/transform/structure.rb +120 -0
- data/lib/prosereflect/transform/transform.rb +341 -0
- data/lib/prosereflect/transform.rb +26 -0
- data/lib/prosereflect/user.rb +15 -15
- data/lib/prosereflect/version.rb +1 -1
- data/lib/prosereflect.rb +30 -17
- data/prosereflect.gemspec +17 -16
- data/spec/fixtures/documents/formatted_text.yaml +14 -0
- data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
- data/spec/fixtures/documents/lists_doc.yaml +32 -0
- data/spec/fixtures/documents/mixed_content.yaml +40 -0
- data/spec/fixtures/documents/nested_doc.yaml +20 -0
- data/spec/fixtures/documents/simple_doc.yaml +6 -0
- data/spec/fixtures/documents/table_doc.yaml +32 -0
- data/spec/fixtures/documents/transform_test.yaml +14 -0
- data/spec/fixtures/schema/custom_schema.rb +37 -0
- data/spec/fixtures/schema/test_schema.rb +46 -0
- data/spec/fixtures/test_builder/helpers.rb +212 -0
- data/spec/prosereflect/document_spec.rb +332 -330
- data/spec/prosereflect/fragment_spec.rb +273 -0
- data/spec/prosereflect/hard_break_spec.rb +125 -125
- data/spec/prosereflect/input/html_spec.rb +718 -522
- data/spec/prosereflect/node_spec.rb +311 -182
- data/spec/prosereflect/output/html_spec.rb +105 -105
- data/spec/prosereflect/output/whitespace_spec.rb +248 -0
- data/spec/prosereflect/paragraph_spec.rb +275 -274
- data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
- data/spec/prosereflect/parser_spec.rb +185 -180
- data/spec/prosereflect/resolved_pos_spec.rb +74 -0
- data/spec/prosereflect/schema/conftest.rb +68 -0
- data/spec/prosereflect/schema/content_match_spec.rb +237 -0
- data/spec/prosereflect/schema/mark_spec.rb +274 -0
- data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
- data/spec/prosereflect/schema/node_type_spec.rb +142 -0
- data/spec/prosereflect/schema/schema_spec.rb +194 -0
- data/spec/prosereflect/table_cell_spec.rb +183 -183
- data/spec/prosereflect/table_row_spec.rb +149 -149
- data/spec/prosereflect/table_spec.rb +320 -318
- data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
- data/spec/prosereflect/text_spec.rb +133 -132
- data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
- data/spec/prosereflect/transform/mapping_spec.rb +226 -0
- data/spec/prosereflect/transform/replace_spec.rb +832 -0
- data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
- data/spec/prosereflect/transform/slice_spec.rb +48 -0
- data/spec/prosereflect/transform/step_map_spec.rb +70 -0
- data/spec/prosereflect/transform/step_spec.rb +211 -0
- data/spec/prosereflect/transform/structure_spec.rb +98 -0
- data/spec/prosereflect/transform/transform_spec.rb +238 -0
- data/spec/prosereflect/user_spec.rb +31 -28
- data/spec/prosereflect_spec.rb +28 -26
- data/spec/spec_helper.rb +7 -6
- data/spec/support/matchers.rb +6 -6
- data/spec/support/shared_examples.rb +49 -49
- metadata +96 -5
- data/spec/prosereflect/version_spec.rb +0 -11
|
@@ -0,0 +1,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
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "conftest"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Prosereflect::Schema::ContentMatch do
|
|
7
|
+
include_context "test_schema"
|
|
8
|
+
|
|
9
|
+
def get(expression)
|
|
10
|
+
described_class.parse(expression, schema.nodes)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def match(expression, types)
|
|
14
|
+
m = get(expression)
|
|
15
|
+
ts = types.split.map { |t| schema.nodes[t] }
|
|
16
|
+
i = 0
|
|
17
|
+
while m && i < ts.length
|
|
18
|
+
m = m.match_type(ts[i])
|
|
19
|
+
i += 1
|
|
20
|
+
end
|
|
21
|
+
m ? m.valid_end : false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "parsing content expressions" do
|
|
25
|
+
it "parses empty expression" do
|
|
26
|
+
expect(get("").valid_end).to be true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "parses 'image*' with no content" do
|
|
30
|
+
expect(match("image*", "")).to be true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "parses 'image*' with image" do
|
|
34
|
+
expect(match("image*", "image")).to be true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "parses 'image*' with multiple images" do
|
|
38
|
+
expect(match("image*", "image image image image")).to be true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "rejects 'image*' with image and text" do
|
|
42
|
+
expect(match("image*", "image text")).to be false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "parses 'inline*' with inline content" do
|
|
46
|
+
expect(match("inline*", "image text")).to be true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "rejects 'inline*' with paragraph" do
|
|
50
|
+
expect(match("inline*", "paragraph")).to be false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "choice expressions" do
|
|
55
|
+
it "parses '(paragraph | heading)' with paragraph" do
|
|
56
|
+
expect(match("(paragraph | heading)", "paragraph")).to be true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "parses '(paragraph | heading)' with heading" do
|
|
60
|
+
expect(match("(paragraph | heading)", "heading")).to be true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "rejects '(paragraph | heading)' with image" do
|
|
64
|
+
expect(match("(paragraph | heading)", "image")).to be false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "sequence expressions" do
|
|
69
|
+
it "parses 'paragraph horizontal_rule paragraph'" do
|
|
70
|
+
expect(match("paragraph horizontal_rule paragraph",
|
|
71
|
+
"paragraph horizontal_rule paragraph")).to be true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "rejects 'paragraph horizontal_rule' when given extra paragraph" do
|
|
75
|
+
expect(match("paragraph horizontal_rule",
|
|
76
|
+
"paragraph horizontal_rule paragraph")).to be false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "rejects 'paragraph horizontal_rule paragraph' when missing final paragraph" do
|
|
80
|
+
expect(match("paragraph horizontal_rule paragraph",
|
|
81
|
+
"paragraph horizontal_rule")).to be false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "rejects when order doesn't match" do
|
|
85
|
+
expect(match("paragraph horizontal_rule",
|
|
86
|
+
"horizontal_rule paragraph horizontal_rule")).to be false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "optional expressions (*)" do
|
|
91
|
+
it "parses 'heading paragraph*' with just heading" do
|
|
92
|
+
expect(match("heading paragraph*", "heading")).to be true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "parses 'heading paragraph*' with multiple paragraphs" do
|
|
96
|
+
expect(match("heading paragraph*",
|
|
97
|
+
"heading paragraph paragraph")).to be true
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "parses 'paragraph paragraph*' with paragraph" do
|
|
101
|
+
expect(match("paragraph paragraph*", "paragraph")).to be true
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe "required expressions (+)" do
|
|
106
|
+
it "parses 'heading paragraph+' with heading and paragraph" do
|
|
107
|
+
expect(match("heading paragraph+", "heading paragraph")).to be true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "parses 'heading paragraph+' with multiple paragraphs" do
|
|
111
|
+
expect(match("heading paragraph+",
|
|
112
|
+
"heading paragraph paragraph")).to be true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "rejects 'heading paragraph+' with only heading" do
|
|
116
|
+
expect(match("heading paragraph+", "heading")).to be false
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "rejects 'paragraph paragraph+' when first is not paragraph" do
|
|
120
|
+
expect(match("paragraph paragraph+",
|
|
121
|
+
"horizontal_rule paragraph")).to be false
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe "optional single (?)" do
|
|
126
|
+
it "parses 'image?' with image" do
|
|
127
|
+
expect(match("image?", "image")).to be true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "parses 'image?' with empty" do
|
|
131
|
+
expect(match("image?", "")).to be true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "rejects 'image?' with multiple images" do
|
|
135
|
+
expect(match("image?", "image image")).to be false
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe "repeated choice with +" do
|
|
140
|
+
it "parses '(heading paragraph+)+' correctly" do
|
|
141
|
+
expect(match("(heading paragraph+)+",
|
|
142
|
+
"heading paragraph heading paragraph paragraph")).to be true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "rejects when extra content at end" do
|
|
146
|
+
expect(match("(heading paragraph+)+",
|
|
147
|
+
"heading paragraph heading paragraph paragraph horizontal_rule")).to be false
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "range expressions" do
|
|
152
|
+
it "parses 'hard_break{2}' with two breaks" do
|
|
153
|
+
expect(match("hard_break{2}", "hard_break hard_break")).to be true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it "rejects 'hard_break{2}' with only one" do
|
|
157
|
+
expect(match("hard_break{2}", "hard_break")).to be false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "rejects 'hard_break{2}' with three" do
|
|
161
|
+
expect(match("hard_break{2}",
|
|
162
|
+
"hard_break hard_break hard_break")).to be false
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "parses 'hard_break{2,4}' with 2-4 breaks" do
|
|
166
|
+
expect(match("hard_break{2,4}", "hard_break hard_break")).to be true
|
|
167
|
+
expect(match("hard_break{2,4}",
|
|
168
|
+
"hard_break hard_break hard_break")).to be true
|
|
169
|
+
expect(match("hard_break{2,4}",
|
|
170
|
+
"hard_break hard_break hard_break hard_break")).to be true
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "rejects 'hard_break{2,4}' with too few" do
|
|
174
|
+
expect(match("hard_break{2,4}", "hard_break")).to be false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "rejects 'hard_break{2,4}' with too many" do
|
|
178
|
+
expect(match("hard_break{2,4}",
|
|
179
|
+
"hard_break hard_break hard_break hard_break hard_break")).to be false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "parses 'hard_break{2,}' (unbounded)" do
|
|
183
|
+
expect(match("hard_break{2,}", "hard_break hard_break")).to be true
|
|
184
|
+
expect(match("hard_break{2,}",
|
|
185
|
+
"hard_break hard_break hard_break hard_break")).to be true
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "rejects 'hard_break{2,}' with only one" do
|
|
189
|
+
expect(match("hard_break{2,}", "hard_break")).to be false
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
describe "mixed expressions" do
|
|
194
|
+
it "parses 'hard_break{2,4} text*'" do
|
|
195
|
+
expect(match("hard_break{2,4} text*", "hard_break hard_break")).to be true
|
|
196
|
+
expect(match("hard_break{2,4} text*",
|
|
197
|
+
"hard_break hard_break text text")).to be true
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "rejects 'hard_break{2,4} text*' with invalid content" do
|
|
201
|
+
expect(match("hard_break{2,4} text*",
|
|
202
|
+
"hard_break hard_break image")).to be false
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "parses 'hard_break{2,4} image?'" do
|
|
206
|
+
expect(match("hard_break{2,4} image?",
|
|
207
|
+
"hard_break hard_break image")).to be true
|
|
208
|
+
expect(match("hard_break{2,4} image?",
|
|
209
|
+
"hard_break hard_break")).to be true
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
describe "edge_count" do
|
|
214
|
+
it "returns number of edges" do
|
|
215
|
+
m = get("paragraph | heading")
|
|
216
|
+
expect(m.edge_count).to eq(2)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
it "returns 0 for empty match" do
|
|
220
|
+
m = get("")
|
|
221
|
+
expect(m.edge_count).to eq(0)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
describe "edge" do
|
|
226
|
+
it "returns edge at index" do
|
|
227
|
+
m = get("paragraph | heading")
|
|
228
|
+
expect(m.edge(0).type.name).to eq("paragraph")
|
|
229
|
+
expect(m.edge(1).type.name).to eq("heading")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it "raises error for out of bounds index" do
|
|
233
|
+
m = get("paragraph")
|
|
234
|
+
expect { m.edge(1) }.to raise_error(Prosereflect::Schema::ContentMatchError)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "conftest"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Prosereflect::Schema::Mark do
|
|
7
|
+
include_context "test_schema"
|
|
8
|
+
|
|
9
|
+
# Use test_schema by default
|
|
10
|
+
let(:schema) { test_schema }
|
|
11
|
+
|
|
12
|
+
describe ".same_set" do
|
|
13
|
+
it "returns true for identical mark sets" do
|
|
14
|
+
em1 = schema.mark_type("em").create
|
|
15
|
+
em2 = schema.mark_type("em").create
|
|
16
|
+
strong = schema.mark_type("strong").create
|
|
17
|
+
|
|
18
|
+
expect(described_class.same_set([em1, strong], [em2, strong])).to be true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "returns false for different mark sets" do
|
|
22
|
+
em = schema.mark_type("em").create
|
|
23
|
+
strong = schema.mark_type("strong").create
|
|
24
|
+
code = schema.mark_type("code").create
|
|
25
|
+
|
|
26
|
+
expect(described_class.same_set([em, strong], [em, code])).to be false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "returns false for sets of different lengths" do
|
|
30
|
+
em = schema.mark_type("em").create
|
|
31
|
+
strong = schema.mark_type("strong").create
|
|
32
|
+
|
|
33
|
+
expect(described_class.same_set([em, strong], [em])).to be false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "returns true for empty sets" do
|
|
37
|
+
expect(described_class.same_set([], [])).to be true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "compares marks with same type but different attrs" do
|
|
41
|
+
link1 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
42
|
+
link2 = schema.mark_type("link").create({ "href" => "http://bar" })
|
|
43
|
+
|
|
44
|
+
expect(described_class.same_set([link1], [link2])).to be false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "compares marks with same type and same attrs" do
|
|
48
|
+
link1 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
49
|
+
link2 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
50
|
+
|
|
51
|
+
expect(described_class.same_set([link1], [link2])).to be true
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe "#eq?" do
|
|
56
|
+
it "returns true for marks with same type and attrs" do
|
|
57
|
+
link1 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
58
|
+
link2 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
59
|
+
|
|
60
|
+
expect(link1.eq?(link2)).to be true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "returns false for marks with different attrs" do
|
|
64
|
+
link1 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
65
|
+
link2 = schema.mark_type("link").create({ "href" => "http://bar" })
|
|
66
|
+
|
|
67
|
+
expect(link1.eq?(link2)).to be false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "returns false for marks with different types" do
|
|
71
|
+
link = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
72
|
+
em = schema.mark_type("em").create
|
|
73
|
+
|
|
74
|
+
expect(link.eq?(em)).to be false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "returns true for same object" do
|
|
78
|
+
link = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
79
|
+
|
|
80
|
+
expect(link.eq?(link)).to be true
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#add_to_set" do
|
|
85
|
+
it "adds mark to empty set" do
|
|
86
|
+
em = schema.mark_type("em").create
|
|
87
|
+
|
|
88
|
+
result = em.add_to_set([])
|
|
89
|
+
|
|
90
|
+
expect(result.length).to eq(1)
|
|
91
|
+
expect(result.first.type.name).to eq("em")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "returns original set if mark already exists" do
|
|
95
|
+
em = schema.mark_type("em").create
|
|
96
|
+
existing_set = [em]
|
|
97
|
+
|
|
98
|
+
result = em.add_to_set(existing_set)
|
|
99
|
+
|
|
100
|
+
expect(result).to equal(existing_set)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "adds mark to set with other marks of different types" do
|
|
104
|
+
em = schema.mark_type("em").create
|
|
105
|
+
strong = schema.mark_type("strong").create
|
|
106
|
+
|
|
107
|
+
result = em.add_to_set([strong])
|
|
108
|
+
|
|
109
|
+
expect(result.length).to eq(2)
|
|
110
|
+
expect(result.map { |x| x.type.name }).to include("em", "strong")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "adds link mark with different href replacing existing link" do
|
|
114
|
+
link1 = schema.mark_type("link").create({ "href" => "http://foo" })
|
|
115
|
+
link2 = schema.mark_type("link").create({ "href" => "http://bar" })
|
|
116
|
+
em = schema.mark_type("em").create
|
|
117
|
+
|
|
118
|
+
result = link2.add_to_set([link1, em])
|
|
119
|
+
|
|
120
|
+
# link2 should replace link1, em should remain
|
|
121
|
+
expect(result.length).to eq(2)
|
|
122
|
+
link_result = result.find { |m| m.type.name == "link" }
|
|
123
|
+
expect(link_result.attrs["href"]).to eq("http://bar")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "maintains rank ordering" do
|
|
127
|
+
em = schema.mark_type("em").create
|
|
128
|
+
strong = schema.mark_type("strong").create
|
|
129
|
+
code = schema.mark_type("code").create
|
|
130
|
+
|
|
131
|
+
# Add marks in different order and verify sorting
|
|
132
|
+
result = code.add_to_set([em, strong])
|
|
133
|
+
|
|
134
|
+
# Should be sorted by rank (em=0, strong=1, code=2)
|
|
135
|
+
expect(result.map { |x| x.type.name }).to eq(["em", "strong", "code"])
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe "#remove_from_set" do
|
|
140
|
+
it "removes mark from set" do
|
|
141
|
+
em = schema.mark_type("em").create
|
|
142
|
+
strong = schema.mark_type("strong").create
|
|
143
|
+
|
|
144
|
+
result = em.remove_from_set([em, strong])
|
|
145
|
+
|
|
146
|
+
expect(result.length).to eq(1)
|
|
147
|
+
expect(result.first.type.name).to eq("strong")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "returns unchanged set if mark not present" do
|
|
151
|
+
em = schema.mark_type("em").create
|
|
152
|
+
strong = schema.mark_type("strong").create
|
|
153
|
+
code = schema.mark_type("code").create
|
|
154
|
+
|
|
155
|
+
result = em.remove_from_set([strong, code])
|
|
156
|
+
|
|
157
|
+
expect(result.length).to eq(2)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "returns empty set when removing only mark" do
|
|
161
|
+
em = schema.mark_type("em").create
|
|
162
|
+
|
|
163
|
+
result = em.remove_from_set([em])
|
|
164
|
+
|
|
165
|
+
expect(result).to be_empty
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
describe "#is_in_set?" do
|
|
170
|
+
it "returns true when mark is in set" do
|
|
171
|
+
em = schema.mark_type("em").create
|
|
172
|
+
strong = schema.mark_type("strong").create
|
|
173
|
+
|
|
174
|
+
expect(em.is_in_set?([em, strong])).to be true
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "returns false when mark is not in set" do
|
|
178
|
+
em = schema.mark_type("em").create
|
|
179
|
+
strong = schema.mark_type("strong").create
|
|
180
|
+
|
|
181
|
+
expect(em.is_in_set?([strong])).to be false
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
context "with custom schema exclusion rules" do
|
|
186
|
+
# Custom schema with link excludes emoji
|
|
187
|
+
let(:custom_schema) do
|
|
188
|
+
Prosereflect::Schema.new(
|
|
189
|
+
nodes_spec: {
|
|
190
|
+
"doc" => { content: "block+" },
|
|
191
|
+
"paragraph" => { content: "inline*", group: "block" },
|
|
192
|
+
"text" => { group: "inline" },
|
|
193
|
+
},
|
|
194
|
+
marks_spec: {
|
|
195
|
+
"bold" => {},
|
|
196
|
+
"italic" => {},
|
|
197
|
+
"link" => { attrs: { "href" => {} }, inclusive: false,
|
|
198
|
+
excludes: "emoji" },
|
|
199
|
+
"emoji" => { group: "mark" },
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
let(:schema) { custom_schema }
|
|
205
|
+
|
|
206
|
+
describe ".same_set" do
|
|
207
|
+
it "returns true for identical mark sets" do
|
|
208
|
+
bold1 = schema.mark_type("bold").create
|
|
209
|
+
bold2 = schema.mark_type("bold").create
|
|
210
|
+
italic = schema.mark_type("italic").create
|
|
211
|
+
|
|
212
|
+
expect(described_class.same_set([bold1, italic], [bold2, italic])).to be true
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
describe "#add_to_set" do
|
|
217
|
+
it "link excludes emoji - emoji removed when link added" do
|
|
218
|
+
# link excludes emoji in this schema
|
|
219
|
+
link = schema.mark_type("link").create({ "href" => "http://example" })
|
|
220
|
+
emoji = schema.mark_type("emoji").create
|
|
221
|
+
|
|
222
|
+
result = link.add_to_set([emoji])
|
|
223
|
+
|
|
224
|
+
# emoji should be removed because link excludes emoji
|
|
225
|
+
expect(result.map { |x| x.type.name }).not_to include("emoji")
|
|
226
|
+
expect(result.map { |x| x.type.name }).to include("link")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it "link can be added to empty set" do
|
|
230
|
+
link = schema.mark_type("link").create({ "href" => "http://example" })
|
|
231
|
+
|
|
232
|
+
result = link.add_to_set([])
|
|
233
|
+
|
|
234
|
+
expect(result.length).to eq(1)
|
|
235
|
+
expect(result.first.type.name).to eq("link")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it "emoji does not exclude link but link excludes emoji" do
|
|
239
|
+
# emoji has no excludes, but link excludes emoji
|
|
240
|
+
# So when we try to add emoji to [link], it should fail
|
|
241
|
+
# because link excludes emoji
|
|
242
|
+
link = schema.mark_type("link").create({ "href" => "http://example" })
|
|
243
|
+
emoji = schema.mark_type("emoji").create
|
|
244
|
+
|
|
245
|
+
result = emoji.add_to_set([link])
|
|
246
|
+
|
|
247
|
+
# link already in set, and link excludes emoji, so emoji not added
|
|
248
|
+
expect(result.map { |x| x.type.name }).to eq(["link"])
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
describe "bold and italic coexist" do
|
|
253
|
+
it "bold and italic can coexist in a set" do
|
|
254
|
+
bold = schema.mark_type("bold").create
|
|
255
|
+
italic = schema.mark_type("italic").create
|
|
256
|
+
|
|
257
|
+
result = bold.add_to_set([italic])
|
|
258
|
+
|
|
259
|
+
expect(result.length).to eq(2)
|
|
260
|
+
expect(result.map { |x| x.type.name }).to include("bold", "italic")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
it "italic and bold can coexist in a set" do
|
|
264
|
+
bold = schema.mark_type("bold").create
|
|
265
|
+
italic = schema.mark_type("italic").create
|
|
266
|
+
|
|
267
|
+
result = italic.add_to_set([bold])
|
|
268
|
+
|
|
269
|
+
expect(result.length).to eq(2)
|
|
270
|
+
expect(result.map { |x| x.type.name }).to include("bold", "italic")
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
require_relative "conftest"
|
|
5
|
+
|
|
6
|
+
RSpec.describe Prosereflect::Schema::MarkType do
|
|
7
|
+
include_context "test_schema"
|
|
8
|
+
|
|
9
|
+
describe "create" do
|
|
10
|
+
it "creates a mark with attrs" do
|
|
11
|
+
mark = schema.mark_type("link").create({ "href" => "http://example.com" })
|
|
12
|
+
expect(mark.attrs["href"]).to eq("http://example.com")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "uses default instance when no attrs provided" do
|
|
16
|
+
mark_type = schema.mark_type("link")
|
|
17
|
+
mark = mark_type.create(nil)
|
|
18
|
+
# The instance should have the default title
|
|
19
|
+
expect(mark.type).to eq(mark_type)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "remove_from_set" do
|
|
24
|
+
it "removes mark from set" do
|
|
25
|
+
mark_type = schema.mark_type("em")
|
|
26
|
+
mark = mark_type.create
|
|
27
|
+
mark_set = [mark]
|
|
28
|
+
|
|
29
|
+
result = mark_type.remove_from_set(mark_set)
|
|
30
|
+
expect(result).to be_empty
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "keeps other marks" do
|
|
34
|
+
em_mark = schema.mark_type("em").create
|
|
35
|
+
strong_mark = schema.mark_type("strong").create
|
|
36
|
+
mark_set = [em_mark, strong_mark]
|
|
37
|
+
|
|
38
|
+
result = schema.mark_type("em").remove_from_set(mark_set)
|
|
39
|
+
expect(result).to eq([strong_mark])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "is_in_set?" do
|
|
44
|
+
it "returns true when mark is in set" do
|
|
45
|
+
mark = schema.mark_type("em").create
|
|
46
|
+
mark_set = [mark]
|
|
47
|
+
|
|
48
|
+
expect(schema.mark_type("em").is_in_set?(mark_set)).to be true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "returns false when mark is not in set" do
|
|
52
|
+
mark = schema.mark_type("em").create
|
|
53
|
+
mark_set = [mark]
|
|
54
|
+
|
|
55
|
+
expect(schema.mark_type("strong").is_in_set?(mark_set)).to be false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe "excludes?" do
|
|
60
|
+
it "returns false by default" do
|
|
61
|
+
expect(schema.mark_type("em").excludes?(schema.mark_type("strong"))).to be false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "with custom_schema (exclusions)" do
|
|
66
|
+
include_context "custom_schema"
|
|
67
|
+
|
|
68
|
+
it "link excludes emoji" do
|
|
69
|
+
expect(custom_schema.mark_type("link").excludes?(custom_schema.mark_type("emoji"))).to be true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "emoji does not exclude link" do
|
|
73
|
+
expect(custom_schema.mark_type("emoji").excludes?(custom_schema.mark_type("link"))).to be false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe "inclusive?" do
|
|
78
|
+
it "returns false for link (non-inclusive)" do
|
|
79
|
+
expect(schema.mark_type("link").inclusive?).to be false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "returns true for em (inclusive by default)" do
|
|
83
|
+
expect(schema.mark_type("em").inclusive?).to be true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|