rux 1.1.2 → 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.
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>
@@ -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>&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
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
- # 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
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
- def call
42
- "<div data-foo=\"#{@data_foo}\"></div>"
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,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
@@ -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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rux
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-29 00:00:00.000000000 Z
11
+ date: 2023-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parser
@@ -54,7 +54,12 @@ files:
54
54
  - bin/ruxc
55
55
  - lib/rux.rb
56
56
  - lib/rux/ast.rb
57
+ - lib/rux/ast/attr_node.rb
58
+ - lib/rux/ast/attrs_node.rb
59
+ - lib/rux/ast/fragment_node.rb
57
60
  - lib/rux/ast/list_node.rb
61
+ - lib/rux/ast/root_node.rb
62
+ - lib/rux/ast/ruby_attr_node.rb
58
63
  - lib/rux/ast/ruby_node.rb
59
64
  - lib/rux/ast/string_node.rb
60
65
  - lib/rux/ast/tag_node.rb
@@ -76,9 +81,19 @@ files:
76
81
  - lib/rux/version.rb
77
82
  - lib/rux/visitor.rb
78
83
  - rux.gemspec
84
+ - spec/parser/attributes_spec.rb
85
+ - spec/parser/fragment_spec.rb
86
+ - spec/parser/html_safety_spec.rb
87
+ - spec/parser/slot_spec.rb
88
+ - spec/parser/tag_spec.rb
79
89
  - spec/parser_spec.rb
80
90
  - spec/render_spec.rb
81
91
  - spec/spec_helper.rb
92
+ - spec/support/components/args_component.rb
93
+ - spec/support/components/data_component.rb
94
+ - spec/support/components/table_component.rb
95
+ - spec/support/components/test_component.rb
96
+ - spec/support/view_component/base.rb
82
97
  homepage: http://github.com/camertron/rux
83
98
  licenses: []
84
99
  metadata: {}