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,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Prosereflect::Transform::ReplaceStep do
|
|
6
|
+
describe "creation" do
|
|
7
|
+
it "creates replace step with from, to, and slice" do
|
|
8
|
+
slice = Prosereflect::Transform::Slice.empty
|
|
9
|
+
step = described_class.new(0, 5, slice)
|
|
10
|
+
expect(step.from).to eq(0)
|
|
11
|
+
expect(step.to).to eq(5)
|
|
12
|
+
expect(step.slice).to eq(slice)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "creates replace step with default empty slice" do
|
|
16
|
+
step = described_class.new(0, 5)
|
|
17
|
+
expect(step.slice).to eq(Prosereflect::Transform::Slice.empty)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "get_map" do
|
|
22
|
+
it "returns step map for the replacement" do
|
|
23
|
+
step = described_class.new(2, 5, Prosereflect::Transform::Slice.empty)
|
|
24
|
+
step_map = step.get_map
|
|
25
|
+
expect(step_map).to be_a(Prosereflect::Transform::StepMap)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "step_type" do
|
|
30
|
+
it "returns replace" do
|
|
31
|
+
step = described_class.new(0, 5)
|
|
32
|
+
expect(step.step_type).to eq("replace")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "merge" do
|
|
37
|
+
it "merges adjacent empty deletions" do
|
|
38
|
+
# step1: delete 3-4
|
|
39
|
+
step1 = described_class.new(3, 4, Prosereflect::Transform::Slice.empty)
|
|
40
|
+
# step2: delete 4-5 (adjacent - step1's end equals step2's start)
|
|
41
|
+
step2 = described_class.new(4, 5, Prosereflect::Transform::Slice.empty)
|
|
42
|
+
|
|
43
|
+
merged = step1.merge(step2)
|
|
44
|
+
expect(merged).not_to be_nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "does not merge overlapping deletions" do
|
|
48
|
+
# step1: delete 1-3
|
|
49
|
+
step1 = described_class.new(1, 3, Prosereflect::Transform::Slice.empty)
|
|
50
|
+
# step2: delete 2-4 (overlaps with step1)
|
|
51
|
+
step2 = described_class.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
52
|
+
|
|
53
|
+
merged = step1.merge(step2)
|
|
54
|
+
expect(merged).to be_nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "merges adjacent deletions extending backward" do
|
|
58
|
+
# step1: delete 3-4
|
|
59
|
+
step1 = described_class.new(3, 4, Prosereflect::Transform::Slice.empty)
|
|
60
|
+
# step2: delete 2-3 (extends backward from step1)
|
|
61
|
+
step2 = described_class.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
62
|
+
|
|
63
|
+
merged = step2.merge(step1)
|
|
64
|
+
expect(merged).not_to be_nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "does not merge far apart steps" do
|
|
68
|
+
step1 = described_class.new(1, 2, Prosereflect::Transform::Slice.empty)
|
|
69
|
+
step2 = described_class.new(5, 6, Prosereflect::Transform::Slice.empty)
|
|
70
|
+
|
|
71
|
+
merged = step1.merge(step2)
|
|
72
|
+
expect(merged).to be_nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "can_extend_deletion?" do
|
|
77
|
+
it "returns true when other starts at this end with empty slice" do
|
|
78
|
+
step1 = described_class.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
79
|
+
step2 = described_class.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
80
|
+
expect(step1.can_extend_deletion?(step2)).to be true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "returns false when other does not start at this end" do
|
|
84
|
+
step1 = described_class.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
85
|
+
step2 = described_class.new(5, 7, Prosereflect::Transform::Slice.empty)
|
|
86
|
+
expect(step1.can_extend_deletion?(step2)).to be false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "returns false when self.slice is not empty" do
|
|
90
|
+
content = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")])
|
|
91
|
+
step1 = described_class.new(2, 4, Prosereflect::Transform::Slice.new(content))
|
|
92
|
+
step2 = described_class.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
93
|
+
expect(step1.can_extend_deletion?(step2)).to be false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "can_prepend_deletion?" do
|
|
98
|
+
it "returns true when other ends at this start with empty slice" do
|
|
99
|
+
step1 = described_class.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
100
|
+
step2 = described_class.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
101
|
+
expect(step1.can_prepend_deletion?(step2)).to be true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "returns false when other does not end at this start" do
|
|
105
|
+
step1 = described_class.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
106
|
+
step2 = described_class.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
107
|
+
expect(step1.can_prepend_deletion?(step2)).to be false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "can_append_content?" do
|
|
112
|
+
it "returns true when other starts at this end with content" do
|
|
113
|
+
step1 = described_class.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
114
|
+
content = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")])
|
|
115
|
+
step2 = described_class.new(3, 3, Prosereflect::Transform::Slice.new(content))
|
|
116
|
+
expect(step1.can_append_content?(step2)).to be true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "returns false when other starts at different position" do
|
|
120
|
+
step1 = described_class.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
121
|
+
content = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")])
|
|
122
|
+
step2 = described_class.new(4, 4, Prosereflect::Transform::Slice.new(content))
|
|
123
|
+
expect(step1.can_append_content?(step2)).to be false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it "returns false when other has empty slice" do
|
|
127
|
+
step1 = described_class.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
128
|
+
step2 = described_class.new(3, 3, Prosereflect::Transform::Slice.empty)
|
|
129
|
+
expect(step1.can_append_content?(step2)).to be false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe "can_prepend_content?" do
|
|
134
|
+
it "returns true when other ends at this start with content" do
|
|
135
|
+
content1 = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "y")])
|
|
136
|
+
step1 = described_class.new(3, 3, Prosereflect::Transform::Slice.new(content1))
|
|
137
|
+
content2 = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")])
|
|
138
|
+
step2 = described_class.new(1, 3, Prosereflect::Transform::Slice.new(content2))
|
|
139
|
+
expect(step1.can_prepend_content?(step2)).to be true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it "returns false when other.slice is empty" do
|
|
143
|
+
content = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "y")])
|
|
144
|
+
step1 = described_class.new(3, 3, Prosereflect::Transform::Slice.new(content))
|
|
145
|
+
step2 = described_class.new(1, 3, Prosereflect::Transform::Slice.empty)
|
|
146
|
+
expect(step1.can_prepend_content?(step2)).to be false
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "returns false when other ends at different position" do
|
|
150
|
+
content = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "y")])
|
|
151
|
+
step1 = described_class.new(3, 3, Prosereflect::Transform::Slice.new(content))
|
|
152
|
+
content2 = Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")])
|
|
153
|
+
step2 = described_class.new(1, 2, Prosereflect::Transform::Slice.new(content2))
|
|
154
|
+
expect(step1.can_prepend_content?(step2)).to be false
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Prosereflect::Transform::Slice do
|
|
6
|
+
let(:content) { Prosereflect::Fragment.new([]) }
|
|
7
|
+
|
|
8
|
+
describe "creation" do
|
|
9
|
+
it "creates empty slice" do
|
|
10
|
+
slice = described_class.new(content)
|
|
11
|
+
expect(slice.empty?).to be true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "creates slice with open boundaries" do
|
|
15
|
+
slice = described_class.new(content, 1, 1)
|
|
16
|
+
expect(slice.open_start).to eq(1)
|
|
17
|
+
expect(slice.open_end).to eq(1)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe "empty?" do
|
|
22
|
+
it "returns true for empty slice" do
|
|
23
|
+
expect(described_class.empty.empty?).to be true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe "size" do
|
|
28
|
+
it "returns 0 for empty slice" do
|
|
29
|
+
expect(described_class.empty.size).to eq(0)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe "cut" do
|
|
34
|
+
it "returns same slice when cutting full range" do
|
|
35
|
+
slice = described_class.new(content)
|
|
36
|
+
result = slice.cut(0, slice.size)
|
|
37
|
+
expect(result.size).to eq(slice.size)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "equality" do
|
|
42
|
+
it "compares equal slices" do
|
|
43
|
+
slice1 = described_class.new(content)
|
|
44
|
+
slice2 = described_class.new(content)
|
|
45
|
+
expect(slice1).to eq(slice2)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Prosereflect::Transform::StepMap do
|
|
6
|
+
describe "creation" do
|
|
7
|
+
it "creates an empty step map" do
|
|
8
|
+
map = described_class.empty
|
|
9
|
+
expect(map.ranges).to eq([])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "creates a delete step map" do
|
|
13
|
+
map = described_class.delete(5, 10)
|
|
14
|
+
expect(map.ranges).to eq([[5, 10, 5, 5]])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "creates a replace step map" do
|
|
18
|
+
map = described_class.replace(5, 10, 5, 15)
|
|
19
|
+
expect(map.ranges).to eq([[5, 10, 5, 15]])
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "map" do
|
|
24
|
+
it "maps position before any range unchanged" do
|
|
25
|
+
map = described_class.new([[5, 10, 5, 5]])
|
|
26
|
+
expect(map.map(3)).to eq(3)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "maps position before range with offset" do
|
|
30
|
+
map = described_class.new([[5, 10, 3, 3]])
|
|
31
|
+
expect(map.map(3)).to eq(1)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "maps position inside range" do
|
|
35
|
+
map = described_class.new([[5, 10, 3, 3]])
|
|
36
|
+
expect(map.map(7)).to eq(5)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "maps position after range with offset" do
|
|
40
|
+
map = described_class.new([[5, 10, 3, 3]])
|
|
41
|
+
expect(map.map(15)).to eq(8)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "deleted?" do
|
|
46
|
+
it "returns false for position before range" do
|
|
47
|
+
map = described_class.new([[5, 10, 5, 5]])
|
|
48
|
+
expect(map.deleted?(3)).to be false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "returns true for position inside range" do
|
|
52
|
+
map = described_class.new([[5, 10, 5, 5]])
|
|
53
|
+
expect(map.deleted?(7)).to be true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "returns false for position after range" do
|
|
57
|
+
map = described_class.new([[5, 10, 5, 5]])
|
|
58
|
+
expect(map.deleted?(15)).to be false
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe "add_map" do
|
|
63
|
+
it "combines two independent maps" do
|
|
64
|
+
map1 = described_class.new([[0, 5, 0, 5]])
|
|
65
|
+
map2 = described_class.new([[10, 15, 10, 15]])
|
|
66
|
+
result = map1.add_map(map2)
|
|
67
|
+
expect(result.ranges.length).to eq(2)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Prosereflect::Transform::Step do
|
|
6
|
+
describe "creation" do
|
|
7
|
+
it "can be instantiated" do
|
|
8
|
+
step = described_class.new
|
|
9
|
+
expect(step).to be_a(described_class)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "merge" do
|
|
14
|
+
it "merges adjacent empty deletions extending backward" do
|
|
15
|
+
# step1: delete 3-4 (empty deletion at position 3)
|
|
16
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
17
|
+
3, 4,
|
|
18
|
+
Prosereflect::Transform::Slice.empty
|
|
19
|
+
)
|
|
20
|
+
# step2: delete 2-3 (empty deletion at position 2)
|
|
21
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
22
|
+
2, 3,
|
|
23
|
+
Prosereflect::Transform::Slice.empty
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# step1's end (4) equals step2's start (2)? No, 4 != 2
|
|
27
|
+
# step1's start (3) equals step2's end (3)? Yes!
|
|
28
|
+
# can_prepend_deletion: other.to == @from && other.slice.empty?
|
|
29
|
+
# 3 == 3 && true = true
|
|
30
|
+
merged = step2.merge(step1)
|
|
31
|
+
expect(merged).not_to be_nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "does not merge steps that are far apart" do
|
|
35
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
36
|
+
2, 2,
|
|
37
|
+
Prosereflect::Transform::Slice.new(
|
|
38
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "a")]),
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
# step2 is at position 4, far from step1 at position 2
|
|
42
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
43
|
+
4, 4,
|
|
44
|
+
Prosereflect::Transform::Slice.new(
|
|
45
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "b")]),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
merged = step1.merge(step2)
|
|
50
|
+
expect(merged).to be_nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "merges adjacent empty deletions" do
|
|
54
|
+
# step1: delete 3-4
|
|
55
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
56
|
+
3, 4,
|
|
57
|
+
Prosereflect::Transform::Slice.empty
|
|
58
|
+
)
|
|
59
|
+
# step2: delete 4-5 (adjacent - step1's end equals step2's start)
|
|
60
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
61
|
+
4, 5,
|
|
62
|
+
Prosereflect::Transform::Slice.empty
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# can_extend_deletion: @to == other.from && @slice.empty?
|
|
66
|
+
# 4 == 4 && true = true
|
|
67
|
+
merged = step1.merge(step2)
|
|
68
|
+
expect(merged).not_to be_nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "does not merge overlapping deletions" do
|
|
72
|
+
# step1: delete 1-3
|
|
73
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
74
|
+
1, 3,
|
|
75
|
+
Prosereflect::Transform::Slice.empty
|
|
76
|
+
)
|
|
77
|
+
# step2: delete 2-4 (overlaps with step1)
|
|
78
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
79
|
+
2, 4,
|
|
80
|
+
Prosereflect::Transform::Slice.empty
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
merged = step1.merge(step2)
|
|
84
|
+
expect(merged).to be_nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "appends content when second step starts at first's end" do
|
|
88
|
+
# step1: replace 2-3 with empty
|
|
89
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
90
|
+
2, 3,
|
|
91
|
+
Prosereflect::Transform::Slice.empty
|
|
92
|
+
)
|
|
93
|
+
# step2: insert "x" at position 3 (step1's end)
|
|
94
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
95
|
+
3, 3,
|
|
96
|
+
Prosereflect::Transform::Slice.new(
|
|
97
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")]),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# can_append_content: @to == other.from && !other.slice.empty?
|
|
102
|
+
merged = step1.merge(step2)
|
|
103
|
+
expect(merged).not_to be_nil
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
context "with ReplaceStep" do
|
|
108
|
+
describe "creation" do
|
|
109
|
+
it "creates replace step with from, to, and slice" do
|
|
110
|
+
slice = Prosereflect::Transform::Slice.empty
|
|
111
|
+
step = Prosereflect::Transform::ReplaceStep.new(0, 5, slice)
|
|
112
|
+
expect(step.from).to eq(0)
|
|
113
|
+
expect(step.to).to eq(5)
|
|
114
|
+
expect(step.slice).to eq(slice)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "creates replace step with default empty slice" do
|
|
118
|
+
step = Prosereflect::Transform::ReplaceStep.new(0, 5)
|
|
119
|
+
expect(step.slice).to eq(Prosereflect::Transform::Slice.empty)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "get_map" do
|
|
124
|
+
it "returns step map for the replacement" do
|
|
125
|
+
step = Prosereflect::Transform::ReplaceStep.new(2, 5, Prosereflect::Transform::Slice.empty)
|
|
126
|
+
step_map = step.get_map
|
|
127
|
+
expect(step_map).to be_a(Prosereflect::Transform::StepMap)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "step_type" do
|
|
132
|
+
it "returns replace" do
|
|
133
|
+
step = Prosereflect::Transform::ReplaceStep.new(0, 5)
|
|
134
|
+
expect(step.step_type).to eq("replace")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe "can_extend_deletion?" do
|
|
139
|
+
it "returns true when other starts at this end with empty slice" do
|
|
140
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
141
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
142
|
+
expect(step1.can_extend_deletion?(step2)).to be true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "returns false when other does not start at this end" do
|
|
146
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
147
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(5, 7, Prosereflect::Transform::Slice.empty)
|
|
148
|
+
expect(step1.can_extend_deletion?(step2)).to be false
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe "can_prepend_deletion?" do
|
|
153
|
+
it "returns true when other ends at this start with empty slice" do
|
|
154
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
155
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(2, 4, Prosereflect::Transform::Slice.empty)
|
|
156
|
+
expect(step1.can_prepend_deletion?(step2)).to be true
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "returns false when other does not end at this start" do
|
|
160
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(4, 6, Prosereflect::Transform::Slice.empty)
|
|
161
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
162
|
+
expect(step1.can_prepend_deletion?(step2)).to be false
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe "can_append_content?" do
|
|
167
|
+
it "returns true when other starts at this end with content" do
|
|
168
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(2, 3, Prosereflect::Transform::Slice.empty)
|
|
169
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
170
|
+
3, 3,
|
|
171
|
+
Prosereflect::Transform::Slice.new(
|
|
172
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")]),
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
expect(step1.can_append_content?(step2)).to be true
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
describe "can_prepend_content?" do
|
|
180
|
+
it "returns true when other ends at this start with content" do
|
|
181
|
+
# step1: insert "y" at position 3 (start=3, end=3)
|
|
182
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
183
|
+
3, 3,
|
|
184
|
+
Prosereflect::Transform::Slice.new(
|
|
185
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "y")]),
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
# step2: insert "x" at position 1-3 (ends at position 3 where step1 starts)
|
|
189
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(
|
|
190
|
+
1, 3,
|
|
191
|
+
Prosereflect::Transform::Slice.new(
|
|
192
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "x")]),
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
expect(step1.can_prepend_content?(step2)).to be true
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it "returns false when other.slice is empty" do
|
|
199
|
+
step1 = Prosereflect::Transform::ReplaceStep.new(
|
|
200
|
+
3, 3,
|
|
201
|
+
Prosereflect::Transform::Slice.new(
|
|
202
|
+
Prosereflect::Fragment.new([Prosereflect::Text.new(text: "y")]),
|
|
203
|
+
)
|
|
204
|
+
)
|
|
205
|
+
# step2 has empty slice
|
|
206
|
+
step2 = Prosereflect::Transform::ReplaceStep.new(1, 3, Prosereflect::Transform::Slice.empty)
|
|
207
|
+
expect(step1.can_prepend_content?(step2)).to be false
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Prosereflect::Transform::Structure do
|
|
6
|
+
let(:doc) do
|
|
7
|
+
Prosereflect::Parser.parse_document({
|
|
8
|
+
"type" => "doc",
|
|
9
|
+
"content" => [
|
|
10
|
+
{
|
|
11
|
+
"type" => "paragraph",
|
|
12
|
+
"content" => [
|
|
13
|
+
{ "type" => "text", "text" => "Hello" },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"type" => "paragraph",
|
|
18
|
+
"content" => [
|
|
19
|
+
{ "type" => "text", "text" => "World" },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
})
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe ".can_split?" do
|
|
27
|
+
it "returns false for negative position" do
|
|
28
|
+
expect(described_class.can_split?(doc, -1)).to be false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "returns false for position beyond document" do
|
|
32
|
+
expect(described_class.can_split?(doc, 999)).to be false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "returns true for valid position" do
|
|
36
|
+
expect(described_class.can_split?(doc, 1)).to be true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "returns true for position 0" do
|
|
40
|
+
expect(described_class.can_split?(doc, 0)).to be true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe ".can_join?" do
|
|
45
|
+
it "returns false for position 0" do
|
|
46
|
+
expect(described_class.can_join?(doc, 0)).to be false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "returns false for position at end" do
|
|
50
|
+
expect(described_class.can_join?(doc, doc.node_size)).to be false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "returns true for position between paragraphs" do
|
|
54
|
+
# Position 8 is between the two paragraphs (after "Hello" para, before "World" para)
|
|
55
|
+
expect(described_class.can_join?(doc, 8)).to be true
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe ".join_point?" do
|
|
60
|
+
it "returns nil for position at start" do
|
|
61
|
+
expect(described_class.join_point?(doc, 0)).to be false
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "returns nil at position within a paragraph" do
|
|
65
|
+
expect(described_class.join_point?(doc, 3)).to be false
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe ".lift_target" do
|
|
70
|
+
it "returns 0 for empty fragment" do
|
|
71
|
+
fragment = Prosereflect::Fragment.new([])
|
|
72
|
+
result = described_class.lift_target(fragment, 0, 5)
|
|
73
|
+
expect(result).to eq(0)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "returns 0 for fragment with no defining nodes" do
|
|
77
|
+
text = Prosereflect::Text.new(text: "hello")
|
|
78
|
+
fragment = Prosereflect::Fragment.new([text])
|
|
79
|
+
result = described_class.lift_target(fragment, 0, 6)
|
|
80
|
+
expect(result).to eq(0)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe ".find_wrapping" do
|
|
85
|
+
it "returns empty array for empty fragment" do
|
|
86
|
+
fragment = Prosereflect::Fragment.new([])
|
|
87
|
+
wrappers = described_class.find_wrapping(fragment, 0, 5, "paragraph")
|
|
88
|
+
expect(wrappers).to be_a(Array)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "returns empty array for fragment with no defining nodes" do
|
|
92
|
+
text = Prosereflect::Text.new(text: "hello")
|
|
93
|
+
fragment = Prosereflect::Fragment.new([text])
|
|
94
|
+
wrappers = described_class.find_wrapping(fragment, 0, 6, "paragraph")
|
|
95
|
+
expect(wrappers).to eq([])
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|