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,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
|
|
@@ -1,274 +1,275 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "spec_helper"
|
|
4
4
|
|
|
5
5
|
RSpec.describe Prosereflect::Text do
|
|
6
|
-
describe
|
|
7
|
-
it
|
|
8
|
-
text = described_class.new({
|
|
9
|
-
expect(text.type).to eq(
|
|
10
|
-
expect(text.text).to eq(
|
|
6
|
+
describe "initialization" do
|
|
7
|
+
it "initializes as a text node" do
|
|
8
|
+
text = described_class.new({ "type" => "text", "text" => "Hello" })
|
|
9
|
+
expect(text.type).to eq("text")
|
|
10
|
+
expect(text.text).to eq("Hello")
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
it
|
|
14
|
-
text = described_class.new({
|
|
15
|
-
expect(text.text).to eq(
|
|
13
|
+
it "initializes with empty text" do
|
|
14
|
+
text = described_class.new({ "type" => "text" })
|
|
15
|
+
expect(text.text).to eq("")
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
it
|
|
18
|
+
it "initializes with marks" do
|
|
19
19
|
marks = [
|
|
20
20
|
Prosereflect::Mark::Bold.new,
|
|
21
|
-
Prosereflect::Mark::Italic.new
|
|
21
|
+
Prosereflect::Mark::Italic.new,
|
|
22
22
|
]
|
|
23
|
-
text = described_class.new(text:
|
|
23
|
+
text = described_class.new(text: "Hello", marks: marks)
|
|
24
24
|
|
|
25
25
|
expect(text.raw_marks).to eq(marks)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
describe
|
|
30
|
-
it
|
|
31
|
-
text = described_class.create(
|
|
29
|
+
describe ".create" do
|
|
30
|
+
it "creates a simple text node" do
|
|
31
|
+
text = described_class.create("Hello world")
|
|
32
32
|
|
|
33
33
|
expected = {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
"type" => "text",
|
|
35
|
+
"text" => "Hello world",
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
expect(text.to_h).to eq(expected)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
it
|
|
42
|
-
text = described_class.create(
|
|
41
|
+
it "creates a text node with marks" do
|
|
42
|
+
text = described_class.create("Formatted text", [
|
|
43
43
|
Prosereflect::Mark::Bold.new,
|
|
44
|
-
Prosereflect::Mark::Italic.new
|
|
44
|
+
Prosereflect::Mark::Italic.new,
|
|
45
45
|
])
|
|
46
46
|
|
|
47
47
|
expected = {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
{
|
|
53
|
-
]
|
|
48
|
+
"type" => "text",
|
|
49
|
+
"text" => "Formatted text",
|
|
50
|
+
"marks" => [
|
|
51
|
+
{ "type" => "bold" },
|
|
52
|
+
{ "type" => "italic" },
|
|
53
|
+
],
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
expect(text.to_h).to eq(expected)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
it
|
|
60
|
-
text = described_class.create(
|
|
61
|
-
Prosereflect::Mark::Link.new(attrs: {
|
|
62
|
-
|
|
59
|
+
it "creates a text node with marks and attributes" do
|
|
60
|
+
text = described_class.create("Link text", [
|
|
61
|
+
Prosereflect::Mark::Link.new(attrs: { "href" => "https://example.com",
|
|
62
|
+
"title" => "Example" }),
|
|
63
|
+
Prosereflect::Mark::Bold.new,
|
|
63
64
|
])
|
|
64
65
|
|
|
65
66
|
expected = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
"type" => "text",
|
|
68
|
+
"text" => "Link text",
|
|
69
|
+
"marks" => [
|
|
69
70
|
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
71
|
+
"type" => "link",
|
|
72
|
+
"attrs" => {
|
|
73
|
+
"href" => "https://example.com",
|
|
74
|
+
"title" => "Example",
|
|
75
|
+
},
|
|
75
76
|
},
|
|
76
|
-
{
|
|
77
|
-
]
|
|
77
|
+
{ "type" => "bold" },
|
|
78
|
+
],
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
expect(text.to_h).to eq(expected)
|
|
81
82
|
end
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
describe
|
|
85
|
-
it
|
|
86
|
-
text = described_class.create(
|
|
85
|
+
describe "text structure" do
|
|
86
|
+
it "creates text with special characters" do
|
|
87
|
+
text = described_class.create("Special chars: →, ←, ©, ®, ™")
|
|
87
88
|
|
|
88
89
|
expected = {
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
"type" => "text",
|
|
91
|
+
"text" => "Special chars: →, ←, ©, ®, ™",
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
expect(text.to_h).to eq(expected)
|
|
94
95
|
end
|
|
95
96
|
|
|
96
|
-
it
|
|
97
|
-
text = described_class.create(
|
|
97
|
+
it "creates text with multiple marks and attributes" do
|
|
98
|
+
text = described_class.create("Complex text", [
|
|
98
99
|
Prosereflect::Mark::Bold.new,
|
|
99
100
|
Prosereflect::Mark::Italic.new,
|
|
100
101
|
Prosereflect::Mark::Strike.new,
|
|
101
|
-
Prosereflect::Mark::Link.new(attrs: {
|
|
102
|
-
Prosereflect::Mark::Underline.new
|
|
102
|
+
Prosereflect::Mark::Link.new(attrs: { "href" => "https://example.com" }),
|
|
103
|
+
Prosereflect::Mark::Underline.new,
|
|
103
104
|
])
|
|
104
105
|
|
|
105
106
|
expected = {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
{
|
|
111
|
-
{
|
|
107
|
+
"type" => "text",
|
|
108
|
+
"text" => "Complex text",
|
|
109
|
+
"marks" => [
|
|
110
|
+
{ "type" => "bold" },
|
|
111
|
+
{ "type" => "italic" },
|
|
112
|
+
{ "type" => "strike" },
|
|
112
113
|
{
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
114
|
+
"type" => "link",
|
|
115
|
+
"attrs" => {
|
|
116
|
+
"href" => "https://example.com",
|
|
117
|
+
},
|
|
117
118
|
},
|
|
118
|
-
{
|
|
119
|
-
]
|
|
119
|
+
{ "type" => "underline" },
|
|
120
|
+
],
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
expect(text.to_h).to eq(expected)
|
|
123
124
|
end
|
|
124
125
|
end
|
|
125
126
|
|
|
126
|
-
describe
|
|
127
|
-
describe
|
|
128
|
-
it
|
|
129
|
-
text = described_class.create(
|
|
127
|
+
describe "text operations" do
|
|
128
|
+
describe "#text_content" do
|
|
129
|
+
it "returns the text content regardless of marks" do
|
|
130
|
+
text = described_class.create("Sample text", [
|
|
130
131
|
Prosereflect::Mark::Bold.new,
|
|
131
|
-
Prosereflect::Mark::Italic.new
|
|
132
|
+
Prosereflect::Mark::Italic.new,
|
|
132
133
|
])
|
|
133
134
|
|
|
134
|
-
expect(text.text_content).to eq(
|
|
135
|
+
expect(text.text_content).to eq("Sample text")
|
|
135
136
|
end
|
|
136
137
|
|
|
137
|
-
it
|
|
138
|
-
text = described_class.create(
|
|
139
|
-
expect(text.text_content).to eq(
|
|
138
|
+
it "returns empty string for empty text" do
|
|
139
|
+
text = described_class.create("")
|
|
140
|
+
expect(text.text_content).to eq("")
|
|
140
141
|
end
|
|
141
142
|
|
|
142
|
-
it
|
|
143
|
+
it "preserves whitespace and special characters" do
|
|
143
144
|
text = described_class.create(" Multiple spaces \t\nand\ttabs ")
|
|
144
145
|
expect(text.text_content).to eq(" Multiple spaces \t\nand\ttabs ")
|
|
145
146
|
end
|
|
146
147
|
end
|
|
147
148
|
|
|
148
|
-
describe
|
|
149
|
-
it
|
|
150
|
-
text = described_class.new(text:
|
|
149
|
+
describe "#marks" do
|
|
150
|
+
it "preserves mark order" do
|
|
151
|
+
text = described_class.new(text: "Hello")
|
|
151
152
|
marks = [
|
|
152
153
|
Prosereflect::Mark::Bold.new,
|
|
153
154
|
Prosereflect::Mark::Italic.new,
|
|
154
|
-
Prosereflect::Mark::Strike.new
|
|
155
|
+
Prosereflect::Mark::Strike.new,
|
|
155
156
|
]
|
|
156
157
|
text.marks = marks
|
|
157
158
|
|
|
158
159
|
expect(text.raw_marks.map(&:type)).to eq(%w[bold italic strike])
|
|
159
160
|
end
|
|
160
161
|
|
|
161
|
-
it
|
|
162
|
-
text = described_class.create(
|
|
162
|
+
it "handles empty marks array" do
|
|
163
|
+
text = described_class.create("No marks", [])
|
|
163
164
|
expect(text.marks).to eq([])
|
|
164
|
-
expect(text.to_h).not_to have_key(
|
|
165
|
+
expect(text.to_h).not_to have_key("marks")
|
|
165
166
|
end
|
|
166
167
|
|
|
167
|
-
it
|
|
168
|
-
text = described_class.create(
|
|
168
|
+
it "handles nil marks" do
|
|
169
|
+
text = described_class.create("No marks", nil)
|
|
169
170
|
expect(text.marks).to be_nil
|
|
170
|
-
expect(text.to_h).not_to have_key(
|
|
171
|
+
expect(text.to_h).not_to have_key("marks")
|
|
171
172
|
end
|
|
172
173
|
end
|
|
173
174
|
end
|
|
174
175
|
|
|
175
|
-
describe
|
|
176
|
-
it
|
|
177
|
-
text = described_class.create(
|
|
176
|
+
describe "mark attributes" do
|
|
177
|
+
it "preserves mark attributes in serialization" do
|
|
178
|
+
text = described_class.create("Styled text", [
|
|
178
179
|
Prosereflect::Mark::Link.new(attrs: {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
"href" => "https://example.com",
|
|
181
|
+
"title" => "Example Link",
|
|
182
|
+
"target" => "_blank",
|
|
182
183
|
}),
|
|
183
184
|
Prosereflect::Mark::Bold.new,
|
|
184
|
-
Prosereflect::Mark::Italic.new
|
|
185
|
+
Prosereflect::Mark::Italic.new,
|
|
185
186
|
])
|
|
186
187
|
|
|
187
188
|
expected = {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
"type" => "text",
|
|
190
|
+
"text" => "Styled text",
|
|
191
|
+
"marks" => [
|
|
191
192
|
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
193
|
+
"type" => "link",
|
|
194
|
+
"attrs" => {
|
|
195
|
+
"href" => "https://example.com",
|
|
196
|
+
"title" => "Example Link",
|
|
197
|
+
"target" => "_blank",
|
|
198
|
+
},
|
|
198
199
|
},
|
|
199
|
-
{
|
|
200
|
-
{
|
|
201
|
-
]
|
|
200
|
+
{ "type" => "bold" },
|
|
201
|
+
{ "type" => "italic" },
|
|
202
|
+
],
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
expect(text.to_h).to eq(expected)
|
|
205
206
|
end
|
|
206
207
|
|
|
207
|
-
it
|
|
208
|
-
text = described_class.create(
|
|
208
|
+
it "handles marks with empty attributes" do
|
|
209
|
+
text = described_class.create("Test text", [
|
|
209
210
|
Prosereflect::Mark::Link.new(attrs: {}),
|
|
210
211
|
Prosereflect::Mark::Bold.new,
|
|
211
|
-
Prosereflect::Mark::Italic.new
|
|
212
|
+
Prosereflect::Mark::Italic.new,
|
|
212
213
|
])
|
|
213
214
|
|
|
214
215
|
expected = {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
{
|
|
220
|
-
{
|
|
221
|
-
]
|
|
216
|
+
"type" => "text",
|
|
217
|
+
"text" => "Test text",
|
|
218
|
+
"marks" => [
|
|
219
|
+
{ "type" => "link" },
|
|
220
|
+
{ "type" => "bold" },
|
|
221
|
+
{ "type" => "italic" },
|
|
222
|
+
],
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
expect(text.to_h).to eq(expected)
|
|
225
226
|
end
|
|
226
227
|
end
|
|
227
228
|
|
|
228
|
-
describe
|
|
229
|
-
it
|
|
230
|
-
text = described_class.new({
|
|
229
|
+
describe "#to_h" do
|
|
230
|
+
it "creates a hash representation with text" do
|
|
231
|
+
text = described_class.new({ "type" => "text", "text" => "Hello" })
|
|
231
232
|
hash = text.to_h
|
|
232
233
|
|
|
233
|
-
expect(hash[
|
|
234
|
-
expect(hash[
|
|
234
|
+
expect(hash["type"]).to eq("text")
|
|
235
|
+
expect(hash["text"]).to eq("Hello")
|
|
235
236
|
end
|
|
236
237
|
|
|
237
|
-
it
|
|
238
|
-
mark = Prosereflect::Mark::Base.new(type:
|
|
238
|
+
it "includes marks in hash representation when present" do
|
|
239
|
+
mark = Prosereflect::Mark::Base.new(type: "bold")
|
|
239
240
|
marks = [mark]
|
|
240
|
-
text = described_class.new(text:
|
|
241
|
+
text = described_class.new(text: "Bold text", marks: marks)
|
|
241
242
|
|
|
242
243
|
hash = text.to_h
|
|
243
|
-
expect(hash[
|
|
244
|
+
expect(hash["marks"]).to eq([{ "type" => "bold" }])
|
|
244
245
|
end
|
|
245
246
|
end
|
|
246
247
|
|
|
247
|
-
describe
|
|
248
|
-
it
|
|
249
|
-
text = described_class.new({
|
|
248
|
+
describe "inheritance" do
|
|
249
|
+
it "is a Node" do
|
|
250
|
+
text = described_class.new({ "type" => "text", "text" => "Test" })
|
|
250
251
|
expect(text).to be_a(Prosereflect::Node)
|
|
251
252
|
end
|
|
252
253
|
end
|
|
253
254
|
|
|
254
|
-
describe
|
|
255
|
-
it
|
|
256
|
-
bold_mark = Prosereflect::Mark::Base.new(type:
|
|
257
|
-
italic_mark = Prosereflect::Mark::Base.new(type:
|
|
258
|
-
underline_mark = Prosereflect::Mark::Base.new(type:
|
|
255
|
+
describe "with marks" do
|
|
256
|
+
it "can have multiple marks" do
|
|
257
|
+
bold_mark = Prosereflect::Mark::Base.new(type: "bold")
|
|
258
|
+
italic_mark = Prosereflect::Mark::Base.new(type: "italic")
|
|
259
|
+
underline_mark = Prosereflect::Mark::Base.new(type: "underline")
|
|
259
260
|
marks = [bold_mark, italic_mark, underline_mark]
|
|
260
261
|
|
|
261
|
-
text = described_class.new(text:
|
|
262
|
+
text = described_class.new(text: "Formatted text", marks: marks)
|
|
262
263
|
|
|
263
264
|
expect(text.marks.size).to eq(3)
|
|
264
265
|
end
|
|
265
266
|
|
|
266
|
-
it
|
|
267
|
-
text = described_class.new(text:
|
|
268
|
-
mark = Prosereflect::Mark::Link.new(attrs: {
|
|
267
|
+
it "can have marks with attributes" do
|
|
268
|
+
text = described_class.new(text: "Hello")
|
|
269
|
+
mark = Prosereflect::Mark::Link.new(attrs: { "href" => "https://example.com" })
|
|
269
270
|
text.marks = [mark]
|
|
270
271
|
|
|
271
|
-
expect(text.raw_marks[0].attrs[
|
|
272
|
+
expect(text.raw_marks[0].attrs["href"]).to eq("https://example.com")
|
|
272
273
|
end
|
|
273
274
|
end
|
|
274
275
|
end
|