rux 1.1.2 → 1.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/CHANGELOG.md +24 -0
- data/Gemfile +8 -1
- data/README.md +88 -0
- data/bin/ruxc +47 -5
- data/bin/ruxlex +13 -0
- 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 +100 -23
- data/lib/rux/lex/states.csv +51 -39
- data/lib/rux/lexer.rb +13 -0
- data/lib/rux/parser.rb +112 -28
- data/lib/rux/ruby_lexer.rb +32 -8
- data/lib/rux/rux_lexer.rb +16 -4
- data/lib/rux/version.rb +1 -1
- data/lib/rux/visitor.rb +16 -0
- data/lib/rux.rb +2 -3
- data/rux.gemspec +3 -1
- data/spec/parser/attributes_spec.rb +121 -0
- data/spec/parser/fragment_spec.rb +64 -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 +56 -232
- data/spec/render_spec.rb +70 -7
- data/spec/spec_helper.rb +19 -32
- 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 +36 -8
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
|
|
@@ -217,7 +10,7 @@ describe Rux::Parser do
|
|
|
217
10
|
it 'raises an error when no state transition can be found' do
|
|
218
11
|
expect { compile('<Hello <foo>') }.to(
|
|
219
12
|
raise_error(Rux::Lexer::TransitionError,
|
|
220
|
-
'no transition found from tRUX_ATTRIBUTE_SPACES_BODY at position 7 '\
|
|
13
|
+
'no transition found from tRUX_ATTRIBUTE_SPACES_BODY for "<" at position 7 '\
|
|
221
14
|
'while lexing rux code')
|
|
222
15
|
)
|
|
223
16
|
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,73 @@ 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
66
|
|
|
274
|
-
it '
|
|
67
|
+
it 'preserves comments' do
|
|
275
68
|
code = <<~RUX
|
|
276
|
-
|
|
69
|
+
# frozen_string_literal: true
|
|
70
|
+
|
|
71
|
+
class Foo
|
|
72
|
+
def call
|
|
73
|
+
<p>Hello</p>
|
|
74
|
+
end
|
|
75
|
+
end
|
|
277
76
|
RUX
|
|
278
|
-
expect(compile(code)).to eq(<<~RUBY
|
|
279
|
-
|
|
77
|
+
expect(compile(code)).to eq(<<~RUBY)
|
|
78
|
+
# frozen_string_literal: true
|
|
79
|
+
class Foo
|
|
80
|
+
def call
|
|
81
|
+
Rux.tag("p") {
|
|
82
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
|
83
|
+
_rux_buf_.safe_append("Hello")
|
|
84
|
+
}.to_s
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
end
|
|
280
88
|
RUBY
|
|
281
89
|
end
|
|
282
90
|
|
|
283
|
-
it '
|
|
91
|
+
it 'allows a tag after ruby code in a branch situation' do
|
|
284
92
|
code = <<~RUX
|
|
285
|
-
<div
|
|
93
|
+
<div>
|
|
94
|
+
{if foo
|
|
95
|
+
<>foo</>
|
|
96
|
+
else
|
|
97
|
+
<div></div>
|
|
98
|
+
end}
|
|
99
|
+
</div>
|
|
286
100
|
RUX
|
|
287
101
|
expect(compile(code)).to eq(<<~RUBY.strip)
|
|
288
|
-
Rux.tag("div"
|
|
102
|
+
Rux.tag("div") {
|
|
103
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
|
104
|
+
_rux_buf_.append(if foo
|
|
105
|
+
Rux.create_buffer.tap { |_rux_buf_|
|
|
106
|
+
_rux_buf_.safe_append("foo")
|
|
107
|
+
}.to_s
|
|
108
|
+
else
|
|
109
|
+
Rux.tag("div")
|
|
110
|
+
end)
|
|
111
|
+
}.to_s
|
|
112
|
+
}
|
|
289
113
|
RUBY
|
|
290
114
|
end
|
|
291
115
|
end
|
data/spec/render_spec.rb
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
|
-
describe
|
|
4
|
-
|
|
5
|
-
ruby_code = Rux.to_ruby(rux_code)
|
|
6
|
-
ViewComponent::Base.new.instance_eval(ruby_code)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
it 'handles a HTML tags inside ruby code' do
|
|
3
|
+
describe 'rendering', type: :render do
|
|
4
|
+
it 'handles HTML tags inside ruby code' do
|
|
10
5
|
result = render(<<~RUBY)
|
|
11
6
|
<div>
|
|
12
7
|
{3.times.map do
|
|
@@ -83,4 +78,72 @@ describe Rux do
|
|
|
83
78
|
"<div data-foo=\"bar\"></div>"
|
|
84
79
|
)
|
|
85
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
|
|
86
149
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,47 +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
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def call
|
|
32
|
-
"<p>#{a} and #{b}</p>"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
class DataComponent < ViewComponent::Base
|
|
37
|
-
def initialize(data_foo:)
|
|
38
|
-
@data_foo = data_foo
|
|
14
|
+
module Rux
|
|
15
|
+
module ParserTestHelpers
|
|
16
|
+
def compile(rux_code)
|
|
17
|
+
Rux.to_ruby(rux_code)
|
|
18
|
+
end
|
|
39
19
|
end
|
|
40
20
|
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
43
28
|
end
|
|
44
29
|
end
|
|
45
30
|
|
|
46
31
|
RSpec.configure do |config|
|
|
32
|
+
config.include(Rux::ParserTestHelpers, type: :parser)
|
|
33
|
+
config.include(Rux::RenderTestHelpers, type: :render)
|
|
47
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
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# a very basic implementation of the view_component lib
|
|
2
|
+
module ViewComponent
|
|
3
|
+
class Slot
|
|
4
|
+
def initialize(component_instance, content_block)
|
|
5
|
+
@component_instance = component_instance
|
|
6
|
+
@content_block = content_block
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_s
|
|
10
|
+
@component_instance.content = @content_block.call(@component_instance) if @content_block
|
|
11
|
+
@component_instance.call
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class Base
|
|
16
|
+
class << self
|
|
17
|
+
def renders_many(name, component)
|
|
18
|
+
singular_name = name.to_s.chomp("s")
|
|
19
|
+
|
|
20
|
+
registered_slots[name] = {
|
|
21
|
+
renderable: component,
|
|
22
|
+
collection: true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
define_method(:"with_#{singular_name}") do |*args, **kwargs, &block|
|
|
26
|
+
slots[name] ||= []
|
|
27
|
+
slots[name] << Slot.new(component.new(*args, **kwargs), block)
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
define_method(name) do
|
|
32
|
+
slots[name] || []
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def registered_slots
|
|
39
|
+
@registered_slots ||= {}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
attr_accessor :content
|
|
44
|
+
|
|
45
|
+
def render(component, &block)
|
|
46
|
+
if block
|
|
47
|
+
component.content = block.call(component)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
component.call
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def slots
|
|
56
|
+
@slots ||= {}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|