phlex 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of phlex might be problematic. Click here for more details.

Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +21 -1
  3. data/Gemfile +26 -13
  4. data/README.md +1 -1
  5. data/SECURITY.md +1 -1
  6. data/bench.rb +7 -0
  7. data/config/sus.rb +15 -0
  8. data/docs/assets/application.css +6 -0
  9. data/docs/build.rb +2 -0
  10. data/docs/components/callout.rb +1 -1
  11. data/docs/components/code_block.rb +2 -2
  12. data/docs/components/code_span.rb +1 -1
  13. data/docs/components/example.rb +4 -4
  14. data/docs/components/heading.rb +2 -2
  15. data/docs/components/layout.rb +55 -32
  16. data/docs/components/markdown.rb +13 -28
  17. data/docs/components/nav/item.rb +1 -1
  18. data/docs/components/nav.rb +1 -1
  19. data/docs/components/tabs/tab.rb +1 -1
  20. data/docs/components/tabs.rb +1 -1
  21. data/docs/components/title.rb +2 -2
  22. data/docs/pages/application_page.rb +1 -1
  23. data/docs/pages/helpers.rb +5 -5
  24. data/docs/pages/library/collections.rb +4 -22
  25. data/docs/pages/rails/getting_started.rb +7 -3
  26. data/docs/pages/rails/helpers.rb +3 -1
  27. data/docs/pages/rails/layouts.rb +2 -2
  28. data/docs/pages/rails/rendering_views.rb +1 -1
  29. data/docs/pages/templates.rb +6 -6
  30. data/docs/pages/testing/capybara.rb +48 -0
  31. data/docs/pages/testing/getting_started.rb +44 -0
  32. data/docs/pages/testing/nokogiri.rb +83 -0
  33. data/docs/pages/testing/rails.rb +17 -0
  34. data/docs/pages/translations.rb +81 -0
  35. data/docs/pages/views.rb +56 -8
  36. data/fixtures/compiler_test_helpers.rb +19 -0
  37. data/fixtures/content.rb +60 -0
  38. data/fixtures/dummy/app/views/application_view.rb +8 -0
  39. data/fixtures/dummy/app/views/articles/form.rb +1 -1
  40. data/fixtures/dummy/app/views/card.rb +1 -1
  41. data/fixtures/dummy/app/views/comments/comment.rb +1 -1
  42. data/fixtures/dummy/app/views/comments/reaction.rb +1 -1
  43. data/fixtures/dummy/app/views/heading.rb +1 -1
  44. data/fixtures/layout.rb +5 -5
  45. data/fixtures/page.rb +18 -24
  46. data/fixtures/{test_helper.rb → rails_helper.rb} +3 -8
  47. data/fixtures/standard_element.rb +87 -0
  48. data/fixtures/view_helper.rb +1 -1
  49. data/fixtures/void_element.rb +31 -0
  50. data/lib/generators/phlex/collection/templates/collection.rb.erb +2 -1
  51. data/lib/generators/phlex/controller/USAGE +10 -0
  52. data/lib/generators/phlex/controller/controller_generator.rb +54 -0
  53. data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
  54. data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
  55. data/lib/generators/phlex/layout/templates/layout.rb.erb +2 -1
  56. data/lib/generators/phlex/page/templates/page.rb.erb +3 -1
  57. data/lib/generators/phlex/table/templates/table.rb.erb +3 -1
  58. data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
  59. data/lib/generators/phlex/view/view_generator.rb +9 -1
  60. data/lib/install/phlex.rb +10 -1
  61. data/lib/phlex/block.rb +2 -4
  62. data/lib/phlex/buffered.rb +6 -8
  63. data/lib/phlex/callable.rb +9 -0
  64. data/lib/phlex/collection.rb +2 -27
  65. data/lib/phlex/compiler/elements.rb +49 -0
  66. data/lib/phlex/compiler/generators/content.rb +103 -0
  67. data/lib/phlex/compiler/generators/element.rb +61 -0
  68. data/lib/phlex/compiler/nodes/base.rb +19 -0
  69. data/lib/phlex/compiler/nodes/call.rb +9 -0
  70. data/lib/phlex/compiler/nodes/command.rb +13 -0
  71. data/lib/phlex/compiler/nodes/fcall.rb +18 -0
  72. data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
  73. data/lib/phlex/compiler/nodes/vcall.rb +9 -0
  74. data/lib/phlex/compiler/optimizer.rb +66 -0
  75. data/lib/phlex/compiler/visitors/base.rb +15 -0
  76. data/lib/phlex/compiler/visitors/file.rb +23 -11
  77. data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
  78. data/lib/phlex/compiler/visitors/statements.rb +36 -0
  79. data/lib/phlex/compiler/visitors/view.rb +19 -0
  80. data/lib/phlex/compiler/visitors/view_method.rb +59 -0
  81. data/lib/phlex/compiler.rb +23 -3
  82. data/lib/phlex/elements.rb +57 -0
  83. data/lib/phlex/helpers.rb +59 -0
  84. data/lib/phlex/html/callbacks.rb +11 -0
  85. data/lib/phlex/html.rb +208 -47
  86. data/lib/phlex/markdown.rb +76 -0
  87. data/lib/phlex/rails/form.rb +67 -0
  88. data/lib/phlex/rails/helpers.rb +39 -2
  89. data/lib/phlex/rails.rb +10 -0
  90. data/lib/phlex/renderable.rb +9 -3
  91. data/lib/phlex/testing/capybara.rb +25 -0
  92. data/lib/phlex/testing/nokogiri.rb +24 -0
  93. data/lib/phlex/testing/rails.rb +19 -0
  94. data/lib/phlex/testing/view_helper.rb +15 -0
  95. data/lib/phlex/translation.rb +23 -0
  96. data/lib/phlex/turbo/frame.rb +21 -0
  97. data/lib/phlex/turbo/stream.rb +18 -0
  98. data/lib/phlex/version.rb +1 -1
  99. data/lib/phlex.rb +23 -24
  100. metadata +62 -14
  101. data/.rspec +0 -1
  102. data/fixtures/compilation/vcall.rb +0 -38
  103. data/lib/phlex/compiler/generators/standard_element.rb +0 -30
  104. data/lib/phlex/compiler/generators/void_element.rb +0 -29
  105. data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
  106. data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
  107. data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
  108. data/lib/phlex/compiler/visitors/component.rb +0 -28
  109. data/lib/phlex/compiler/visitors/component_method.rb +0 -28
  110. data/lib/phlex/view.rb +0 -229
