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.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +97 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +19 -1
  8. data/.rubocop_todo.yml +119 -183
  9. data/CLAUDE.md +78 -0
  10. data/Gemfile +8 -4
  11. data/README.adoc +2 -0
  12. data/Rakefile +3 -3
  13. data/docs/Gemfile +10 -0
  14. data/docs/INDEX.adoc +45 -0
  15. data/docs/_advanced/index.adoc +15 -0
  16. data/docs/_advanced/schema.adoc +112 -0
  17. data/docs/_advanced/step-map.adoc +66 -0
  18. data/docs/_advanced/steps.adoc +88 -0
  19. data/docs/_advanced/test-builder.adoc +61 -0
  20. data/docs/_advanced/transform.adoc +92 -0
  21. data/docs/_config.yml +174 -0
  22. data/docs/_features/html-input.adoc +69 -0
  23. data/docs/_features/html-output.adoc +45 -0
  24. data/docs/_features/index.adoc +15 -0
  25. data/docs/_features/marks.adoc +86 -0
  26. data/docs/_features/node-types.adoc +124 -0
  27. data/docs/_features/user-mentions.adoc +47 -0
  28. data/docs/_guides/custom-nodes.adoc +107 -0
  29. data/docs/_guides/index.adoc +13 -0
  30. data/docs/_guides/round-trip-html.adoc +91 -0
  31. data/docs/_guides/serialization.adoc +109 -0
  32. data/docs/_pages/index.adoc +67 -0
  33. data/docs/_reference/document-api.adoc +49 -0
  34. data/docs/_reference/index.adoc +14 -0
  35. data/docs/_reference/node-api.adoc +79 -0
  36. data/docs/_reference/schema-api.adoc +95 -0
  37. data/docs/_reference/transform-api.adoc +77 -0
  38. data/docs/_understanding/document-model.adoc +65 -0
  39. data/docs/_understanding/fragment.adoc +52 -0
  40. data/docs/_understanding/index.adoc +14 -0
  41. data/docs/_understanding/resolved-position.adoc +53 -0
  42. data/docs/_understanding/slice.adoc +54 -0
  43. data/docs/lychee.toml +63 -0
  44. data/lib/prosereflect/attribute/base.rb +4 -6
  45. data/lib/prosereflect/attribute/bold.rb +2 -4
  46. data/lib/prosereflect/attribute/href.rb +1 -3
  47. data/lib/prosereflect/attribute/id.rb +7 -7
  48. data/lib/prosereflect/attribute.rb +4 -7
  49. data/lib/prosereflect/blockquote.rb +19 -11
  50. data/lib/prosereflect/bullet_list.rb +36 -29
  51. data/lib/prosereflect/code_block.rb +23 -27
  52. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  53. data/lib/prosereflect/document.rb +14 -22
  54. data/lib/prosereflect/fragment.rb +249 -0
  55. data/lib/prosereflect/hard_break.rb +6 -6
  56. data/lib/prosereflect/heading.rb +14 -15
  57. data/lib/prosereflect/horizontal_rule.rb +23 -14
  58. data/lib/prosereflect/image.rb +32 -23
  59. data/lib/prosereflect/input/html.rb +179 -104
  60. data/lib/prosereflect/input.rb +7 -0
  61. data/lib/prosereflect/list_item.rb +11 -12
  62. data/lib/prosereflect/mark/base.rb +9 -11
  63. data/lib/prosereflect/mark/bold.rb +1 -3
  64. data/lib/prosereflect/mark/code.rb +1 -3
  65. data/lib/prosereflect/mark/italic.rb +1 -3
  66. data/lib/prosereflect/mark/link.rb +1 -3
  67. data/lib/prosereflect/mark/strike.rb +1 -3
  68. data/lib/prosereflect/mark/subscript.rb +1 -3
  69. data/lib/prosereflect/mark/superscript.rb +1 -3
  70. data/lib/prosereflect/mark/underline.rb +1 -3
  71. data/lib/prosereflect/mark.rb +9 -5
  72. data/lib/prosereflect/node.rb +171 -33
  73. data/lib/prosereflect/ordered_list.rb +17 -14
  74. data/lib/prosereflect/output/html.rb +279 -50
  75. data/lib/prosereflect/output.rb +7 -0
  76. data/lib/prosereflect/paragraph.rb +11 -13
  77. data/lib/prosereflect/parser.rb +56 -66
  78. data/lib/prosereflect/resolved_pos.rb +256 -0
  79. data/lib/prosereflect/schema/attribute.rb +57 -0
  80. data/lib/prosereflect/schema/content_match.rb +656 -0
  81. data/lib/prosereflect/schema/fragment.rb +166 -0
  82. data/lib/prosereflect/schema/mark.rb +121 -0
  83. data/lib/prosereflect/schema/mark_type.rb +130 -0
  84. data/lib/prosereflect/schema/node.rb +236 -0
  85. data/lib/prosereflect/schema/node_type.rb +274 -0
  86. data/lib/prosereflect/schema/schema_main.rb +190 -0
  87. data/lib/prosereflect/schema/spec.rb +92 -0
  88. data/lib/prosereflect/schema.rb +39 -0
  89. data/lib/prosereflect/table.rb +12 -13
  90. data/lib/prosereflect/table_cell.rb +13 -13
  91. data/lib/prosereflect/table_header.rb +17 -17
  92. data/lib/prosereflect/table_row.rb +12 -12
  93. data/lib/prosereflect/text.rb +35 -11
  94. data/lib/prosereflect/transform/attr_step.rb +157 -0
  95. data/lib/prosereflect/transform/insert_step.rb +115 -0
  96. data/lib/prosereflect/transform/mapping.rb +82 -0
  97. data/lib/prosereflect/transform/mark_step.rb +269 -0
  98. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  99. data/lib/prosereflect/transform/replace_step.rb +157 -0
  100. data/lib/prosereflect/transform/slice.rb +91 -0
  101. data/lib/prosereflect/transform/step.rb +89 -0
  102. data/lib/prosereflect/transform/step_map.rb +126 -0
  103. data/lib/prosereflect/transform/structure.rb +120 -0
  104. data/lib/prosereflect/transform/transform.rb +341 -0
  105. data/lib/prosereflect/transform.rb +26 -0
  106. data/lib/prosereflect/user.rb +15 -15
  107. data/lib/prosereflect/version.rb +1 -1
  108. data/lib/prosereflect.rb +30 -17
  109. data/prosereflect.gemspec +17 -16
  110. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  111. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  112. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  113. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  114. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  115. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  116. data/spec/fixtures/documents/table_doc.yaml +32 -0
  117. data/spec/fixtures/documents/transform_test.yaml +14 -0
  118. data/spec/fixtures/schema/custom_schema.rb +37 -0
  119. data/spec/fixtures/schema/test_schema.rb +46 -0
  120. data/spec/fixtures/test_builder/helpers.rb +212 -0
  121. data/spec/prosereflect/document_spec.rb +332 -330
  122. data/spec/prosereflect/fragment_spec.rb +273 -0
  123. data/spec/prosereflect/hard_break_spec.rb +125 -125
  124. data/spec/prosereflect/input/html_spec.rb +718 -522
  125. data/spec/prosereflect/node_spec.rb +311 -182
  126. data/spec/prosereflect/output/html_spec.rb +105 -105
  127. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  128. data/spec/prosereflect/paragraph_spec.rb +275 -274
  129. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  130. data/spec/prosereflect/parser_spec.rb +185 -180
  131. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  132. data/spec/prosereflect/schema/conftest.rb +68 -0
  133. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  134. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  135. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  136. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  137. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  138. data/spec/prosereflect/table_cell_spec.rb +183 -183
  139. data/spec/prosereflect/table_row_spec.rb +149 -149
  140. data/spec/prosereflect/table_spec.rb +320 -318
  141. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  142. data/spec/prosereflect/text_spec.rb +133 -132
  143. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  144. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  145. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  146. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  147. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  148. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  149. data/spec/prosereflect/transform/step_spec.rb +211 -0
  150. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  151. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  152. data/spec/prosereflect/user_spec.rb +31 -28
  153. data/spec/prosereflect_spec.rb +28 -26
  154. data/spec/spec_helper.rb +7 -6
  155. data/spec/support/matchers.rb +6 -6
  156. data/spec/support/shared_examples.rb +49 -49
  157. metadata +96 -5
  158. 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 'spec_helper'
