rux 1.1.1 → 1.2.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/CHANGELOG.md +16 -0
- data/Gemfile +1 -1
- data/README.md +91 -1
- data/bin/ruxc +21 -4
- data/lib/rux/ast/attr_node.rb +22 -0
- data/lib/rux/ast/attrs_node.rb +30 -0
- data/lib/rux/ast/fragment_node.rb +15 -0
- data/lib/rux/ast/root_node.rb +19 -0
- data/lib/rux/ast/ruby_attr_node.rb +28 -0
- data/lib/rux/ast/string_node.rb +4 -2
- data/lib/rux/ast/tag_node.rb +10 -1
- data/lib/rux/ast/text_node.rb +2 -1
- data/lib/rux/ast.rb +10 -5
- data/lib/rux/buffer.rb +21 -3
- data/lib/rux/default_tag_builder.rb +15 -6
- data/lib/rux/default_visitor.rb +93 -19
- data/lib/rux/lex/states.csv +22 -11
- data/lib/rux/parser.rb +102 -24
- data/lib/rux/ruby_lexer.rb +1 -0
- data/lib/rux/rux_lexer.rb +15 -3
- data/lib/rux/version.rb +1 -1
- data/lib/rux/visitor.rb +16 -0
- data/spec/parser/attributes_spec.rb +97 -0
- data/spec/parser/fragment_spec.rb +47 -0
- data/spec/parser/html_safety_spec.rb +13 -0
- data/spec/parser/slot_spec.rb +40 -0
- data/spec/parser/tag_spec.rb +174 -0
- data/spec/parser_spec.rb +17 -242
- data/spec/render_spec.rb +89 -6
- data/spec/spec_helper.rb +19 -22
- data/spec/support/components/args_component.rb +14 -0
- data/spec/support/components/data_component.rb +11 -0
- data/spec/support/components/table_component.rb +23 -0
- data/spec/support/components/test_component.rb +7 -0
- data/spec/support/view_component/base.rb +59 -0
- metadata +17 -2
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'tags', type: :parser do
|
4
|
+
it 'handles a single self-closing tag' do
|
5
|
+
expect(compile("<Hello/>")).to eq("render(Hello.new)")
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'handles a self-closing tag with spaces preceding the closing punctuation' do
|
9
|
+
expect(compile("<Hello />")).to eq("render(Hello.new)")
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'handles a single opening and closing tag' do
|
13
|
+
expect(compile("<Hello></Hello>")).to eq('render(Hello.new)')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'handles a single tag with a text body' do
|
17
|
+
expect(compile("<Hello>foo</Hello>")).to eq(<<~RUBY.strip)
|
18
|
+
render(Hello.new) { |rux_block_arg0|
|
19
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
20
|
+
_rux_buf_.safe_append("foo")
|
21
|
+
}.to_s
|
22
|
+
}
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'handles simple ruby statements in tag bodies' do
|
27
|
+
expect(compile('<Hello>{"foo"}</Hello>')).to eq(<<~RUBY.strip)
|
28
|
+
render(Hello.new) { |rux_block_arg0|
|
29
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
30
|
+
_rux_buf_.append("foo")
|
31
|
+
}.to_s
|
32
|
+
}
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'handles tag bodies containing ruby code with curly braces' do
|
37
|
+
expect(compile('<Hello>{[1, 2, 3].map { |n| n * 2 }.join(", ")}</Hello>')).to eq(<<~RUBY.strip)
|
38
|
+
render(Hello.new) { |rux_block_arg0|
|
39
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
40
|
+
_rux_buf_.append([1, 2, 3].map { |n|
|
41
|
+
n * 2
|
42
|
+
}.join(", "))
|
43
|
+
}.to_s
|
44
|
+
}
|
45
|
+
RUBY
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'handles tag bodies with intermixed text and ruby code' do
|
49
|
+
expect(compile('<Hello>abc {foo} def {bar} baz</Hello>')).to eq(<<~RUBY.strip)
|
50
|
+
render(Hello.new) { |rux_block_arg0|
|
51
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
52
|
+
_rux_buf_.safe_append("abc ")
|
53
|
+
_rux_buf_.append(foo)
|
54
|
+
_rux_buf_.safe_append(" def ")
|
55
|
+
_rux_buf_.append(bar)
|
56
|
+
_rux_buf_.safe_append(" baz")
|
57
|
+
}.to_s
|
58
|
+
}
|
59
|
+
RUBY
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'handles rux tags inside ruby code' do
|
63
|
+
rux_code = <<~RUX
|
64
|
+
<Outer>
|
65
|
+
{5.times.map do
|
66
|
+
<Inner>What a {@thing}</Inner>
|
67
|
+
end}
|
68
|
+
</Outer>
|
69
|
+
RUX
|
70
|
+
|
71
|
+
expect(compile(rux_code)).to eq(<<~RUBY.strip)
|
72
|
+
render(Outer.new) { |rux_block_arg0|
|
73
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
74
|
+
_rux_buf_.append(5.times.map {
|
75
|
+
render(Inner.new) { |rux_block_arg1|
|
76
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
77
|
+
_rux_buf_.safe_append("What a ")
|
78
|
+
_rux_buf_.append(@thing)
|
79
|
+
}.to_s
|
80
|
+
}
|
81
|
+
})
|
82
|
+
}.to_s
|
83
|
+
}
|
84
|
+
RUBY
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'handles HTML tags inside ruby code' do
|
88
|
+
rux_code = <<~RUX
|
89
|
+
<div>
|
90
|
+
{5.times.map do
|
91
|
+
<p>What a {@thing}</p>
|
92
|
+
end}
|
93
|
+
</div>
|
94
|
+
RUX
|
95
|
+
|
96
|
+
expect(compile(rux_code)).to eq(<<~RUBY.strip)
|
97
|
+
Rux.tag("div") {
|
98
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
99
|
+
_rux_buf_.append(5.times.map {
|
100
|
+
Rux.tag("p") {
|
101
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
102
|
+
_rux_buf_.safe_append("What a ")
|
103
|
+
_rux_buf_.append(@thing)
|
104
|
+
}.to_s
|
105
|
+
}
|
106
|
+
})
|
107
|
+
}.to_s
|
108
|
+
}
|
109
|
+
RUBY
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'handles regular HTML tags' do
|
113
|
+
expect(compile('<div>foo</div>')).to eq(<<~RUBY.strip)
|
114
|
+
Rux.tag("div") {
|
115
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
116
|
+
_rux_buf_.safe_append("foo")
|
117
|
+
}.to_s
|
118
|
+
}
|
119
|
+
RUBY
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'handles regular HTML tags inside ruby code' do
|
123
|
+
rux_code = <<~RUX
|
124
|
+
<Outer>
|
125
|
+
{5.times.map do
|
126
|
+
<div>So {@cool}</div>
|
127
|
+
end}
|
128
|
+
</Outer>
|
129
|
+
RUX
|
130
|
+
|
131
|
+
expect(compile(rux_code)).to eq(<<~RUBY.strip)
|
132
|
+
render(Outer.new) { |rux_block_arg0|
|
133
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
134
|
+
_rux_buf_.append(5.times.map {
|
135
|
+
Rux.tag("div") {
|
136
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
137
|
+
_rux_buf_.safe_append("So ")
|
138
|
+
_rux_buf_.append(@cool)
|
139
|
+
}.to_s
|
140
|
+
}
|
141
|
+
})
|
142
|
+
}.to_s
|
143
|
+
}
|
144
|
+
RUBY
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'demonstrates that tags support keyword splats' do
|
148
|
+
code = <<~RUX
|
149
|
+
<div {**args}>foo</div>
|
150
|
+
RUX
|
151
|
+
|
152
|
+
expect(compile(code)).to eq(<<~RUBY.strip)
|
153
|
+
Rux.tag("div", { **args }) {
|
154
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
155
|
+
_rux_buf_.safe_append("foo")
|
156
|
+
}.to_s
|
157
|
+
}
|
158
|
+
RUBY
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'demonstrates that tags support keyword arguments mixed with splats' do
|
162
|
+
code = <<~RUX
|
163
|
+
<div foo="bar" {**args} baz={"boo"}>foo</div>
|
164
|
+
RUX
|
165
|
+
|
166
|
+
expect(compile(code)).to eq(<<~RUBY.strip)
|
167
|
+
Rux.tag("div", { foo: "bar", **args, baz: "boo" }) {
|
168
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
169
|
+
_rux_buf_.safe_append("foo")
|
170
|
+
}.to_s
|
171
|
+
}
|
172
|
+
RUBY
|
173
|
+
end
|
174
|
+
end
|
data/spec/parser_spec.rb
CHANGED
@@ -2,214 +2,7 @@ require 'spec_helper'
|
|
2
2
|
require 'parser'
|
3
3
|
require 'unparser'
|
4
4
|
|
5
|
-
describe
|
6
|
-
def compile(rux_code)
|
7
|
-
Rux.to_ruby(rux_code)
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'handles a single self-closing tag' do
|
11
|
-
expect(compile("<Hello/>")).to eq("render(Hello.new)")
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'handles a self-closing tag with spaces preceding the closing punctuation' do
|
15
|
-
expect(compile("<Hello />")).to eq("render(Hello.new)")
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'handles a single opening and closing tag' do
|
19
|
-
expect(compile("<Hello></Hello>")).to eq('render(Hello.new)')
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'handles a single tag with a text body' do
|
23
|
-
expect(compile("<Hello>foo</Hello>")).to eq(<<~RUBY.strip)
|
24
|
-
render(Hello.new) {
|
25
|
-
"foo"
|
26
|
-
}
|
27
|
-
RUBY
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'handles single-quoted rux attributes' do
|
31
|
-
expect(compile("<Hello foo='bar' />")).to eq(
|
32
|
-
'render(Hello.new(foo: "bar"))'
|
33
|
-
)
|
34
|
-
|
35
|
-
expect(compile("<Hello foo='bar'></Hello>")).to eq(
|
36
|
-
'render(Hello.new(foo: "bar"))'
|
37
|
-
)
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'handles double-quoted rux attributes' do
|
41
|
-
expect(compile('<Hello foo="bar" />')).to eq(
|
42
|
-
'render(Hello.new(foo: "bar"))'
|
43
|
-
)
|
44
|
-
|
45
|
-
expect(compile('<Hello foo="bar"></Hello>')).to eq(
|
46
|
-
'render(Hello.new(foo: "bar"))'
|
47
|
-
)
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'handles non-uniform spacing between attributes' do
|
51
|
-
expect(compile('<Hello foo="bar" baz= "boo" bix ="bit" />')).to eq(
|
52
|
-
'render(Hello.new(foo: "bar", baz: "boo", bix: "bit"))'
|
53
|
-
)
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'handles boolean attributes' do
|
57
|
-
expect(compile('<Hello disabled />')).to eq(
|
58
|
-
'render(Hello.new(disabled: "true"))'
|
59
|
-
)
|
60
|
-
|
61
|
-
expect(compile('<Hello disabled/>')).to eq(
|
62
|
-
'render(Hello.new(disabled: "true"))'
|
63
|
-
)
|
64
|
-
|
65
|
-
expect(compile('<Hello disabled></Hello>')).to eq(
|
66
|
-
'render(Hello.new(disabled: "true"))'
|
67
|
-
)
|
68
|
-
end
|
69
|
-
|
70
|
-
it 'converts dashes to underscores in attribute keys' do
|
71
|
-
expect(compile('<Hello foo-bar="baz" />')).to eq(
|
72
|
-
'render(Hello.new(foo_bar: "baz"))'
|
73
|
-
)
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'handles simple ruby statements in attributes' do
|
77
|
-
expect(compile('<Hello foo={true} />')).to eq(
|
78
|
-
'render(Hello.new(foo: true))'
|
79
|
-
)
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'handles ruby hashes in attributes' do
|
83
|
-
expect(compile('<Hello foo={{ foo: "bar", baz: "boo" }} />')).to eq(
|
84
|
-
'render(Hello.new(foo: { foo: "bar", baz: "boo" }))'
|
85
|
-
)
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'handles ruby code with curly braces in attributes' do
|
89
|
-
expect(compile('<Hello foo={[1, 2, 3].map { |n| n * 2 }} />')).to eq(<<~RUBY.strip)
|
90
|
-
render(Hello.new(foo: [1, 2, 3].map { |n|
|
91
|
-
n * 2
|
92
|
-
}))
|
93
|
-
RUBY
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'handles simple ruby statements in tag bodies' do
|
97
|
-
expect(compile('<Hello>{"foo"}</Hello>')).to eq(<<~RUBY.strip)
|
98
|
-
render(Hello.new) {
|
99
|
-
"foo"
|
100
|
-
}
|
101
|
-
RUBY
|
102
|
-
end
|
103
|
-
|
104
|
-
it 'handles tag bodies containing ruby code with curly braces' do
|
105
|
-
expect(compile('<Hello>{[1, 2, 3].map { |n| n * 2 }.join(", ")}</Hello>')).to eq(<<~RUBY.strip)
|
106
|
-
render(Hello.new) {
|
107
|
-
[1, 2, 3].map { |n|
|
108
|
-
n * 2
|
109
|
-
}.join(", ")
|
110
|
-
}
|
111
|
-
RUBY
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'handles tag bodies with intermixed text and ruby code' do
|
115
|
-
expect(compile('<Hello>abc {foo} def {bar} baz</Hello>')).to eq(<<~RUBY.strip)
|
116
|
-
render(Hello.new) {
|
117
|
-
Rux.create_buffer.tap { |_rux_buf_|
|
118
|
-
_rux_buf_ << "abc "
|
119
|
-
_rux_buf_ << foo
|
120
|
-
_rux_buf_ << " def "
|
121
|
-
_rux_buf_ << bar
|
122
|
-
_rux_buf_ << " baz"
|
123
|
-
}.to_s
|
124
|
-
}
|
125
|
-
RUBY
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'handles rux tags inside ruby code' do
|
129
|
-
rux_code = <<~RUX
|
130
|
-
<Outer>
|
131
|
-
{5.times.map do
|
132
|
-
<Inner>What a {@thing}</Inner>
|
133
|
-
end}
|
134
|
-
</Outer>
|
135
|
-
RUX
|
136
|
-
|
137
|
-
expect(compile(rux_code)).to eq(<<~RUBY.strip)
|
138
|
-
render(Outer.new) {
|
139
|
-
5.times.map {
|
140
|
-
render(Inner.new) {
|
141
|
-
Rux.create_buffer.tap { |_rux_buf_|
|
142
|
-
_rux_buf_ << "What a "
|
143
|
-
_rux_buf_ << @thing
|
144
|
-
}.to_s
|
145
|
-
}
|
146
|
-
}
|
147
|
-
}
|
148
|
-
RUBY
|
149
|
-
end
|
150
|
-
|
151
|
-
it 'handles HTML tags inside ruby code' do
|
152
|
-
rux_code = <<~RUX
|
153
|
-
<div>
|
154
|
-
{5.times.map do
|
155
|
-
<p>What a {@thing}</p>
|
156
|
-
end}
|
157
|
-
</div>
|
158
|
-
RUX
|
159
|
-
|
160
|
-
expect(compile(rux_code)).to eq(<<~RUBY.strip)
|
161
|
-
Rux.tag("div") {
|
162
|
-
5.times.map {
|
163
|
-
Rux.tag("p") {
|
164
|
-
Rux.create_buffer.tap { |_rux_buf_|
|
165
|
-
_rux_buf_ << "What a "
|
166
|
-
_rux_buf_ << @thing
|
167
|
-
}.to_s
|
168
|
-
}
|
169
|
-
}
|
170
|
-
}
|
171
|
-
RUBY
|
172
|
-
end
|
173
|
-
|
174
|
-
it 'handles regular HTML tags' do
|
175
|
-
expect(compile('<div>foo</div>')).to eq(<<~RUBY.strip)
|
176
|
-
Rux.tag("div") {
|
177
|
-
"foo"
|
178
|
-
}
|
179
|
-
RUBY
|
180
|
-
end
|
181
|
-
|
182
|
-
it 'handles regular HTML tags inside ruby code' do
|
183
|
-
rux_code = <<~RUX
|
184
|
-
<Outer>
|
185
|
-
{5.times.map do
|
186
|
-
<div>So {@cool}</div>
|
187
|
-
end}
|
188
|
-
</Outer>
|
189
|
-
RUX
|
190
|
-
|
191
|
-
expect(compile(rux_code)).to eq(<<~RUBY.strip)
|
192
|
-
render(Outer.new) {
|
193
|
-
5.times.map {
|
194
|
-
Rux.tag("div") {
|
195
|
-
Rux.create_buffer.tap { |_rux_buf_|
|
196
|
-
_rux_buf_ << "So "
|
197
|
-
_rux_buf_ << @cool
|
198
|
-
}.to_s
|
199
|
-
}
|
200
|
-
}
|
201
|
-
}
|
202
|
-
RUBY
|
203
|
-
end
|
204
|
-
|
205
|
-
it 'escapes HTML entities in strings' do
|
206
|
-
expect(compile('<Hello>"foo"</Hello>')).to eq(<<~RUBY.strip)
|
207
|
-
render(Hello.new) {
|
208
|
-
""foo""
|
209
|
-
}
|
210
|
-
RUBY
|
211
|
-
end
|
212
|
-
|
5
|
+
describe 'parsing', type: :parser do
|
213
6
|
it 'raises an error on premature end of input' do
|
214
7
|
expect { compile('<Hello') }.to raise_error(Rux::Lexer::EOFError)
|
215
8
|
end
|
@@ -230,13 +23,13 @@ describe Rux::Parser do
|
|
230
23
|
)
|
231
24
|
end
|
232
25
|
|
233
|
-
it 'emits
|
26
|
+
it 'emits spaces between adjacent ruby code snippets' do
|
234
27
|
expect(compile("<Hello>{first} {second}</Hello>")).to eq(<<~RUBY.strip)
|
235
|
-
render(Hello.new) {
|
28
|
+
render(Hello.new) { |rux_block_arg0|
|
236
29
|
Rux.create_buffer.tap { |_rux_buf_|
|
237
|
-
_rux_buf_
|
238
|
-
_rux_buf_
|
239
|
-
_rux_buf_
|
30
|
+
_rux_buf_.append(first)
|
31
|
+
_rux_buf_.safe_append(" ")
|
32
|
+
_rux_buf_.append(second)
|
240
33
|
}.to_s
|
241
34
|
}
|
242
35
|
RUBY
|
@@ -250,42 +43,24 @@ describe Rux::Parser do
|
|
250
43
|
</Hello>
|
251
44
|
RUX
|
252
45
|
expect(compile(code)).to eq(<<~RUBY.strip)
|
253
|
-
render(Hello.new) {
|
46
|
+
render(Hello.new) { |rux_block_arg0|
|
254
47
|
Rux.create_buffer.tap { |_rux_buf_|
|
255
|
-
_rux_buf_
|
48
|
+
_rux_buf_.append(render(Hola.new) { |rux_block_arg1|
|
256
49
|
Rux.create_buffer.tap { |_rux_buf_|
|
257
|
-
_rux_buf_
|
258
|
-
_rux_buf_
|
259
|
-
_rux_buf_
|
50
|
+
_rux_buf_.append(first)
|
51
|
+
_rux_buf_.safe_append(" ")
|
52
|
+
_rux_buf_.append(second)
|
260
53
|
}.to_s
|
261
|
-
}
|
262
|
-
_rux_buf_
|
54
|
+
})
|
55
|
+
_rux_buf_.append(render(Hola.new) { |rux_block_arg1|
|
263
56
|
Rux.create_buffer.tap { |_rux_buf_|
|
264
|
-
_rux_buf_
|
265
|
-
_rux_buf_
|
266
|
-
_rux_buf_
|
57
|
+
_rux_buf_.append(first)
|
58
|
+
_rux_buf_.safe_append(" ")
|
59
|
+
_rux_buf_.append(second)
|
267
60
|
}.to_s
|
268
|
-
}
|
61
|
+
})
|
269
62
|
}.to_s
|
270
63
|
}
|
271
64
|
RUBY
|
272
65
|
end
|
273
|
-
|
274
|
-
it 'slugifies ruby arguments' do
|
275
|
-
code = <<~RUX
|
276
|
-
<Hello data-foo="bar" />
|
277
|
-
RUX
|
278
|
-
expect(compile(code)).to eq(<<~RUBY.strip)
|
279
|
-
render(Hello.new(data_foo: "bar"))
|
280
|
-
RUBY
|
281
|
-
end
|
282
|
-
|
283
|
-
it 'does not slugify HTML attributes' do
|
284
|
-
code = <<~RUX
|
285
|
-
<div data-foo="bar" />
|
286
|
-
RUX
|
287
|
-
expect(compile(code)).to eq(<<~RUBY.strip)
|
288
|
-
Rux.tag("div", { :"data-foo" => "bar" })
|
289
|
-
RUBY
|
290
|
-
end
|
291
66
|
end
|
data/spec/render_spec.rb
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
4
|
-
def render(rux_code)
|
5
|
-
ruby_code = Rux.to_ruby(rux_code)
|
6
|
-
ViewComponent::Base.new.instance_eval(ruby_code)
|
7
|
-
end
|
8
|
-
|
3
|
+
describe 'rendering', type: :render do
|
9
4
|
it 'handles a HTML tags inside ruby code' do
|
10
5
|
result = render(<<~RUBY)
|
11
6
|
<div>
|
@@ -63,4 +58,92 @@ describe Rux do
|
|
63
58
|
"<div><p>Hello World</p><p>Hello World</p><p>Hello World</p><p>Hello World</p></div>"
|
64
59
|
)
|
65
60
|
end
|
61
|
+
|
62
|
+
it 'slugifies ruby arguments' do
|
63
|
+
result = render(<<~RUBY)
|
64
|
+
<DataComponent data-foo="foo" />
|
65
|
+
RUBY
|
66
|
+
|
67
|
+
expect(result).to eq(
|
68
|
+
"<div data-foo=\"foo\"></div>"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'does not slugify HTML attributes' do
|
73
|
+
result = render(<<~RUBY)
|
74
|
+
<div data-foo="bar"></div>
|
75
|
+
RUBY
|
76
|
+
|
77
|
+
expect(result).to eq(
|
78
|
+
"<div data-foo=\"bar\"></div>"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'renders slots correctly' do
|
83
|
+
result = render(<<~RUBY)
|
84
|
+
<TableComponent>
|
85
|
+
<WithRow>
|
86
|
+
<WithColumn>Foo 1</WithColumn>
|
87
|
+
</WithRow>
|
88
|
+
<WithRow>
|
89
|
+
<WithColumn>Foo 2</WithColumn>
|
90
|
+
</WithRow>
|
91
|
+
</TableComponent>
|
92
|
+
RUBY
|
93
|
+
|
94
|
+
expect(result).to eq(
|
95
|
+
"<table><tr><td>Foo 1</td></tr><tr><td>Foo 2</td></tr></table>"
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'calls .to_s on anything appended to a buffer' do
|
100
|
+
result = render(<<~RUBY)
|
101
|
+
<div>
|
102
|
+
{[["foo", "bar"], ["baz", "boo"]].map do |row|
|
103
|
+
<>{row}</>
|
104
|
+
end}
|
105
|
+
</div>
|
106
|
+
RUBY
|
107
|
+
expect(result).to eq(
|
108
|
+
'<div>foobarbazboo</div>'
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'escapes arbitrary ruby expressions' do
|
113
|
+
result = render(<<~RUBY, value: "<p>Foo</p>")
|
114
|
+
<div>{kwargs[:value]}</div>
|
115
|
+
RUBY
|
116
|
+
expect(result).to eq(
|
117
|
+
"<div><p>Foo</p></div>"
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'escapes double quotes in HTML attributes' do
|
122
|
+
result = render(<<~RUBY)
|
123
|
+
<div class={'"foo"'}>foo</div>
|
124
|
+
RUBY
|
125
|
+
expect(result).to eq(
|
126
|
+
"<div class=\""foo"\">foo</div>"
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'handles keyword splats' do
|
131
|
+
kwargs = { a: "foo", b: "bar" }
|
132
|
+
result = render(<<~RUBY, **kwargs)
|
133
|
+
<ArgsComponent {**kwargs} />
|
134
|
+
RUBY
|
135
|
+
expect(result).to eq(
|
136
|
+
"<p>foo and bar</p>"
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'handles keyword splats mixed with keyword args' do
|
141
|
+
kwargs = { a: "foo", b: "bar" }
|
142
|
+
result = render(<<~RUBY, **kwargs)
|
143
|
+
<ArgsComponent {**(kwargs.except(:b))} b={kwargs[:b]} />
|
144
|
+
RUBY
|
145
|
+
expect(result).to eq(
|
146
|
+
"<p>foo and bar</p>"
|
147
|
+
)
|
148
|
+
end
|
66
149
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,37 +1,34 @@
|
|
1
1
|
require 'rspec'
|
2
2
|
require 'rux'
|
3
|
-
require 'pry-byebug'
|
4
3
|
|
5
|
-
|
6
|
-
module ViewComponent
|
7
|
-
class Base
|
8
|
-
attr_accessor :content
|
4
|
+
$:.push(File.join(__dir__, 'support'))
|
9
5
|
|
10
|
-
|
11
|
-
component.content = block.call if block
|
12
|
-
component.call
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
6
|
+
require 'view_component/base'
|
16
7
|
|
17
|
-
|
18
|
-
|
19
|
-
|
8
|
+
Dir.chdir(File.join(__dir__, 'support')) do
|
9
|
+
Dir.glob('components/*.rb').each do |component_file|
|
10
|
+
require component_file
|
20
11
|
end
|
21
12
|
end
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@b = b
|
14
|
+
module Rux
|
15
|
+
module ParserTestHelpers
|
16
|
+
def compile(rux_code)
|
17
|
+
Rux.to_ruby(rux_code)
|
18
|
+
end
|
29
19
|
end
|
30
20
|
|
31
|
-
|
32
|
-
|
21
|
+
module RenderTestHelpers
|
22
|
+
def render(rux_code, **kwargs)
|
23
|
+
ruby_code = Rux.to_ruby(rux_code)
|
24
|
+
ViewComponent::Base.new.instance_exec(ruby_code, **kwargs) do |ruby_code, **kwargs|
|
25
|
+
eval(ruby_code)
|
26
|
+
end
|
27
|
+
end
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
31
|
RSpec.configure do |config|
|
32
|
+
config.include(Rux::ParserTestHelpers, type: :parser)
|
33
|
+
config.include(Rux::RenderTestHelpers, type: :render)
|
37
34
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'view_component/base'
|
2
|
+
|
3
|
+
class ColumnComponent < ViewComponent::Base
|
4
|
+
def call
|
5
|
+
"<td>#{content}</td>"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class RowComponent < ViewComponent::Base
|
10
|
+
renders_many :columns, ColumnComponent
|
11
|
+
|
12
|
+
def call
|
13
|
+
"<tr>#{columns.map(&:to_s).join}</tr>"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TableComponent < ViewComponent::Base
|
18
|
+
renders_many :rows, RowComponent
|
19
|
+
|
20
|
+
def call
|
21
|
+
"<table>#{rows.map(&:to_s).join}</table>"
|
22
|
+
end
|
23
|
+
end
|