data/lib/phlex/html.rb CHANGED
@@ -5,7 +5,7 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.0")
5
5
  end
6
6
 
7
7
  module Phlex
8
- module HTML
8
+ class HTML
9
9
  DOCTYPE = "<!DOCTYPE html>"
10
10
 
11
11
  STANDARD_ELEMENTS = {
@@ -126,52 +126,213 @@ module Phlex
126
126
 
127
127
  EVENT_ATTRIBUTES = %w[onabort onafterprint onbeforeprint onbeforeunload onblur oncanplay oncanplaythrough onchange onclick oncontextmenu oncopy oncuechange oncut ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended onerror onerror onfocus onhashchange oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart onmessage onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onoffline ononline onpagehide onpageshow onpaste onpause onplay onplaying onpopstate onprogress onratechange onreset onresize onscroll onsearch onseeked onseeking onselect onstalled onstorage onsubmit onsuspend ontimeupdate ontoggle onunload onvolumechange onwaiting onwheel].to_h { [_1, true] }.freeze
128
128
 
129
- def register_element(element, tag: element.name.tr("_", "-"))
130
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
131
- # frozen_string_literal: true
132
-
133
- def #{element}(content = nil, **attributes, &block)
134
- if content
135
- raise ArgumentError, %(👋 You can no longer pass content to #{element} as a positional argument.\n Instead, you can pass it as a block, e.g. #{element} { "Hello" })
136
- end
137
-
138
- if attributes.length > 0
139
- if block_given?
140
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">"
141
- yield_content(&block)
142
- @_target << "</#{tag}>"
143
- else
144
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << "></#{tag}>"
145
- end
146
- else
147
- if block_given?
148
- @_target << "<#{tag}>"
149
- yield_content(&block)
150
- @_target << "</#{tag}>"
151
- else
152
- @_target << "<#{tag}></#{tag}>"
153
- end
154
- end
155
-
156
- nil
157
- end
158
- RUBY
159
- end
160
-
161
- def register_void_element(element, tag: element.name.tr("_", "-"))
162
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
163
- # frozen_string_literal: true
164
-
165
- def #{element}(**attributes)
166
- if attributes.length > 0
167
- @_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << " />"
168
- else
169
- @_target << "<#{tag} />"
170
- end
171
-
172
- nil
173
- end
174
- RUBY
129
+ extend Elements
130
+ include Helpers
131
+ include Callable
132
+ include Renderable
133
+
134
+ class << self
135
+ attr_accessor :rendered_at_least_once
136
+
137
+ # def compile
138
+ # return if @compiled
139
+ # return unless name
140
+ # return if name.start_with? "#"
141
+ #
142
+ # Compiler.new(self).call
143
+ #
144
+ # @compiled = true
145
+ # end
146
+ #
147
+ # def compiled?
148
+ # !!@compiled
149
+ # end
150
+ end
151
+
152
+ def call(buffer = +"", view_context: nil, parent: nil, &block)
153
+ return buffer unless render?
154
+
155
+ raise "The same view instance shouldn't be rendered twice" if rendered?
156
+
157
+ @_rendered = true
158
+ @_target = buffer
159
+ @_view_context = view_context
160
+ @_parent = parent
161
+ @output_buffer = self
162
+
163
+ template(&block)
164
+
165
+ self.class.rendered_at_least_once ||= true
166
+
167
+ buffer
168
+ end
169
+
170
+ def rendered?
171
+ @_rendered ||= false
172
+ end
173
+
174
+ def render?
175
+ true
176
+ end
177
+
178
+ STANDARD_ELEMENTS.each do |method_name, tag|
179
+ register_element(method_name, tag: tag)
180
+ end
181
+
182
+ VOID_ELEMENTS.each do |method_name, tag|
183
+ register_void_element(method_name, tag: tag)
184
+ end
185
+
186
+ def yield_content(&block)
187
+ return unless block_given?
188
+
189
+ original_length = @_target.length
190
+ output = yield(self)
191
+ unchanged = (original_length == @_target.length)
192
+
193
+ if unchanged
194
+ case output
195
+ when String, Symbol, Integer, Float
196
+ text(output)
197
+ end
198
+ end
199
+
200
+ nil
201
+ end
202
+
203
+ def text(content)
204
+ @_target << _output(content)
205
+
206
+ nil
207
+ end
208
+
209
+ def _output(content)
210
+ case content
211
+ when String then Hescape.escape_html(content)
212
+ when Symbol then Hescape.escape_html(content.name)
213
+ else Hescape.escape_html(content.to_s)
214
+ end
215
+ end
216
+
217
+ def whitespace
218
+ @_target << " "
219
+
220
+ if block_given?
221
+ yield
222
+ @_target << " "
223
+ end
224
+
225
+ nil
226
+ end
227
+
228
+ def comment(content = "")
229
+ @_target << "<!-- " << Hescape.escape_html(content.to_s) << " -->"
230
+ nil
231
+ end
232
+
233
+ def doctype
234
+ @_target << DOCTYPE
235
+ nil
236
+ end
237
+
238
+ def unsafe_raw(content = nil, &block)
239
+ @_target << (content || instance_exec(&block))
240
+ nil
241
+ end
242
+
243
+ def html_safe?
244
+ true
245
+ end
246
+
247
+ def safe_append=(value)
248
+ return unless value
249
+
250
+ @_target << case value
251
+ when String then value
252
+ when Symbol then value.name
253
+ else value.to_s
254
+ end
255
+ end
256
+
257
+ def append=(value)
258
+ return unless value
259
+
260
+ if value.html_safe?
261
+ self.safe_append = value
262
+ else
263
+ @_target << case value
264
+ when String then Hescape.escape_html(value)
265
+ when Symbol then Hescape.escape_html(value.name)
266
+ else Hescape.escape_html(value.to_s)
267
+ end
268
+ end
269
+ end
270
+
271
+ def capture(&block)
272
+ return unless block_given?
273
+
274
+ original_buffer = @_target
275
+ new_buffer = +""
276
+ @_target = new_buffer
277
+
278
+ yield
279
+
280
+ @_target = original_buffer
281
+
282
+ new_buffer.html_safe
283
+ end
284
+
285
+ def helpers
286
+ @_view_context
287
+ end
288
+
289
+ def _attributes(**attributes)
290
+ if attributes[:href]&.start_with?(/\s*javascript/)
291
+ attributes[:href] = attributes[:href].sub(/^\s*(javascript:)+/, "")
292
+ end
293
+
294
+ buffer = +""
295
+ _build_attributes(attributes, buffer: buffer)
296
+
297
+ unless self.class.rendered_at_least_once
298
+ Phlex::ATTRIBUTE_CACHE[attributes.hash] = buffer.freeze
299
+ end
300
+
301
+ buffer
302
+ end
303
+
304
+ def _build_attributes(attributes, buffer:)
305
+ attributes.each do |k, v|
306
+ next unless v
307
+
308
+ name = case k
309
+ when String
310
+ k
311
+ when Symbol
312
+ k.name.tr("_", "-")
313
+ else
314
+ k.to_s
315
+ end
316
+
317
+ if HTML::EVENT_ATTRIBUTES[name] || name.match?(/[<>&"']/)
318
+ raise ArgumentError, "Unsafe attribute name detected: #{k}."
319
+ end
320
+
321
+ case v
322
+ when true
323
+ buffer << " " << name
324
+ when String
325
+ buffer << " " << name << '="' << Hescape.escape_html(v) << '"'
326
+ when Symbol
327
+ buffer << " " << name << '="' << Hescape.escape_html(v.name) << '"'
328
+ when Hash
329
+ _build_attributes(v.transform_keys { "#{k}-#{_1.name.tr('_', '-')}" }, buffer: buffer)
330
+ else
331
+ buffer << " " << name << '="' << Hescape.escape_html(v.to_s) << '"'
332
+ end
333
+ end
334
+
335
+ buffer
175
336
  end
176
337
  end
177
338
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "markly"
4
+
5
+ module Phlex
6
+ class Markdown < Phlex::HTML
7
+ def initialize(content)
8
+ @content = content
9
+ end
10
+
11
+ def template
12
+ visit(doc)
13
+ end
14
+
15
+ private
16
+
17
+ def doc
18
+ Markly.parse(@content)
19
+ end
20
+
21
+ def visit(node)
22
+ return if node.nil?
23
+
24
+ case node.type
25
+ in :document | :softbreak
26
+ visit_children(node)
27
+ in :text
28
+ text(node.string_content)
29
+ in :header
30
+ case node.header_level
31
+ in 1 then h1 { visit_children(node) }
32
+ in 2 then h2 { visit_children(node) }
33
+ in 3 then h3 { visit_children(node) }
34
+ in 4 then h4 { visit_children(node) }
35
+ in 5 then h5 { visit_children(node) }
36
+ in 6 then h6 { visit_children(node) }
37
+ end
38
+ in :paragraph
39
+ grandparent = node.parent&.parent
40
+
41
+ if grandparent&.type == :list && grandparent&.list_tight
42
+ visit_children(node)
43
+ else
44
+ p { visit_children(node) }
45
+ end
46
+ in :link
47
+ a(href: node.url) { visit_children(node) }
48
+ in :emph
49
+ em { visit_children(node) }
50
+ in :strong
51
+ strong { visit_children(node) }
52
+ in :list
53
+ case node.list_type
54
+ in :ordered_list then ol { visit_children(node) }
55
+ in :bullet_list then ul { visit_children(node) }
56
+ end
57
+ in :list_item
58
+ li { visit_children(node) }
59
+ in :code
60
+ whitespace { code { text(node.string_content) } }
61
+ in :code_block
62
+ code_block(node.string_content, language: node.fence_info) do |**attributes|
63
+ pre(**attributes) { text(node.string_content) }
64
+ end
65
+ end
66
+ end
67
+
68
+ def code_block(code, language:)
69
+ yield
70
+ end
71
+
72
+ def visit_children(node)
73
+ node.each { |c| visit(c) }
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Rails
5
+ class Form < Phlex::HTML
6
+ def initialize(model)
7
+ @model = model
8
+ end
9
+
10
+ def self.input_field(method_name, type:)
11
+ define_method method_name do |field, value: @model.attributes[field.to_s], **attributes|
12
+ input(
13
+ name: field_name(field),
14
+ type: type,
15
+ value: value,
16
+ **attributes
17
+ )
18
+ end
19
+ end
20
+
21
+ def template(&block)
22
+ form action: @url, method: @method do
23
+ authenticity_token_field
24
+ yield_content(&block)
25
+ end
26
+ end
27
+
28
+ def authenticity_token_field
29
+ input(
30
+ name: "authenticity_token",
31
+ type: "hidden",
32
+ value: @_view_context.form_authenticity_token
33
+ )
34
+ end
35
+
36
+ def submit(value)
37
+ input(
38
+ name: "commit",
39
+ type: "submit",
40
+ value: value
41
+ )
42
+ end
43
+
44
+ def url
45
+ @_view_context.url_for(@model)
46
+ end
47
+
48
+ def field_name(*field)
49
+ @_view_context.field_name(ActiveModel::Naming.param_key(@model.class), *field)
50
+ end
51
+
52
+ input_field :url_field, type: "url"
53
+ input_field :text_field, type: "text"
54
+ input_field :date_field, type: "date"
55
+ input_field :time_field, type: "time"
56
+ input_field :week_field, type: "week"
57
+ input_field :month_field, type: "month"
58
+ input_field :email_field, type: "email"
59
+ input_field :color_field, type: "color"
60
+ input_field :hidden_field, type: "hidden"
61
+ input_field :search_field, type: "search"
62
+ input_field :password_field, type: "password"
63
+ input_field :telephone_field, type: "tel"
64
+ input_field :datetime_local_field, type: "datetime-local"
65
+ end
66
+ end
67
+ end
@@ -28,11 +28,48 @@ module Phlex
28
28
  end
29
29
 
30
30
  module FormWith
31
+ class BufferedFormWith < Phlex::Buffered
32
+ alias_method :check_box, :__output_method__
33
+ alias_method :collection_check_boxes, :__output_method__
34
+ alias_method :collection_radio_buttons, :__output_method__
35
+ alias_method :collection_select, :__output_method__
36
+ alias_method :color_field, :__output_method__
37
+ alias_method :date_field, :__output_method__
38
+ alias_method :date_select, :__output_method__
39
+ alias_method :datetime_field, :__output_method__
40
+ alias_method :datetime_local_field, :__output_method__
41
+ alias_method :datetime_select, :__output_method__
42
+ alias_method :email_field, :__output_method__
43
+ alias_method :file_field, :__output_method__
44
+ alias_method :grouped_collection_select, :__output_method__
45
+ alias_method :hidden_field, :__output_method__
46
+ alias_method :label, :__output_method__
47
+ alias_method :month_field, :__output_method__
48
+ alias_method :number_field, :__output_method__
49
+ alias_method :password_field, :__output_method__
50
+ alias_method :phone_field, :__output_method__
51
+ alias_method :radio_button, :__output_method__
52
+ alias_method :range_field, :__output_method__
53
+ alias_method :search_field, :__output_method__
54
+ alias_method :select, :__output_method__
55
+ alias_method :submit, :__output_method__
56
+ alias_method :telephone_field, :__output_method__
57
+ alias_method :text_area, :__output_method__
58
+ alias_method :text_field, :__output_method__
59
+ alias_method :time_field, :__output_method__
60
+ alias_method :time_select, :__output_method__
61
+ alias_method :time_zone_select, :__output_method__
62
+ alias_method :url_field, :__output_method__
63
+ alias_method :week_field, :__output_method__
64
+ alias_method :weekday_select, :__output_method__
65
+ alias_method :button, :__output_method__
66
+ end
67
+
31
68
  def form_with(*args, **kwargs, &block)
32
- raw @_view_context.form_with(*args, **kwargs) { |form|
69
+ @_target << @_view_context.form_with(*args, **kwargs) { |form|
33
70
  capture do
34
71
  yield(
35
- Phlex::Buffered.new(form, buffer: @_target)
72
+ BufferedFormWith.new(form, buffer: @_target)
36
73
  )
37
74
  end
38
75
  }
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Phlex::Rails
6
+ Loader = Zeitwerk::Loader.new.tap do |loader|
7
+ loader.push_dir("#{__dir__}/rails", namespace: Phlex::Rails)
8
+ loader.setup
9
+ end
10
+ end
@@ -3,13 +3,13 @@
3
3
  module Phlex
4
4
  module Renderable
5
5
  def render(renderable, *args, **kwargs, &block)
6
- if renderable.is_a?(View)
6
+ if renderable.is_a?(HTML)
7
7
  if block_given? && !block.binding.receiver.is_a?(Phlex::Block)
8
8
  block = Phlex::Block.new(self, &block)
9
9
  end
10
10
 
11
11
  renderable.call(@_target, view_context: @_view_context, parent: self, &block)
12
- elsif renderable.is_a?(Class) && renderable < View
12
+ elsif renderable.is_a?(Class) && renderable < Phlex::HTML
13
13
  raise ArgumentError, "You tried to render the Phlex view class: #{renderable.name} but you probably meant to render an instance of that class instead."
14
14
  else
15
15
  @_target << @_view_context.render(renderable, *args, **kwargs, &block)
@@ -26,7 +26,13 @@ module Phlex
26
26
  output = yield(*args, **kwargs)
27
27
  unchanged = (original_length == @_target.length)
28
28
 
29
- text(output) if unchanged && output.is_a?(String)
29
+ if unchanged
30
+ if defined?(ActiveSupport::SafeBuffer) && output.is_a?(ActiveSupport::SafeBuffer)
31
+ unsafe_raw(output)
32
+ else
33
+ text(output)
34
+ end
35
+ end
30
36
  end
31
37
  end.html_safe
32
38
  else
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "capybara"
4
+ require_relative "view_helper"
5
+
6
+ module Phlex::Testing
7
+ module Capybara
8
+ module ViewHelper
9
+ include Phlex::Testing::ViewHelper
10
+
11
+ def self.included(klass)
12
+ if defined?(Minitest::Test) && klass < Minitest::Test
13
+ require "capybara/minitest"
14
+ include ::Capybara::Minitest::Assertions
15
+ end
16
+ end
17
+
18
+ attr_accessor :page
19
+
20
+ def render(view, &block)
21
+ @page = ::Capybara::Node::Simple.new(super)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require_relative "view_helper"
5
+
6
+ module Phlex::Testing
7
+ module Nokogiri
8
+ module DocumentHelper
9
+ include Phlex::Testing::ViewHelper
10
+
11
+ def render(view, &block)
12
+ ::Nokogiri::HTML5(super)
13
+ end
14
+ end
15
+
16
+ module FragmentHelper
17
+ include Phlex::Testing::ViewHelper
18
+
19
+ def render(view, &block)
20
+ ::Nokogiri::HTML5.fragment(super)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "view_helper"
4
+
5
+ module Phlex::Testing
6
+ module Rails
7
+ module ViewHelper
8
+ include Phlex::Testing::ViewHelper
9
+
10
+ def view_context
11
+ controller.view_context
12
+ end
13
+
14
+ def controller
15
+ @controller ||= ActionView::TestCase::TestController.new
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Testing
5
+ module ViewHelper
6
+ def render(view, &block)
7
+ view.call(view_context: view_context, &block)
8
+ end
9
+
10
+ def view_context
11
+ nil
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Translation
5
+ def self.included(view)
6
+ view.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ attr_writer :translation_path
11
+
12
+ def translation_path
13
+ @translation_path ||= name&.split("::")&.join(".")&.downcase.to_s
14
+ end
15
+ end
16
+
17
+ def translate(key, **options)
18
+ key = "#{self.class.translation_path}#{key}" if key.start_with?(".")
19
+
20
+ ::I18n.translate(key, **options)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Turbo
5
+ class Frame < Phlex::HTML
6
+ register_element :turbo_frame
7
+
8
+ def initialize(src:, loading:, disabled:, target:, autoscroll:)
9
+ @src = src
10
+ @loading = loading
11
+ @disabled = disabled
12
+ @target = target
13
+ @autoscroll = autoscroll
14
+ end
15
+
16
+ def template(&content)
17
+ turbo_frame(src: @src, loading: @loading, disabled: @disabled, target: @target, autoscroll: @autoscroll, &content)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlex
4
+ module Turbo
5
+ class Stream < Phlex::HTML
6
+ register_element :turbo_stream
7
+
8
+ def initialize(action:, **attributes)
9
+ @action = action
10
+ @attributes = attributes
11
+ end
12
+
13
+ def template(&content)
14
+ turbo_stream(action: @action, **@attributes, &content)
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/phlex/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Phlex
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.1"
5
5
  end