3
+ require "spec_helper"
4
4
 
5
5
  RSpec.describe Prosereflect::Text do
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')
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 'initializes with empty text' do
14
- text = described_class.new({ 'type' => 'text' })
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 'initializes with marks' do
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: 'Hello', marks: marks)
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 '.create' do
30
- it 'creates a simple text node' do
31
- text = described_class.create('Hello world')
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
- 'type' => 'text',
35
- 'text' => 'Hello world'
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 'creates a text node with marks' do
42
- text = described_class.create('Formatted text', [
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
- 'type' => 'text',
49
- 'text' => 'Formatted text',
50
- 'marks' => [
51
- { 'type' => 'bold' },
52
- { 'type' => 'italic' }
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 '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', 'title' => 'Example' }),
62
- Prosereflect::Mark::Bold.new
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
- 'type' => 'text',
67
- 'text' => 'Link text',
68
- 'marks' => [
67
+ "type" => "text",
68
+ "text" => "Link text",
69
+ "marks" => [
69
70
  {
70
- 'type' => 'link',
71
- 'attrs' => {
72
- 'href' => 'https://example.com',
73
- 'title' => 'Example'
74
- }
71
+ "type" => "link",
72
+ "attrs" => {
73
+ "href" => "https://example.com",
74
+ "title" => "Example",
75
+ },
75
76
  },
76
- { 'type' => 'bold' }
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 'text structure' do
85
- it 'creates text with special characters' do
86
- text = described_class.create('Special chars: →, ←, ©, ®, ™')
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
- 'type' => 'text',
90
- 'text' => 'Special chars: →, ←, ©, ®, ™'
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 'creates text with multiple marks and attributes' do
97
- text = described_class.create('Complex text', [
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: { 'href' => 'https://example.com' }),
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
- 'type' => 'text',
107
- 'text' => 'Complex text',
108
- 'marks' => [
109
- { 'type' => 'bold' },
110
- { 'type' => 'italic' },
111
- { 'type' => 'strike' },
107
+ "type" => "text",
108
+ "text" => "Complex text",
109
+ "marks" => [
110
+ { "type" => "bold" },
111
+ { "type" => "italic" },
112
+ { "type" => "strike" },
112
113
  {
113
- 'type' => 'link',
114
- 'attrs' => {
115
- 'href' => 'https://example.com'
116
- }
114
+ "type" => "link",
115
+ "attrs" => {
116
+ "href" => "https://example.com",
117
+ },
117
118
  },
118
- { 'type' => 'underline' }
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 'text operations' do
127
- describe '#text_content' do
128
- it 'returns the text content regardless of marks' do
129
- text = described_class.create('Sample text', [
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('Sample text')
135
+ expect(text.text_content).to eq("Sample text")
135
136
  end
136
137
 
137
- it 'returns empty string for empty text' do
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 'preserves whitespace and special characters' do
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 '#marks' do
149
- it 'preserves mark order' do
150
- text = described_class.new(text: 'Hello')
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 'handles empty marks array' do
162
- text = described_class.create('No marks', [])
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('marks')
165
+ expect(text.to_h).not_to have_key("marks")
165
166
  end
166
167
 
167
- it 'handles nil marks' do
168
- text = described_class.create('No marks', nil)
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('marks')
171
+ expect(text.to_h).not_to have_key("marks")
171
172
  end
172
173
  end
173
174
  end
174
175
 
175
- describe 'mark attributes' do
176
- it 'preserves mark attributes in serialization' do
177
- text = described_class.create('Styled text', [
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
- 'href' => 'https://example.com',
180
- 'title' => 'Example Link',
181
- 'target' => '_blank'
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
- 'type' => 'text',
189
- 'text' => 'Styled text',
190
- 'marks' => [
189
+ "type" => "text",
190
+ "text" => "Styled text",
191
+ "marks" => [
191
192
  {
192
- 'type' => 'link',
193
- 'attrs' => {
194
- 'href' => 'https://example.com',
195
- 'title' => 'Example Link',
196
- 'target' => '_blank'
197
- }
193
+ "type" => "link",
194
+ "attrs" => {
195
+ "href" => "https://example.com",
196
+ "title" => "Example Link",
197
+ "target" => "_blank",
198
+ },
198
199
  },
199
- { 'type' => 'bold' },
200
- { 'type' => 'italic' }
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 'handles marks with empty attributes' do
208
- text = described_class.create('Test text', [
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
- 'type' => 'text',
216
- 'text' => 'Test text',
217
- 'marks' => [
218
- { 'type' => 'link' },
219
- { 'type' => 'bold' },
220
- { 'type' => 'italic' }
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 '#to_h' do
229
- it 'creates a hash representation with text' do
230
- text = described_class.new({ 'type' => 'text', 'text' => 'Hello' })
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['type']).to eq('text')
234
- expect(hash['text']).to eq('Hello')
234
+ expect(hash["type"]).to eq("text")
235
+ expect(hash["text"]).to eq("Hello")
235
236
  end
236
237
 
237
- it 'includes marks in hash representation when present' do
238
- mark = Prosereflect::Mark::Base.new(type: 'bold')
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: 'Bold text', marks: marks)
241
+ text = described_class.new(text: "Bold text", marks: marks)
241
242
 
242
243
  hash = text.to_h
243
- expect(hash['marks']).to eq([{ 'type' => 'bold' }])
244
+ expect(hash["marks"]).to eq([{ "type" => "bold" }])
244
245
  end
245
246
  end
246
247
 
247
- describe 'inheritance' do
248
- it 'is a Node' do
249
- text = described_class.new({ 'type' => 'text', 'text' => 'Test' })
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 'with marks' do
255
- it 'can have multiple marks' do
256
- bold_mark = Prosereflect::Mark::Base.new(type: 'bold')
257
- italic_mark = Prosereflect::Mark::Base.new(type: 'italic')
258
- underline_mark = Prosereflect::Mark::Base.new(type: 'underline')
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: 'Formatted text', marks: marks)
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 'can have marks with attributes' do
267
- text = described_class.new(text: 'Hello')
268
- mark = Prosereflect::Mark::Link.new(attrs: { 'href' => 'https://example.com' })
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['href']).to eq('https://example.com')
272
+ expect(text.raw_marks[0].attrs["href"]).to eq("https://example.com")
272
273
  end
273
274
  end
274
275
  end