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.
@@ -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 Rux::Parser do
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
- "&quot;foo&quot;"
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 handles spaces between adjacent ruby code snippets' do
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_ << first
238
- _rux_buf_ << " "
239
- _rux_buf_ << second
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_ << render(Hola.new) {
48
+ _rux_buf_.append(render(Hola.new) { |rux_block_arg1|
256
49
  Rux.create_buffer.tap { |_rux_buf_|
257
- _rux_buf_ << first
258
- _rux_buf_ << " "
259
- _rux_buf_ << second
50
+ _rux_buf_.append(first)
51
+ _rux_buf_.safe_append(" ")
52
+ _rux_buf_.append(second)
260
53
  }.to_s
261
- }
262
- _rux_buf_ << render(Hola.new) {
54
+ })
55
+ _rux_buf_.append(render(Hola.new) { |rux_block_arg1|
263
56
  Rux.create_buffer.tap { |_rux_buf_|
264
- _rux_buf_ << first
265
- _rux_buf_ << " "
266
- _rux_buf_ << second
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 Rux do
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>&lt;p&gt;Foo&lt;/p&gt;</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=\"&quot;foo&quot;\">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
- # a very basic implementation of the view_component lib
6
- module ViewComponent
7
- class Base
8
- attr_accessor :content
4
+ $:.push(File.join(__dir__, 'support'))
9
5
 
10
- def render(component, &block)
11
- component.content = block.call if block
12
- component.call
13
- end
14
- end
15
- end
6
+ require 'view_component/base'
16
7
 
17
- class TestComponent < ViewComponent::Base
18
- def call
19
- content
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
- class ArgsComponent < ViewComponent::Base
24
- attr_reader :a, :b
25
-
26
- def initialize(a:, b:)
27
- @a = a
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
- def call
32
- "<p>#{a} and #{b}</p>"
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,14 @@
1
+ require 'view_component/base'
2
+
3
+ class ArgsComponent < ViewComponent::Base
4
+ attr_reader :a, :b
5
+
6
+ def initialize(a:, b:)
7
+ @a = a
8
+ @b = b
9
+ end
10
+
11
+ def call
12
+ "<p>#{a} and #{b}</p>"
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'view_component/base'
2
+
3
+ class DataComponent < ViewComponent::Base
4
+ def initialize(data_foo:)
5
+ @data_foo = data_foo
6
+ end
7
+
8
+ def call
9
+ "<div data-foo=\"#{@data_foo}\"></div>"
10
+ end
11
+ 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,7 @@
1
+ require 'view_component/base'
2
+
3
+ class TestComponent < ViewComponent::Base
4
+ def call
5
+ content
6
+ end
7
+ end