loading
Generated 2026-02-06T13:01:20+00:00

All Files ( 94.19% covered at 58.36 hits/line )

71 files in total.
2633 relevant lines, 2480 lines covered and 153 lines missed. ( 94.19% )
4017 total branches, 1292 branches covered and 2725 branches missed. ( 32.16% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex.rb 80.56 % 74 36 29 7 23.31 50.00 % 6 3 3
lib/phlex/compiler.rb 96.30 % 57 27 26 1 6.96 50.00 % 2 1 1
lib/phlex/compiler/class_compiler.rb 84.00 % 47 25 21 4 7.12 60.00 % 10 6 4
lib/phlex/compiler/compactor.rb 88.00 % 74 25 22 3 152.48 80.00 % 10 8 2
lib/phlex/compiler/file_compiler.rb 88.89 % 52 27 24 3 5.89 50.00 % 2 1 1
lib/phlex/compiler/method_compiler.rb 88.72 % 579 195 173 22 20.67 76.47 % 85 65 20
lib/phlex/csv.rb 95.35 % 265 129 123 6 31.64 94.12 % 51 48 3
lib/phlex/error.rb 100.00 % 5 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/errors/argument_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/double_render_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/name_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/runtime_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/fifo.rb 90.00 % 55 30 27 3 206.77 100.00 % 7 7 0
lib/phlex/fifo_cache_store.rb 69.57 % 49 23 16 7 31.43 40.00 % 10 4 6
lib/phlex/helpers.rb 95.83 % 51 24 23 1 6.25 94.12 % 17 16 1
lib/phlex/html.rb 93.88 % 100 49 46 3 97.69 89.29 % 28 25 3
lib/phlex/html/standard_elements.rb 100.00 % 633 105 105 0 1.00 100.00 % 0 0 0
lib/phlex/html/void_elements.rb 100.00 % 66 14 14 0 1.00 100.00 % 0 0 0
lib/phlex/kit.rb 85.00 % 83 40 34 6 2.83 66.67 % 15 10 5
lib/phlex/sgml.rb 86.54 % 454 208 180 28 254.61 73.12 % 93 68 25
lib/phlex/sgml/attributes.rb 95.95 % 313 148 142 6 42.64 94.96 % 119 113 6
lib/phlex/sgml/elements.rb 94.29 % 171 70 66 4 476.27 24.83 % 3488 866 2622
lib/phlex/sgml/safe_object.rb 100.00 % 7 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/sgml/safe_value.rb 100.00 % 11 5 5 0 3.20 100.00 % 0 0 0
lib/phlex/sgml/state.rb 100.00 % 123 74 74 0 304.68 95.45 % 22 21 1
lib/phlex/svg.rb 86.11 % 66 36 31 5 71.75 70.00 % 20 14 6
lib/phlex/svg/standard_elements.rb 100.00 % 421 66 66 0 1.00 100.00 % 0 0 0
quickdraw/attribute_cache_expansion.test.rb 100.00 % 15 4 4 0 1.00 100.00 % 0 0 0
quickdraw/caching.test.rb 100.00 % 29 17 17 0 1.18 100.00 % 0 0 0
quickdraw/compilation_equivalence.test.rb 100.00 % 30 18 18 0 4.56 100.00 % 0 0 0
quickdraw/compilation_equivalence_cases/attributes.rb 66.67 % 22 3 2 1 0.67 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/basic.rb 40.00 % 9 5 2 3 0.40 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/comment.rb 50.00 % 8 4 2 2 0.50 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/heredoc.rb 57.14 % 16 7 4 3 0.57 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/interpolated_string.rb 28.57 % 11 7 2 5 0.29 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/override_elements.rb 33.33 % 16 9 3 6 0.33 50.00 % 8 4 4
quickdraw/compilation_equivalence_cases/plain.rb 33.33 % 10 6 2 4 0.33 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/raw.rb 40.00 % 9 5 2 3 0.40 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/whitespace.rb 18.18 % 16 11 2 9 0.18 50.00 % 2 1 1
quickdraw/compiler.test.rb 80.00 % 41 5 4 1 0.80 100.00 % 0 0 0
quickdraw/context.test.rb 100.00 % 41 20 20 0 1.15 100.00 % 0 0 0
quickdraw/csv.test.rb 100.00 % 286 94 94 0 3.23 100.00 % 0 0 0
quickdraw/fifo.test.rb 100.00 % 30 15 15 0 7.60 100.00 % 0 0 0
quickdraw/fifo_cache_store.test.rb 90.00 % 37 20 18 2 0.90 100.00 % 0 0 0
quickdraw/helpers/grab.test.rb 100.00 % 15 8 8 0 1.00 100.00 % 0 0 0
quickdraw/helpers/mix.test.rb 100.00 % 88 47 47 0 1.00 100.00 % 0 0 0
quickdraw/html.test.rb 100.00 % 9 4 4 0 1.00 100.00 % 0 0 0
quickdraw/html/doctype.test.rb 100.00 % 17 8 8 0 1.00 100.00 % 0 0 0
quickdraw/html/standard_elements.test.rb 100.00 % 45 22 22 0 117.14 100.00 % 0 0 0
quickdraw/html/svg.test.rb 100.00 % 25 12 12 0 1.00 100.00 % 0 0 0
quickdraw/html/void_elements.test.rb 100.00 % 35 17 17 0 10.71 100.00 % 0 0 0
quickdraw/inline.test.rb 100.00 % 30 14 14 0 1.29 100.00 % 0 0 0
quickdraw/kit.test.rb 100.00 % 29 16 16 0 1.25 100.00 % 0 0 0
quickdraw/selective_rendering_from_cache.test.rb 100.00 % 93 58 58 0 3.72 100.00 % 0 0 0
quickdraw/sgml.test.rb 94.44 % 41 18 17 1 1.00 100.00 % 0 0 0
quickdraw/sgml/attributes.test.rb 100.00 % 732 439 439 0 1.44 100.00 % 0 0 0
quickdraw/sgml/callbacks.test.rb 100.00 % 33 17 17 0 1.41 100.00 % 0 0 0
quickdraw/sgml/capture.test.rb 100.00 % 17 8 8 0 1.13 100.00 % 0 0 0
quickdraw/sgml/comment.test.rb 100.00 % 25 12 12 0 1.17 100.00 % 0 0 0
quickdraw/sgml/conditional_rendering.test.rb 100.00 % 33 17 17 0 1.18 100.00 % 0 0 0
quickdraw/sgml/content_yielding.test.rb 100.00 % 31 15 15 0 1.07 100.00 % 0 0 0
quickdraw/sgml/plain.test.rb 100.00 % 38 21 21 0 1.29 100.00 % 0 0 0
quickdraw/sgml/raw.test.rb 100.00 % 30 16 16 0 1.38 100.00 % 0 0 0
quickdraw/sgml/safe.test.rb 100.00 % 36 16 16 0 1.13 100.00 % 0 0 0
quickdraw/sgml/selective_rendering.test.rb 96.00 % 118 75 72 3 1.81 100.00 % 0 0 0
quickdraw/sgml/to_proc.test.rb 100.00 % 33 16 16 0 1.06 100.00 % 0 0 0
quickdraw/sgml/whitespace.test.rb 100.00 % 27 15 15 0 1.07 100.00 % 0 0 0
quickdraw/svg.test.rb 100.00 % 42 21 21 0 1.00 100.00 % 0 0 0
quickdraw/svg/standard_elements.test.rb 100.00 % 35 17 17 0 67.88 100.00 % 0 0 0
quickdraw/tag.test.rb 98.78 % 187 82 81 1 56.44 100.00 % 0 0 0
quickdraw/💪.test.rb 100.00 % 13 6 6 0 1.17 100.00 % 0 0 0

SGML ( 92.49% covered at 227.61 hits/line )

6 files in total.
506 relevant lines, 468 lines covered and 38 lines missed. ( 92.49% )
3722 total branches, 1068 branches covered and 2654 branches missed. ( 28.69% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/sgml.rb 86.54 % 454 208 180 28 254.61 73.12 % 93 68 25
lib/phlex/sgml/attributes.rb 95.95 % 313 148 142 6 42.64 94.96 % 119 113 6
lib/phlex/sgml/elements.rb 94.29 % 171 70 66 4 476.27 24.83 % 3488 866 2622
lib/phlex/sgml/safe_object.rb 100.00 % 7 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/sgml/safe_value.rb 100.00 % 11 5 5 0 3.20 100.00 % 0 0 0
lib/phlex/sgml/state.rb 100.00 % 123 74 74 0 304.68 95.45 % 22 21 1

HTML ( 98.21% covered at 29.2 hits/line )

3 files in total.
168 relevant lines, 165 lines covered and 3 lines missed. ( 98.21% )
28 total branches, 25 branches covered and 3 branches missed. ( 89.29% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/html.rb 93.88 % 100 49 46 3 97.69 89.29 % 28 25 3
lib/phlex/html/standard_elements.rb 100.00 % 633 105 105 0 1.00 100.00 % 0 0 0
lib/phlex/html/void_elements.rb 100.00 % 66 14 14 0 1.00 100.00 % 0 0 0

SVG ( 95.1% covered at 25.97 hits/line )

2 files in total.
102 relevant lines, 97 lines covered and 5 lines missed. ( 95.1% )
20 total branches, 14 branches covered and 6 branches missed. ( 70.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/svg.rb 86.11 % 66 36 31 5 71.75 70.00 % 20 14 6
lib/phlex/svg/standard_elements.rb 100.00 % 421 66 66 0 1.00 100.00 % 0 0 0

CSV ( 95.35% covered at 31.64 hits/line )

1 files in total.
129 relevant lines, 123 lines covered and 6 lines missed. ( 95.35% )
51 total branches, 48 branches covered and 3 branches missed. ( 94.12% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex/csv.rb 95.35 % 265 129 123 6 31.64 94.12 % 51 48 3

Ungrouped ( 94.16% covered at 15.55 hits/line )

59 files in total.
1728 relevant lines, 1627 lines covered and 101 lines missed. ( 94.16% )
196 total branches, 137 branches covered and 59 branches missed. ( 69.9% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/phlex.rb 80.56 % 74 36 29 7 23.31 50.00 % 6 3 3
lib/phlex/compiler.rb 96.30 % 57 27 26 1 6.96 50.00 % 2 1 1
lib/phlex/compiler/class_compiler.rb 84.00 % 47 25 21 4 7.12 60.00 % 10 6 4
lib/phlex/compiler/compactor.rb 88.00 % 74 25 22 3 152.48 80.00 % 10 8 2
lib/phlex/compiler/file_compiler.rb 88.89 % 52 27 24 3 5.89 50.00 % 2 1 1
lib/phlex/compiler/method_compiler.rb 88.72 % 579 195 173 22 20.67 76.47 % 85 65 20
lib/phlex/error.rb 100.00 % 5 1 1 0 1.00 100.00 % 0 0 0
lib/phlex/errors/argument_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/double_render_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/name_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/errors/runtime_error.rb 100.00 % 5 2 2 0 1.00 100.00 % 0 0 0
lib/phlex/fifo.rb 90.00 % 55 30 27 3 206.77 100.00 % 7 7 0
lib/phlex/fifo_cache_store.rb 69.57 % 49 23 16 7 31.43 40.00 % 10 4 6
lib/phlex/helpers.rb 95.83 % 51 24 23 1 6.25 94.12 % 17 16 1
lib/phlex/kit.rb 85.00 % 83 40 34 6 2.83 66.67 % 15 10 5
quickdraw/attribute_cache_expansion.test.rb 100.00 % 15 4 4 0 1.00 100.00 % 0 0 0
quickdraw/caching.test.rb 100.00 % 29 17 17 0 1.18 100.00 % 0 0 0
quickdraw/compilation_equivalence.test.rb 100.00 % 30 18 18 0 4.56 100.00 % 0 0 0
quickdraw/compilation_equivalence_cases/attributes.rb 66.67 % 22 3 2 1 0.67 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/basic.rb 40.00 % 9 5 2 3 0.40 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/comment.rb 50.00 % 8 4 2 2 0.50 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/heredoc.rb 57.14 % 16 7 4 3 0.57 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/interpolated_string.rb 28.57 % 11 7 2 5 0.29 50.00 % 2 1 1
quickdraw/compilation_equivalence_cases/override_elements.rb 33.33 % 16 9 3 6 0.33 50.00 % 8 4 4
quickdraw/compilation_equivalence_cases/plain.rb 33.33 % 10 6 2 4 0.33 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/raw.rb 40.00 % 9 5 2 3 0.40 50.00 % 4 2 2
quickdraw/compilation_equivalence_cases/whitespace.rb 18.18 % 16 11 2 9 0.18 50.00 % 2 1 1
quickdraw/compiler.test.rb 80.00 % 41 5 4 1 0.80 100.00 % 0 0 0
quickdraw/context.test.rb 100.00 % 41 20 20 0 1.15 100.00 % 0 0 0
quickdraw/csv.test.rb 100.00 % 286 94 94 0 3.23 100.00 % 0 0 0
quickdraw/fifo.test.rb 100.00 % 30 15 15 0 7.60 100.00 % 0 0 0
quickdraw/fifo_cache_store.test.rb 90.00 % 37 20 18 2 0.90 100.00 % 0 0 0
quickdraw/helpers/grab.test.rb 100.00 % 15 8 8 0 1.00 100.00 % 0 0 0
quickdraw/helpers/mix.test.rb 100.00 % 88 47 47 0 1.00 100.00 % 0 0 0
quickdraw/html.test.rb 100.00 % 9 4 4 0 1.00 100.00 % 0 0 0
quickdraw/html/doctype.test.rb 100.00 % 17 8 8 0 1.00 100.00 % 0 0 0
quickdraw/html/standard_elements.test.rb 100.00 % 45 22 22 0 117.14 100.00 % 0 0 0
quickdraw/html/svg.test.rb 100.00 % 25 12 12 0 1.00 100.00 % 0 0 0
quickdraw/html/void_elements.test.rb 100.00 % 35 17 17 0 10.71 100.00 % 0 0 0
quickdraw/inline.test.rb 100.00 % 30 14 14 0 1.29 100.00 % 0 0 0
quickdraw/kit.test.rb 100.00 % 29 16 16 0 1.25 100.00 % 0 0 0
quickdraw/selective_rendering_from_cache.test.rb 100.00 % 93 58 58 0 3.72 100.00 % 0 0 0
quickdraw/sgml.test.rb 94.44 % 41 18 17 1 1.00 100.00 % 0 0 0
quickdraw/sgml/attributes.test.rb 100.00 % 732 439 439 0 1.44 100.00 % 0 0 0
quickdraw/sgml/callbacks.test.rb 100.00 % 33 17 17 0 1.41 100.00 % 0 0 0
quickdraw/sgml/capture.test.rb 100.00 % 17 8 8 0 1.13 100.00 % 0 0 0
quickdraw/sgml/comment.test.rb 100.00 % 25 12 12 0 1.17 100.00 % 0 0 0
quickdraw/sgml/conditional_rendering.test.rb 100.00 % 33 17 17 0 1.18 100.00 % 0 0 0
quickdraw/sgml/content_yielding.test.rb 100.00 % 31 15 15 0 1.07 100.00 % 0 0 0
quickdraw/sgml/plain.test.rb 100.00 % 38 21 21 0 1.29 100.00 % 0 0 0
quickdraw/sgml/raw.test.rb 100.00 % 30 16 16 0 1.38 100.00 % 0 0 0
quickdraw/sgml/safe.test.rb 100.00 % 36 16 16 0 1.13 100.00 % 0 0 0
quickdraw/sgml/selective_rendering.test.rb 96.00 % 118 75 72 3 1.81 100.00 % 0 0 0
quickdraw/sgml/to_proc.test.rb 100.00 % 33 16 16 0 1.06 100.00 % 0 0 0
quickdraw/sgml/whitespace.test.rb 100.00 % 27 15 15 0 1.07 100.00 % 0 0 0
quickdraw/svg.test.rb 100.00 % 42 21 21 0 1.00 100.00 % 0 0 0
quickdraw/svg/standard_elements.test.rb 100.00 % 35 17 17 0 67.88 100.00 % 0 0 0
quickdraw/tag.test.rb 98.78 % 187 82 81 1 56.44 100.00 % 0 0 0
quickdraw/💪.test.rb 100.00 % 13 6 6 0 1.17 100.00 % 0 0 0

lib/phlex.rb

80.56% lines covered

50.0% branches covered

36 relevant lines. 29 lines covered and 7 lines missed.
6 total branches, 3 branches covered and 3 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "erb"
  3. 1 require "set"
  4. 1 require "zeitwerk"
  5. 1 module Phlex
  6. 1 Loader = Zeitwerk::Loader.for_gem.tap do |loader|
  7. 1 loader.ignore("#{__dir__}/ruby_lsp")
  8. 1 loader.inflector.inflect(
  9. "csv" => "CSV",
  10. "fifo" => "FIFO",
  11. "fifo_cache_store" => "FIFOCacheStore",
  12. "html" => "HTML",
  13. "sgml" => "SGML",
  14. "svg" => "SVG",
  15. )
  16. 1 loader.collapse("#{__dir__}/phlex/errors")
  17. 1 loader.setup
  18. end
  19. 1 Escape = ERB::Escape
  20. 1 DEPLOYED_AT = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
  21. 1 CACHED_FILES = Set.new
  22. 1 ATTRIBUTE_CACHE = FIFO.new
  23. 1 UNBOUND_INSTANCE_METHOD_METHOD = Module.instance_method(:instance_method)
  24. 1 def self.__expand_attribute_cache__(file_path)
  25. 703 else: 662 then: 41 unless CACHED_FILES.include?(file_path)
  26. 41 CACHED_FILES << file_path
  27. 41 Phlex::ATTRIBUTE_CACHE.expand(File.size(file_path))
  28. end
  29. end
  30. # Generate an HTML string using Phlex’ HTML DSL
  31. 1 def self.html(&block)
  32. 2 HTML.call do |component|
  33. 2 receiver = block.binding.receiver
  34. 2 receiver.instance_variables.each do |ivar|
  35. 9 then: 0 else: 9 next if component.instance_variable_defined?(ivar)
  36. 9 value = receiver.instance_variable_get(ivar)
  37. 9 component.instance_variable_set(ivar, value)
  38. end
  39. 2 component.instance_exec(receiver, &block)
  40. end
  41. end
  42. # Generate an SVG string using Phlex’ SVG DSL
  43. 1 def self.svg(&block)
  44. SVG.call do |component|
  45. receiver = block.binding.receiver
  46. receiver.instance_variables.each do |ivar|
  47. then: 0 else: 0 next if component.instance_variable_defined?(ivar)
  48. value = receiver.instance_variable_get(ivar)
  49. component.instance_variable_set(ivar, value)
  50. end
  51. component.instance_exec(receiver, &block)
  52. end
  53. end
  54. end
  55. 1 def 💪
  56. 1 Phlex
  57. end

lib/phlex/compiler.rb

96.3% lines covered

50.0% branches covered

27 relevant lines. 26 lines covered and 1 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "prism"
  3. 1 require "refract"
  4. 1 module Phlex::Compiler
  5. 1 MAP = {}
  6. 1 Error = Class.new(StandardError)
  7. 1 Concat = Data.define(:node) do
  8. 1 def start_line = nil
  9. 1 def accept(visitor) = self
  10. end
  11. 1 def self.compile(component)
  12. 11 path, line = Object.const_source_location(component.name)
  13. 11 compile_file(path)
  14. end
  15. 1 def self.compile_file(path)
  16. 11 else: 11 then: 0 unless File.exist?(path)
  17. raise ArgumentError, "Can’t compile #{path} because it doesn’t exist."
  18. end
  19. 11 require(path)
  20. 11 source = File.read(path)
  21. 11 tree = Prism.parse(source).value
  22. 11 refract = Refract::Converter.new.visit(tree)
  23. 11 last_line = source.count("\n")
  24. 11 starting_line = last_line + 1
  25. 11 results = FileCompiler.new(path).compile(refract)
  26. 11 result = Refract::StatementsNode.new(
  27. body: results.map do |result|
  28. 11 result.namespace.reverse_each.reduce(
  29. result.compiled_snippets
  30. ) do |body, scope|
  31. 13 scope.copy(
  32. body: Refract::StatementsNode.new(
  33. body: [body]
  34. )
  35. )
  36. end
  37. end
  38. )
  39. 11 formatting_result = Refract::Formatter.new(starting_line:).format_node(result)
  40. 11 MAP[path] = formatting_result.source_map
  41. 11 eval("# frozen_string_literal: true\n#{formatting_result.source}", TOPLEVEL_BINDING, path, starting_line - 1)
  42. end
  43. end

lib/phlex/compiler/class_compiler.rb

84.0% lines covered

60.0% branches covered

25 relevant lines. 21 lines covered and 4 lines missed.
10 total branches, 6 branches covered and 4 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::Compiler::ClassCompiler < Refract::Visitor
  3. 1 def initialize(component, path)
  4. 11 super()
  5. 11 @component = component
  6. 11 @path = path
  7. 11 @compiled_snippets = []
  8. end
  9. 1 def compile(node)
  10. 11 visit(node.body)
  11. 11 @compiled_snippets.compact.freeze
  12. end
  13. 1 visit Refract::DefNode do |node|
  14. 14 then: 1 else: 13 return if node.name == :initialize
  15. 13 then: 0 else: 13 return if node.receiver
  16. method = begin
  17. 13 Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name)
  18. rescue NameError
  19. nil
  20. end
  21. 13 else: 13 then: 0 return unless method
  22. 13 path, lineno = method.source_location
  23. 13 else: 13 then: 0 return unless @path == path
  24. 13 else: 13 then: 0 return unless node.start_line == lineno
  25. 13 @compiled_snippets << Phlex::Compiler::MethodCompiler.new(
  26. @component
  27. ).compile(node)
  28. end
  29. 1 visit Refract::ClassNode do |node|
  30. nil
  31. end
  32. 1 visit Refract::ModuleNode do |node|
  33. nil
  34. end
  35. 1 visit Refract::BlockNode do |node|
  36. nil
  37. end
  38. end

lib/phlex/compiler/compactor.rb

88.0% lines covered

80.0% branches covered

25 relevant lines. 22 lines covered and 3 lines missed.
10 total branches, 8 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::Compiler::Compactor < Refract::MutationVisitor
  3. 1 visit Refract::StatementsNode do |node|
  4. 63 queue = []
  5. 63 results = []
  6. 63 current_buffer = nil
  7. 63 nil_context = false
  8. 160 node.body.reverse_each { |n| queue << n }
  9. 729 body: 603 while (child_node = queue.pop)
  10. 603 case child_node
  11. when: 288 when Refract::StatementsNode
  12. 794 child_node.body.reverse_each { |n| queue << n }
  13. when: 193 when Phlex::Compiler::Concat
  14. 193 then: 173 if current_buffer
  15. 173 current_buffer << child_node.node
  16. 173 else: 173 then: 0 unless nil_context
  17. results << Refract::NilNode.new
  18. nil_context = true
  19. end
  20. else: 20 else
  21. 20 current_buffer = [child_node.node]
  22. 20 results << Refract::ParenthesesNode.new(
  23. body: Refract::StatementsNode.new(
  24. body: [
  25. Refract::IfNode.new(
  26. inline: false,
  27. predicate: Refract::LocalVariableReadNode.new(
  28. name: :__phlex_should_render__
  29. ),
  30. statements: Refract::StatementsNode.new(
  31. body: [
  32. Refract::CallNode.new(
  33. receiver: Refract::CallNode.new(
  34. name: :__phlex_buffer__,
  35. ),
  36. name: :<<,
  37. arguments: Refract::ArgumentsNode.new(
  38. arguments: [
  39. Refract::InterpolatedStringNode.new(
  40. parts: current_buffer
  41. ),
  42. ]
  43. )
  44. ),
  45. ]
  46. )
  47. ),
  48. Refract::NilNode.new,
  49. ]
  50. )
  51. )
  52. 20 nil_context = true
  53. end
  54. else: 122 else
  55. 122 resolved = visit(child_node)
  56. 122 case resolved
  57. when: 0 when Refract::StatementsNode
  58. resolved.body.reverse_each { |n| queue << n }
  59. else: 122 else
  60. 122 current_buffer = nil
  61. 122 results << resolved
  62. 122 nil_context = false
  63. end
  64. end
  65. end
  66. 63 node.copy(
  67. body: results
  68. )
  69. end
  70. end

lib/phlex/compiler/file_compiler.rb

88.89% lines covered

50.0% branches covered

27 relevant lines. 24 lines covered and 3 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::Compiler::FileCompiler < Refract::Visitor
  3. 1 Result = Data.define(:namespace, :compiled_snippets)
  4. 1 def initialize(path)
  5. 11 super()
  6. 11 @path = path
  7. 11 @current_namespace = []
  8. 11 @results = []
  9. end
  10. 1 def compile(node)
  11. 11 visit(node)
  12. 11 @results.freeze
  13. end
  14. 1 visit Refract::ModuleNode do |node|
  15. 2 @current_namespace.push(node)
  16. 2 super(node)
  17. 2 @current_namespace.pop
  18. end
  19. 1 visit Refract::ClassNode do |node|
  20. 11 @current_namespace.push(node)
  21. 11 namespace = @current_namespace.map do |node|
  22. 13 Refract::Formatter.new.format_node(node.constant_path).source
  23. end.join("::")
  24. 11 const = eval(namespace, TOPLEVEL_BINDING)
  25. 11 then: 11 if Class === const && Phlex::SGML > const
  26. 11 @results << Result.new(
  27. namespace: @current_namespace.dup.freeze,
  28. compiled_snippets: Phlex::Compiler::ClassCompiler.new(const, @path).compile(node)
  29. )
  30. else: 0 else
  31. super(node)
  32. end
  33. 11 @current_namespace.pop
  34. end
  35. 1 visit Refract::DefNode do |node|
  36. nil
  37. end
  38. 1 visit Refract::BlockNode do |node|
  39. nil
  40. end
  41. end

lib/phlex/compiler/method_compiler.rb

88.72% lines covered

76.47% branches covered

195 relevant lines. 173 lines covered and 22 lines missed.
85 total branches, 65 branches covered and 20 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "prism"
  3. 1 module Phlex::Compiler
  4. 1 class MethodCompiler < Refract::MutationVisitor
  5. 1 def initialize(component)
  6. 14 super()
  7. 14 @component = component
  8. 14 @preamble = []
  9. 14 @optimized = false
  10. end
  11. 1 def compile(node)
  12. 14 tree = visit(node)
  13. 13 then: 12 if @optimized
  14. 12 Compactor.new.visit(
  15. tree
  16. )
  17. else: 1 else
  18. 1 nil
  19. end
  20. end
  21. 1 visit Refract::ClassNode do |node|
  22. node
  23. end
  24. 1 visit Refract::ModuleNode do |node|
  25. node
  26. end
  27. 1 visit Refract::DefNode do |node|
  28. 14 then: 14 if @stack.size == 1
  29. 14 node.copy(
  30. body: Refract::BeginNode.new(
  31. statements: Refract::StatementsNode.new(
  32. body: [
  33. Refract::StatementsNode.new(
  34. body: @preamble
  35. ),
  36. Refract::NilNode.new,
  37. visit(node.body),
  38. ]
  39. ),
  40. rescue_clause: Refract::RescueNode.new(
  41. exceptions: [],
  42. reference: Refract::LocalVariableTargetNode.new(
  43. name: :__phlex_exception__
  44. ),
  45. statements: Refract::StatementsNode.new(
  46. body: [
  47. Refract::CallNode.new(
  48. receiver: Refract::ConstantReadNode.new(
  49. name: :Kernel
  50. ),
  51. name: :raise,
  52. arguments: Refract::ArgumentsNode.new(
  53. arguments: [
  54. Refract::CallNode.new(
  55. name: :__map_exception__,
  56. arguments: Refract::ArgumentsNode.new(
  57. arguments: [
  58. Refract::LocalVariableReadNode.new(
  59. name: :__phlex_exception__
  60. ),
  61. ]
  62. )
  63. ),
  64. ]
  65. )
  66. ),
  67. ]
  68. ),
  69. subsequent: nil
  70. ),
  71. else_clause: nil,
  72. ensure_clause: nil
  73. )
  74. )
  75. else: 0 else
  76. node
  77. end
  78. end
  79. 1 visit Refract::CallNode do |node|
  80. 63 then: 62 else: 1 if nil == node.receiver
  81. 62 then: 41 if (tag = standard_element?(node))
  82. 41 else: 21 return compile_standard_element(node, tag)
  83. 21 then: 6 elsif (tag = void_element?(node))
  84. 6 else: 15 return compile_void_element(node, tag)
  85. 15 then: 2 elsif whitespace_helper?(node)
  86. 2 else: 13 return compile_whitespace_helper(node)
  87. 13 then: 0 elsif doctype_helper?(node)
  88. else: 13 return compile_doctype_helper(node)
  89. 13 then: 6 elsif plain_helper?(node)
  90. 6 else: 7 return compile_plain_helper(node)
  91. 7 then: 0 elsif fragment_helper?(node)
  92. else: 7 return compile_fragment_helper(node)
  93. 7 then: 2 elsif comment_helper?(node)
  94. 2 else: 5 return compile_comment_helper(node)
  95. 5 then: 1 else: 4 elsif raw_helper?(node)
  96. 1 return compile_raw_helper(node)
  97. end
  98. end
  99. 5 super(node)
  100. end
  101. 1 visit Refract::BlockNode do |node|
  102. 3 then: 3 if node.body
  103. 3 node.copy(
  104. body: compile_block_body_node(
  105. node.body
  106. )
  107. )
  108. else: 0 else
  109. node
  110. end
  111. end
  112. 1 def compile_standard_element(node, tag)
  113. 41 node => Refract::CallNode
  114. 40 Refract::StatementsNode.new(
  115. body: [
  116. raw("<#{tag}"),
  117. *(
  118. 40 then: 16 else: 24 if node.arguments
  119. 16 compile_phlex_attributes(node.arguments)
  120. end
  121. ),
  122. raw(">"),
  123. *(
  124. 40 then: 38 else: 2 if node.block
  125. 38 compile_phlex_block(node.block)
  126. end
  127. ),
  128. raw("</#{tag}>"),
  129. ]
  130. )
  131. end
  132. 1 def compile_void_element(node, tag)
  133. 6 node => Refract::CallNode
  134. 6 Refract::StatementsNode.new(
  135. body: [
  136. raw("<#{tag}"),
  137. *(
  138. 6 then: 3 else: 3 if node.arguments
  139. 3 compile_phlex_attributes(node.arguments)
  140. end
  141. ),
  142. raw(">"),
  143. ]
  144. )
  145. end
  146. 1 def compile_phlex_attributes(node)
  147. 19 arguments = node.arguments
  148. 19 then: 19 else: 0 if arguments.size == 1 && Refract::KeywordHashNode === (first_argument = arguments[0])
  149. 19 attributes = first_argument.elements
  150. 19 literal_attributes = attributes.all? do |attribute|
  151. 35 Refract::AssocNode === attribute && static_attribute_value_literal?(attribute)
  152. end
  153. 19 then: 18 else: 1 if literal_attributes
  154. 18 return raw(
  155. Phlex::SGML::Attributes.generate_attributes(
  156. eval(
  157. "{#{Refract::Formatter.new.format_node(node).source}}",
  158. TOPLEVEL_BINDING
  159. )
  160. )
  161. )
  162. end
  163. 1 Refract::CallNode.new(
  164. name: :__render_attributes__,
  165. arguments: Refract::ArgumentsNode.new(
  166. arguments: [
  167. node,
  168. ]
  169. )
  170. )
  171. end
  172. end
  173. 1 def compile_phlex_block(node)
  174. 41 else: 1 case node
  175. when: 40 when Refract::BlockNode
  176. 40 then: 17 if output_block?(node)
  177. 17 else: 23 return visit(node.body)
  178. 23 then: 21 else: 2 elsif static_content_block?(node)
  179. 21 content = node.body.body.first
  180. 21 case content
  181. when: 16 when Refract::StringNode, Refract::SymbolNode
  182. 16 return plain(content.unescaped)
  183. when: 5 when Refract::InterpolatedStringNode
  184. 5 return compile_interpolated_string_node(content)
  185. when: 0 when Refract::NilNode
  186. return nil
  187. else: 0 else
  188. raise
  189. end
  190. end
  191. end
  192. 3 Refract::CallNode.new(
  193. name: :__yield_content__,
  194. block: node
  195. )
  196. end
  197. 1 def compile_block_body_node(node)
  198. 3 node => Refract::StatementsNode | Refract::BeginNode
  199. 3 Refract::StatementsNode.new(
  200. body: [
  201. Refract::IfNode.new(
  202. inline: false,
  203. predicate: Refract::CallNode.new(
  204. receiver: Refract::SelfNode.new,
  205. name: :==,
  206. arguments: Refract::ArgumentsNode.new(
  207. arguments: [
  208. Refract::LocalVariableReadNode.new(
  209. name: self_local
  210. ),
  211. ]
  212. )
  213. ),
  214. statements: Refract::StatementsNode.new(
  215. 4 body: node.body.map { |n| visit(n) }
  216. ),
  217. subsequent: Refract::ElseNode.new(
  218. statements: node
  219. )
  220. ),
  221. ]
  222. )
  223. end
  224. 1 def compile_interpolated_string_node(node)
  225. 5 node => Refract::InterpolatedStringNode
  226. 5 Refract::StatementsNode.new(
  227. body: node.parts.map do |part|
  228. 15 case part
  229. when: 10 when Refract::StringNode
  230. 10 plain(part.unescaped)
  231. when: 1 when Refract::EmbeddedVariableNode
  232. 1 interpolate(part.variable)
  233. when: 4 when Refract::EmbeddedStatementsNode
  234. 4 interpolate(part.statements)
  235. else: 0 else
  236. raise Phlex::Compiler::Error, "Unexpected node type in InterpolatedStringNode: #{part.class}"
  237. end
  238. end
  239. )
  240. end
  241. 1 def compile_whitespace_helper(node)
  242. 2 node => Refract::CallNode
  243. 2 then: 1 if node.block
  244. 1 Refract::StatementsNode.new(
  245. body: [
  246. raw(" "),
  247. compile_phlex_block(node.block),
  248. raw(" "),
  249. ]
  250. )
  251. else: 1 else
  252. 1 raw(" ")
  253. end
  254. end
  255. 1 def compile_doctype_helper(node)
  256. node => Refract::CallNode
  257. raw("<!doctype html>")
  258. end
  259. 1 def compile_plain_helper(node)
  260. 6 node => Refract::CallNode
  261. 6 then: 5 if node.arguments.arguments in [Refract::StringNode]
  262. 5 raw(node.arguments.arguments.first.unescaped)
  263. else: 1 else
  264. 1 node
  265. end
  266. end
  267. 1 def compile_fragment_helper(node)
  268. node => Refract::CallNode
  269. node.copy(
  270. block: compile_fragment_helper_block(node.block)
  271. )
  272. end
  273. 1 def compile_fragment_helper_block(node)
  274. node => Refract::BlockNode
  275. node.copy(
  276. body: Refract::StatementsNode.new(
  277. body: [
  278. Refract::LocalVariableWriteNode.new(
  279. name: :__phlex_original_should_render__,
  280. value: Refract::LocalVariableReadNode.new(
  281. name: should_render_local
  282. )
  283. ),
  284. Refract::LocalVariableWriteNode.new(
  285. name: should_render_local,
  286. value: Refract::CallNode.new(
  287. receiver: Refract::LocalVariableReadNode.new(
  288. name: state_local
  289. ),
  290. name: :should_render?
  291. )
  292. ),
  293. visit(node.body),
  294. Refract::LocalVariableWriteNode.new(
  295. name: should_render_local,
  296. value: Refract::LocalVariableReadNode.new(
  297. name: :__phlex_original_should_render__
  298. )
  299. ),
  300. ]
  301. )
  302. )
  303. end
  304. 1 def compile_comment_helper(node)
  305. 2 node => Refract::CallNode
  306. 2 Refract::StatementsNode.new(
  307. body: [
  308. raw("<!-- "),
  309. compile_phlex_block(node.block),
  310. raw(" -->"),
  311. ]
  312. )
  313. end
  314. 1 def compile_raw_helper(node)
  315. 1 node => Refract::CallNode
  316. 1 node
  317. end
  318. 1 private def plain(value)
  319. 26 value => String
  320. 26 raw(Phlex::Escape.html_escape(value))
  321. end
  322. 1 private def raw(value)
  323. 188 value => String
  324. 188 buffer(
  325. Refract::StringNode.new(
  326. unescaped: value
  327. )
  328. )
  329. end
  330. 1 private def interpolate(statements)
  331. 5 buffer(
  332. Refract::EmbeddedStatementsNode.new(
  333. statements: Refract::StatementsNode.new(
  334. body: [
  335. Refract::CallNode.new(
  336. receiver: Refract::ConstantPathNode.new(
  337. parent: Refract::ConstantPathNode.new(
  338. name: "Phlex"
  339. ),
  340. name: "Escape"
  341. ),
  342. name: :html_escape,
  343. arguments: Refract::ArgumentsNode.new(
  344. arguments: [
  345. Refract::CallNode.new(
  346. receiver: Refract::ParenthesesNode.new(
  347. body: statements
  348. ),
  349. name: :to_s
  350. ),
  351. ]
  352. )
  353. ),
  354. ]
  355. )
  356. )
  357. )
  358. end
  359. 1 private def buffer(node)
  360. 193 @optimized = true
  361. 193 node => Refract::StringNode | Refract::EmbeddedStatementsNode
  362. 193 should_render_local
  363. 193 buffer_local
  364. 193 Refract::StatementsNode.new(
  365. body: [
  366. Concat.new(
  367. node
  368. ),
  369. ]
  370. )
  371. end
  372. 1 private def output_block?(node)
  373. 40 then: 40 else: 0 then: 40 else: 0 node.body&.body&.any? do |child|
  374. 40 Refract::CallNode === child && (
  375. 18 standard_element?(child) ||
  376. void_element?(child) ||
  377. plain_helper?(child) ||
  378. whitespace_helper?(child) ||
  379. raw_helper?(child)
  380. )
  381. end
  382. end
  383. 1 private def static_content_block?(node)
  384. 23 then: 23 else: 0 then: 23 else: 0 else: 23 then: 0 return false unless node.body&.body&.length == 1
  385. 23 node.body.body.first in Refract::StringNode | Refract::InterpolatedStringNode | Refract::SymbolNode | Refract::NilNode
  386. end
  387. 1 private def standard_element?(node)
  388. 80 if (tag = Phlex::HTML::StandardElements.__registered_elements__[node.name]) &&
  389. 57 (Phlex::HTML::StandardElements == Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
  390. then: 57
  391. 57 tag
  392. else: 23 else
  393. 23 false
  394. end
  395. end
  396. 1 private def void_element?(node)
  397. 23 if (tag = Phlex::HTML::VoidElements.__registered_elements__[node.name]) &&
  398. 9 (Phlex::HTML::VoidElements == Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
  399. then: 6
  400. 6 tag
  401. else: 17 else
  402. 17 false
  403. end
  404. end
  405. 1 private def static_attribute_value_literal?(value)
  406. 70 case value
  407. when: 34 when Refract::SymbolNode, Refract::StringNode, Refract::IntegerNode, Refract::FloatNode, Refract::TrueNode, Refract::FalseNode, Refract::NilNode
  408. 34 true
  409. when: 1 when Refract::ArrayNode
  410. 2 value.elements.all? { |n| static_token_value_literal?(n) }
  411. when: 0 when Refract::HashNode
  412. value.elements.all? { |n| static_attribute_value_literal?(n) }
  413. when: 35 when Refract::AssocNode
  414. 35 (Refract::StringNode === value.key || Refract::SymbolNode === value.key) && static_attribute_value_literal?(value.value)
  415. when: 0 when Refract::CallNode
  416. then: 0 if value in { receiver: Prism::ConstantReadNode[name: :Set] | Prism::ConstantPathNode[name: :Set, parent: nil], name: :[] }
  417. value.arguments.arguments.all? { |n| static_token_value_literal?(n) }
  418. else: 0 else
  419. false
  420. end
  421. else: 0 else
  422. false
  423. end
  424. end
  425. 1 private def static_token_value_literal?(value)
  426. 1 case value
  427. when: 0 when Prism::SymbolNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::NilNode
  428. true
  429. when: 0 when Prism::ArrayNode
  430. value.elements.all? { |n| static_token_value_literal?(n) }
  431. else: 1 else
  432. 1 false
  433. end
  434. end
  435. 1 private def whitespace_helper?(node)
  436. 16 node.name == :whitespace && own_method_without_scope?(node)
  437. end
  438. 1 private def doctype_helper?(node)
  439. 13 node.name == :doctype && own_method_without_scope?(node)
  440. end
  441. 1 private def plain_helper?(node)
  442. 15 node.name == :plain && own_method_without_scope?(node)
  443. end
  444. 1 private def fragment_helper?(node)
  445. 7 node.name == :fragment && own_method_without_scope?(node)
  446. end
  447. 1 private def comment_helper?(node)
  448. 7 node.name == :comment && own_method_without_scope?(node)
  449. end
  450. 1 private def raw_helper?(node)
  451. 6 node.name == :raw && own_method_without_scope?(node)
  452. end
  453. 1 ALLOWED_OWNERS = Set[Phlex::SGML, Phlex::HTML, Phlex::SVG]
  454. 1 private def own_method_without_scope?(node)
  455. 12 ALLOWED_OWNERS.include?(Phlex::UNBOUND_INSTANCE_METHOD_METHOD.bind_call(@component, node.name).owner)
  456. end
  457. 1 private def state_local
  458. 24 :__phlex_state__.tap do |local|
  459. 24 else: 12 then: 12 unless @state_local_set
  460. 12 @preamble << Refract::LocalVariableWriteNode.new(
  461. name: local,
  462. value: Refract::InstanceVariableReadNode.new(
  463. name: :@_state
  464. )
  465. )
  466. 12 @state_local_set = true
  467. end
  468. end
  469. end
  470. 1 private def buffer_local
  471. 193 :__phlex_buffer__.tap do |local|
  472. 193 else: 181 then: 12 unless @buffer_local_set
  473. 12 @preamble << Refract::LocalVariableWriteNode.new(
  474. name: local,
  475. value: Refract::CallNode.new(
  476. receiver: Refract::LocalVariableReadNode.new(
  477. name: state_local
  478. ),
  479. name: :buffer,
  480. )
  481. )
  482. 12 @buffer_local_set = true
  483. end
  484. end
  485. end
  486. 1 private def self_local
  487. 3 :__phlex_self__.tap do |local|
  488. 3 else: 1 then: 2 unless @self_local_set
  489. 2 @preamble << Refract::LocalVariableWriteNode.new(
  490. name: local,
  491. value: Refract::SelfNode.new
  492. )
  493. 2 @self_local_set = true
  494. end
  495. end
  496. end
  497. 1 private def should_render_local
  498. 193 :__phlex_should_render__.tap do |local|
  499. 193 else: 181 then: 12 unless @should_render_local_set
  500. 12 @preamble << Refract::LocalVariableWriteNode.new(
  501. name: :__phlex_should_render__,
  502. value: Refract::CallNode.new(
  503. receiver: Refract::LocalVariableReadNode.new(
  504. name: state_local
  505. ),
  506. name: :should_render?
  507. )
  508. )
  509. 12 @should_render_local_set = true
  510. end
  511. end
  512. end
  513. end
  514. end

lib/phlex/csv.rb

95.35% lines covered

94.12% branches covered

129 relevant lines. 123 lines covered and 6 lines missed.
51 total branches, 48 branches covered and 3 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::CSV
  3. 1 FORMULA_PREFIXES_MAP = Array.new(128).tap do |map|
  4. 1 "=+-@\t\r".each_byte do |byte|
  5. 6 map[byte] = true
  6. end
  7. end.freeze
  8. 1 UNDEFINED = Object.new
  9. 1 def initialize(collection)
  10. 14 @collection = collection
  11. 14 @_row_buffer = []
  12. 14 @_headers = []
  13. 14 @_row_appender = nil
  14. end
  15. 1 attr_reader :collection
  16. 1 def call(buffer = +"", context: nil, delimiter: self.delimiter)
  17. 12 ensure_escape_csv_injection_configured!
  18. 11 strip_whitespace = trim_whitespace?
  19. 11 escape_csv_injection = escape_csv_injection?
  20. 11 row_buffer = @_row_buffer
  21. 11 headers = @_headers
  22. 11 has_yielder = respond_to?(:yielder, true)
  23. 11 first_row = true
  24. 11 render_headers = render_headers?
  25. 11 then: 1 else: 10 if delimiter.length != 1
  26. 1 raise Phlex::ArgumentError.new("Delimiter must be a single character")
  27. end
  28. 10 then: 4 if strip_whitespace
  29. 4 escape_regex = /[\n"#{delimiter}]/
  30. else: 6 else
  31. 6 escape_regex = /^\s|\s$|[\n"#{delimiter}]/
  32. end
  33. 10 then: 1 else: 9 if has_yielder
  34. 1 warn <<~MESSAGE
  35. Custom yielders are deprecated in Phlex::CSV.
  36. Please replace your yielder with an `around_row` method.
  37. You should be able to just rename your yielder method
  38. and change `yield` to `super`.
  39. MESSAGE
  40. end
  41. 10 row_appender = -> {
  42. 84 row = row_buffer
  43. 84 then: 10 else: 74 if first_row
  44. 10 first_row = false
  45. 10 i = 0
  46. 10 number_of_columns = row.length
  47. 10 first_col = true
  48. 10 body: 20 while i < number_of_columns
  49. 20 header, = row[i]
  50. 20 headers[i] = header
  51. 20 then: 18 else: 2 if render_headers
  52. 18 then: 9 if first_col
  53. 9 first_col = false
  54. else: 9 else
  55. 9 buffer << delimiter
  56. end
  57. 18 __escape__(buffer, header, escape_csv_injection:, strip_whitespace:, escape_regex:)
  58. end
  59. 20 i += 1
  60. end
  61. 10 then: 9 else: 1 buffer << "\n" if render_headers
  62. end
  63. 84 i = 0
  64. 84 number_of_columns = row.length
  65. 84 first_col = true
  66. 84 body: 168 while i < number_of_columns
  67. 168 header, value = row[i]
  68. 168 else: 168 then: 0 unless headers[i] == header
  69. raise Phlex::RuntimeError.new("Header mismatch at index #{i}: expected #{headers[i]}, got #{header}.")
  70. end
  71. 168 then: 84 if first_col
  72. 84 first_col = false
  73. else: 84 else
  74. 84 buffer << delimiter
  75. end
  76. 168 __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:)
  77. 168 i += 1
  78. end
  79. 84 buffer << "\n"
  80. 84 row_buffer.clear
  81. }
  82. 10 then: 1 if has_yielder
  83. 1 each_item do |record|
  84. 7 yielder(record) do |*a, **k|
  85. 14 row_template(*a, **k)
  86. 14 row_appender.call
  87. end
  88. end
  89. else: 9 else
  90. 9 @row_appender = row_appender
  91. 9 each_item do |record|
  92. 63 around_row(record)
  93. end
  94. end
  95. 10 buffer
  96. end
  97. 1 def around_row(...)
  98. 70 row_template(...)
  99. 70 @row_appender.call
  100. end
  101. 1 def filename
  102. 1 nil
  103. end
  104. 1 def content_type
  105. 1 "text/csv"
  106. end
  107. 1 def delimiter
  108. 9 ","
  109. end
  110. 1 private def column(header = nil, value)
  111. 168 @_row_buffer << [header, value]
  112. end
  113. 1 private def each_item(&)
  114. 10 collection.each(&)
  115. end
  116. # Override and set to `false` to disable rendering headers.
  117. 1 private def render_headers?
  118. 10 true
  119. end
  120. # Override and set to `true` to strip leading and trailing whitespace from values.
  121. 1 private def trim_whitespace?
  122. 3 false
  123. end
  124. # Override and set to `false` to disable CSV injection escapes or `true` to enable.
  125. 1 private def escape_csv_injection?
  126. 1 UNDEFINED
  127. end
  128. 1 private def __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:)
  129. 186 value = case value
  130. when: 126 when String
  131. 126 value
  132. when: 12 when Symbol
  133. 12 value.name
  134. else: 48 else
  135. 48 value.to_s
  136. end
  137. 186 then: 64 if strip_whitespace
  138. 64 value = value.strip
  139. 64 then: 48 if escape_csv_injection
  140. 48 then: 12 if value.empty?
  141. 12 else: 36 buffer << value
  142. 36 then: 6 elsif FORMULA_PREFIXES_MAP[value.getbyte(0)]
  143. 6 value.gsub!('"', '""')
  144. 6 else: 30 buffer << '"\'' << value << '"'
  145. 30 then: 6 elsif value.match?(escape_regex)
  146. 6 value.gsub!('"', '""')
  147. 6 buffer << '"' << value << '"'
  148. else: 24 else
  149. 24 buffer << value
  150. end
  151. else: 16 else # not escaping CSV injection
  152. 16 buffer << value
  153. end
  154. else: 122 else # not stripping whitespace
  155. 122 then: 92 if escape_csv_injection
  156. 92 first_byte = value.getbyte(0)
  157. 92 then: 24 if value.empty?
  158. 24 else: 68 buffer << '""'
  159. 68 then: 12 elsif FORMULA_PREFIXES_MAP[first_byte]
  160. 12 else: 56 buffer << '"\'' << value.gsub('"', '""') << '"'
  161. 56 then: 18 elsif value.match?(escape_regex)
  162. 18 buffer << '"' << value.gsub('"', '""') << '"'
  163. else: 38 else
  164. 38 buffer << value
  165. end
  166. else: 30 else # not escaping CSV injection
  167. 30 then: 8 if value.empty?
  168. 8 else: 22 buffer << '""'
  169. 22 then: 6 elsif value.match?(escape_regex)
  170. 6 buffer << '"' << value.gsub('"', '""') << '"'
  171. else: 16 else
  172. 16 buffer << value
  173. end
  174. end
  175. end
  176. end
  177. # Handle legacy `view_template` method
  178. 1 private def respond_to_missing?(method_name, include_private)
  179. 10 (method_name == :row_template && respond_to?(:view_template)) || super
  180. end
  181. # Handle legacy `view_template` method
  182. 1 private def method_missing(method_name, ...)
  183. then: 0 if method_name == :row_template && respond_to?(:view_template)
  184. warn "Deprecated: Use `row_template` instead of `view_template` in Phlex CSVs."
  185. self.class.alias_method :row_template, :view_template
  186. view_template(...)
  187. else: 0 else
  188. super
  189. end
  190. end
  191. 1 private def ensure_escape_csv_injection_configured!
  192. 12 then: 1 else: 11 if escape_csv_injection? == UNDEFINED
  193. 1 raise <<~MESSAGE
  194. You need to define `escape_csv_injection?` in #{self.class.name}.
  195. CSV injection is a security vulnerability where malicious spreadsheet
  196. formulae are used to execute code or exfiltrate data when a CSV is opened
  197. in a spreadsheet program such as Microsoft Excel or Google Sheets.
  198. For more information, see https://owasp.org/www-community/attacks/CSV_Injection
  199. If you’re sure this CSV will never be opened in a spreadsheet program,
  200. you can *disable* CSV injection escapes:
  201. def escape_csv_injection? = false
  202. This is useful when using CSVs for byte-for-byte data exchange between secure systems.
  203. Alternatively, you can *enable* CSV injection escapes at the cost of data integrity:
  204. def escape_csv_injection? = true
  205. Enabling the CSV injection escapes will prefix with a single quote `'` any
  206. values that start with: `=`, `+`, `-`, `@`, `\\t`, `\\r`
  207. Unfortunately, there is no one-size-fits-all solution to CSV injection.
  208. You need to decide based on your specific use case.
  209. MESSAGE
  210. end
  211. end
  212. end

lib/phlex/error.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 module Phlex::Error
  4. end

lib/phlex/errors/argument_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::ArgumentError < ArgumentError
  3. 1 include Phlex::Error
  4. end

lib/phlex/errors/double_render_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::DoubleRenderError < RuntimeError
  3. 1 include Phlex::Error
  4. end

lib/phlex/errors/name_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::NameError < NameError
  3. 1 include Phlex::Error
  4. end

lib/phlex/errors/runtime_error.rb

100.0% lines covered

100.0% branches covered

2 relevant lines. 2 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::RuntimeError < RuntimeError
  3. 1 include Phlex::Error
  4. end

lib/phlex/fifo.rb

90.0% lines covered

100.0% branches covered

30 relevant lines. 27 lines covered and 3 lines missed.
7 total branches, 7 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 class Phlex::FIFO
  4. 1 def initialize(max_bytesize: 2_000, max_value_bytesize: 2_000)
  5. 15 @store = {}
  6. 15 @max_bytesize = max_bytesize
  7. 15 @max_value_bytesize = max_value_bytesize
  8. 15 @bytesize = 0
  9. 15 @mutex = Monitor.new
  10. end
  11. 1 attr_reader :bytesize, :max_bytesize
  12. 1 def expand(bytes)
  13. 41 @mutex.synchronize do
  14. 41 @max_bytesize += bytes
  15. end
  16. end
  17. 1 def [](key)
  18. 1837 k, v = @store[key.hash]
  19. 1837 then: 1596 else: 241 v if k.eql?(key)
  20. end
  21. 1 def []=(key, value)
  22. 311 then: 1 else: 310 return if value.bytesize > @max_value_bytesize
  23. 310 digest = key.hash
  24. 310 @mutex.synchronize do
  25. # Check the key definitely doesn't exist now we have the lock
  26. 310 then: 1 else: 309 return if @store[digest]
  27. 309 @store[digest] = [key, value].freeze
  28. 309 @bytesize += value.bytesize
  29. 309 body: 97 while @bytesize > @max_bytesize
  30. 97 _k, v = @store.shift
  31. 97 @bytesize -= v[1].bytesize
  32. end
  33. end
  34. end
  35. 1 def size
  36. 2 @store.size
  37. end
  38. 1 def clear
  39. @mutex.synchronize do
  40. @store.clear
  41. @bytesize = 0
  42. end
  43. end
  44. end

lib/phlex/fifo_cache_store.rb

69.57% lines covered

40.0% branches covered

23 relevant lines. 16 lines covered and 7 lines missed.
10 total branches, 4 branches covered and 6 branches missed.
    
  1. # frozen_string_literal: true
  2. # An extremely fast in-memory cache store that evicts keys on a first-in-first-out basis.
  3. 1 class Phlex::FIFOCacheStore
  4. 1 def initialize(max_bytesize: 2 ** 20)
  5. 11 @fifo = Phlex::FIFO.new(
  6. max_bytesize:,
  7. max_value_bytesize: max_bytesize
  8. )
  9. end
  10. 1 def fetch(key)
  11. 29 fifo = @fifo
  12. 29 key = map_key(key)
  13. 29 then: 7 if (result = fifo[key])
  14. 7 JSON.parse(result)
  15. else: 22 else
  16. 22 result = yield
  17. 22 fifo[key] = JSON.fast_generate(result)
  18. 22 result
  19. end
  20. end
  21. 1 def clear
  22. @fifo.clear
  23. end
  24. 1 private def map_key(value)
  25. 192 case value
  26. when: 50 when Array
  27. 213 value.map { |it| map_key(it) }
  28. when: 0 when Hash
  29. value.to_h { |k, v| [map_key(k), map_key(v)].freeze }
  30. when: 142 when String, Symbol, Integer, Float, Time, true, false, nil
  31. 142 value
  32. else: 0 else
  33. then: 0 if value.respond_to?(:cache_key_with_version)
  34. else: 0 map_key(value.cache_key_with_version)
  35. then: 0 elsif value.respond_to?(:cache_key)
  36. map_key(value.cache_key)
  37. else: 0 else
  38. raise ArgumentError.new("Invalid cache key: #{value.class}")
  39. end
  40. end
  41. end
  42. end

lib/phlex/helpers.rb

95.83% lines covered

94.12% branches covered

24 relevant lines. 23 lines covered and 1 lines missed.
17 total branches, 16 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 module Phlex::Helpers
  4. 1 private def mix(*args)
  5. 16 args.each_with_object({}) do |object, result|
  6. 32 result.merge!(object) do |_key, old, new|
  7. 15 case [old, new].freeze
  8. in: 2 in [Array, Array] | [Set, Set]
  9. 2 old + new
  10. in: 1 in [Array, Set]
  11. 1 old + new.to_a
  12. in: 1 in [Array, String]
  13. 1 old + [new]
  14. in: 1 in [Hash, Hash]
  15. 1 mix(old, new)
  16. in: 1 in [Set, Array]
  17. 1 old.to_a + new
  18. in: 1 in [Set, String]
  19. 1 old.to_a + [new]
  20. in: 1 in [String, Array]
  21. 1 [old] + new
  22. in: 1 in [String, Set]
  23. 1 [old] + new.to_a
  24. in: 2 in [String, String]
  25. 2 "#{old} #{new}"
  26. in: 1 in [_, Hash]
  27. 1 { _: old, **new }
  28. in: 0 in [Hash, _]
  29. { **old, _: new }
  30. in: 2 in [_, nil]
  31. 2 old
  32. else: 1 else
  33. 1 new
  34. end
  35. end
  36. 32 result.transform_keys! do |key|
  37. 33 then: 1 else: 32 key.end_with?("!") ? key.name.chop.to_sym : key
  38. end
  39. end
  40. end
  41. 1 private def grab(**bindings)
  42. 2 then: 1 if bindings.size > 1
  43. 1 bindings.values
  44. else: 1 else
  45. 1 bindings.values.first
  46. end
  47. end
  48. end

lib/phlex/html.rb

93.88% lines covered

89.29% branches covered

49 relevant lines. 46 lines covered and 3 lines missed.
28 total branches, 25 branches covered and 3 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::HTML < Phlex::SGML
  3. 1 extend Phlex::SGML::Elements
  4. 1 include VoidElements, StandardElements
  5. # Output an HTML doctype.
  6. 1 def doctype
  7. 5 state = @_state
  8. 5 else: 1 then: 4 return unless state.should_render?
  9. 1 state.buffer << "<!doctype html>"
  10. 1 nil
  11. end
  12. # Outputs an `<svg>` tag.
  13. #
  14. # [MDN Docs](https://developer.mozilla.org/docs/Web/SVG/Element/svg)
  15. # [Spec](https://html.spec.whatwg.org/#the-svg-element)
  16. 1 def svg(*, **, &)
  17. 6 then: 5 if block_given?
  18. 10 super { render Phlex::SVG.new(&) }
  19. else: 1 else
  20. 1 super
  21. end
  22. end
  23. # Override to provide a filename for the HTML file
  24. 1 def filename
  25. nil
  26. end
  27. # Returns the string "text/html"
  28. 1 def content_type
  29. 1 "text/html"
  30. end
  31. # Output an HTML tag dynamically, e.g:
  32. #
  33. # ```ruby
  34. # @tag_name = :h1
  35. # tag(@tag_name, class: "title")
  36. # ```
  37. 1 def tag(name, **attributes, &)
  38. 454 state = @_state
  39. 454 block_given = block_given?
  40. 454 buffer = state.buffer
  41. 454 else: 454 then: 0 unless state.should_render?
  42. then: 0 else: 0 yield(self) if block_given
  43. return nil
  44. end
  45. 454 else: 453 then: 1 unless Symbol === name
  46. 1 raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
  47. end
  48. 453 then: 414 if (tag = StandardElements.__registered_elements__[name]) || ((tag = name.name.tr("_", "-")).include?("-") && tag.match?(/\A[a-z0-9-]+\z/))
  49. 414 then: 206 if attributes.length > 0 # with attributes
  50. 206 then: 103 if block_given # with content block
  51. 103 buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
  52. 103 then: 1 if tag == "svg"
  53. 1 render Phlex::SVG.new(&)
  54. else: 102 else
  55. 102 __yield_content__(&)
  56. end
  57. 103 buffer << "</#{tag}>"
  58. else: 103 else # without content
  59. 103 buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
  60. end
  61. else: 208 else # without attributes
  62. 208 then: 104 if block_given # with content block
  63. 104 buffer << ("<#{tag}>")
  64. 104 then: 2 if tag == "svg"
  65. 2 render Phlex::SVG.new(&)
  66. else: 102 else
  67. 102 __yield_content__(&)
  68. end
  69. 104 buffer << "</#{tag}>"
  70. else: 104 else # without content
  71. 104 buffer << "<#{tag}></#{tag}>"
  72. end
  73. else: 39 end
  74. 39 then: 36 elsif (tag = VoidElements.__registered_elements__[name])
  75. 36 then: 12 else: 24 if block_given
  76. 12 raise Phlex::ArgumentError.new("Void elements cannot have content blocks.")
  77. end
  78. 24 then: 12 if attributes.length > 0 # with attributes
  79. 12 buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
  80. else: 12 else # without attributes
  81. 12 buffer << "<#{tag}>"
  82. end
  83. 24 nil
  84. else: 3 else
  85. 3 raise Phlex::ArgumentError.new("Invalid HTML tag: #{name}")
  86. end
  87. end
  88. end

lib/phlex/html/standard_elements.rb

100.0% lines covered

100.0% branches covered

105 relevant lines. 105 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # Standard HTML elements accept content and always have a closing tag.
  3. 1 module Phlex::HTML::StandardElements
  4. 1 extend Phlex::SGML::Elements
  5. # Outputs an `<a>` tag.
  6. #
  7. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/a)
  8. # [Spec](https://html.spec.whatwg.org/#the-a-element)
  9. 1 register_element def a(**attributes, &content) = nil
  10. # Outputs an `<abbr>` tag.
  11. #
  12. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/abbr)
  13. # [Spec](https://html.spec.whatwg.org/#the-abbr-element)
  14. 1 register_element def abbr(**attributes, &content) = nil
  15. # Outputs an `<address>` tag.
  16. #
  17. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/address)
  18. # [Spec](https://html.spec.whatwg.org/#the-address-element)
  19. 1 register_element def address(**attributes, &content) = nil
  20. # Outputs an `<article>` tag.
  21. #
  22. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/article)
  23. # [Spec](https://html.spec.whatwg.org/#the-article-element)
  24. 1 register_element def article(**attributes, &content) = nil
  25. # Outputs an `<aside>` tag.
  26. #
  27. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/aside)
  28. # [Spec](https://html.spec.whatwg.org/#the-aside-element)
  29. 1 register_element def aside(**attributes, &content) = nil
  30. # Outputs an `<audio>` tag.
  31. #
  32. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/audio)
  33. # [Spec](https://html.spec.whatwg.org/#the-audio-element)
  34. # [Can I Use?](https://caniuse.com/audio)
  35. 1 register_element def audio(**attributes, &content) = nil
  36. # Outputs a `<b>` tag.
  37. #
  38. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/b)
  39. # [Spec](https://html.spec.whatwg.org/#the-b-element)
  40. 1 register_element def b(**attributes, &content) = nil
  41. # Outputs a `<bdi>` tag.
  42. #
  43. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/bdi)
  44. # [Spec](https://html.spec.whatwg.org/#the-bdi-element)
  45. 1 register_element def bdi(**attributes, &content) = nil
  46. # Outputs a `<bdo>` tag.
  47. #
  48. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/bdo)
  49. # [Spec](https://html.spec.whatwg.org/#the-bdo-element)
  50. 1 register_element def bdo(**attributes, &content) = nil
  51. # Outputs a `<blockquote>` tag.
  52. #
  53. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/blockquote)
  54. # [Spec](https://html.spec.whatwg.org/#the-blockquote-element)
  55. 1 register_element def blockquote(**attributes, &content) = nil
  56. # Outputs a `<body>` tag.
  57. #
  58. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/body)
  59. # [Spec](https://html.spec.whatwg.org/#the-body-element)
  60. 1 register_element def body(**attributes, &content) = nil
  61. # Outputs a `<button>` tag.
  62. # The `<button>` element is an interactive element activated by a user with a mouse, keyboard, finger, voice command, or other assistive technology to perform an action, such as submitting a form or opening a dialog.
  63. #
  64. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/button)
  65. # [Spec](https://html.spec.whatwg.org/#the-button-element)
  66. 1 register_element def button(**attributes, &content) = nil
  67. # Outputs a `<canvas>` tag.
  68. #
  69. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/canvas)
  70. # [Spec](https://html.spec.whatwg.org/#the-canvas-element)
  71. 1 register_element def canvas(**attributes, &content) = nil
  72. # Outputs a `<caption>` tag.
  73. #
  74. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/caption)
  75. # [Spec](https://html.spec.whatwg.org/#the-caption-element)
  76. 1 register_element def caption(**attributes, &content) = nil
  77. # Outputs a `<cite>` tag.
  78. #
  79. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/cite)
  80. # [Spec](https://html.spec.whatwg.org/#the-cite-element)
  81. 1 register_element def cite(**attributes, &content) = nil
  82. # Outputs a `<code>` tag.
  83. #
  84. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/code)
  85. # [Spec](https://html.spec.whatwg.org/#the-code-element)
  86. 1 register_element def code(**attributes, &content) = nil
  87. # Outputs a `<colgroup>` tag.
  88. #
  89. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/colgroup)
  90. # [Spec](https://html.spec.whatwg.org/#the-colgroup-element)
  91. 1 register_element def colgroup(**attributes, &content) = nil
  92. # Outputs a `<data>` tag.
  93. #
  94. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/data)
  95. # [Spec](https://html.spec.whatwg.org/#the-data-element)
  96. 1 register_element def data(**attributes, &content) = nil
  97. # Outputs a `<datalist>` tag.
  98. #
  99. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/datalist)
  100. # [Spec](https://html.spec.whatwg.org/#the-datalist-element)
  101. 1 register_element def datalist(**attributes, &content) = nil
  102. # Outputs a `<dd>` tag.
  103. #
  104. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dd)
  105. # [Spec](https://html.spec.whatwg.org/#the-dd-element)
  106. 1 register_element def dd(**attributes, &content) = nil
  107. # Outputs a `<del>` tag.
  108. #
  109. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/del)
  110. # [Spec](https://html.spec.whatwg.org/#the-del-element)
  111. 1 register_element def del(**attributes, &content) = nil
  112. # Outputs a `<details>` tag.
  113. #
  114. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/details)
  115. # [Spec](https://html.spec.whatwg.org/#the-details-element)
  116. # [Can I Use?](https://caniuse.com/details)
  117. 1 register_element def details(**attributes, &content) = nil
  118. # Outputs a `<dfn>` tag.
  119. #
  120. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dfn)
  121. # [Spec](https://html.spec.whatwg.org/#the-dfn-element)
  122. 1 register_element def dfn(**attributes, &content) = nil
  123. # Outputs a `<dialog>` tag.
  124. #
  125. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dialog)
  126. # [Spec](https://html.spec.whatwg.org/#the-dialog-element)
  127. # [Can I use?](https://caniuse.com/dialog)
  128. 1 register_element def dialog(**attributes, &content) = nil
  129. # Outputs a `<div>` tag.
  130. #
  131. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/div)
  132. # [Spec](https://html.spec.whatwg.org/#the-div-element)
  133. 1 register_element def div(**attributes, &content) = nil
  134. # Outputs a `<dl>` tag.
  135. #
  136. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dl)
  137. # [Spec](https://html.spec.whatwg.org/#the-dl-element)
  138. 1 register_element def dl(**attributes, &content) = nil
  139. # Outputs a `<dt>` tag.
  140. #
  141. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/dt)
  142. # [Spec](https://html.spec.whatwg.org/#the-dt-element)
  143. 1 register_element def dt(**attributes, &content) = nil
  144. # Outputs an `<em>` tag.
  145. #
  146. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/em)
  147. # [Spec](https://html.spec.whatwg.org/#the-em-element)
  148. 1 register_element def em(**attributes, &content) = nil
  149. # [EXPERIMENTAL] Outputs a `<fencedframe>` tag.
  150. #
  151. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/fencedframe)
  152. # [Can I Use?](https://caniuse.com/mdn-html_elements_fencedframe)
  153. 1 register_element def fencedframe(**attributes, &content) = nil
  154. # Outputs a `<fieldset>` tag.
  155. #
  156. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/fieldset)
  157. # [Spec](https://html.spec.whatwg.org/#the-fieldset-element)
  158. 1 register_element def fieldset(**attributes, &content) = nil
  159. # Outputs a `<figcaption>` tag.
  160. #
  161. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/figcaption)
  162. # [Spec](https://html.spec.whatwg.org/#the-figcaption-element)
  163. 1 register_element def figcaption(**attributes, &content) = nil
  164. # Outputs a `<figure>` tag.
  165. #
  166. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/figure)
  167. # [Spec](https://html.spec.whatwg.org/#the-figure-element)
  168. 1 register_element def figure(**attributes, &content) = nil
  169. # Outputs a `<footer>` tag.
  170. #
  171. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/footer)
  172. # [Spec](https://html.spec.whatwg.org/#the-footer-element)
  173. 1 register_element def footer(**attributes, &content) = nil
  174. # Outputs a `<form>` tag.
  175. #
  176. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/form)
  177. # [Spec](https://html.spec.whatwg.org/#the-form-element)
  178. 1 register_element def form(**attributes, &content) = nil
  179. # Outputs an `<h1>` tag.
  180. #
  181. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h1)
  182. # [Spec](https://html.spec.whatwg.org/#the-h1-element)
  183. 1 register_element def h1(**attributes, &content) = nil
  184. # Outputs an `<h2>` tag.
  185. #
  186. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h2)
  187. # [Spec](https://html.spec.whatwg.org/#the-h2-element)
  188. 1 register_element def h2(**attributes, &content) = nil
  189. # Outputs an `<h3>` tag.
  190. #
  191. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h3)
  192. # [Spec](https://html.spec.whatwg.org/#the-h3-element)
  193. 1 register_element def h3(**attributes, &content) = nil
  194. # Outputs an `<h4>` tag.
  195. #
  196. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h4)
  197. # [Spec](https://html.spec.whatwg.org/#the-h4-element)
  198. 1 register_element def h4(**attributes, &content) = nil
  199. # Outputs an `<h5>` tag.
  200. #
  201. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h5)
  202. # [Spec](https://html.spec.whatwg.org/#the-h5-element)
  203. 1 register_element def h5(**attributes, &content) = nil
  204. # Outputs an `<h6>` tag.
  205. #
  206. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/h6)
  207. # [Spec](https://html.spec.whatwg.org/#the-h6-element)
  208. 1 register_element def h6(**attributes, &content) = nil
  209. # Outputs a `<head>` tag.
  210. #
  211. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/head)
  212. # [Spec](https://html.spec.whatwg.org/#the-head-element)
  213. 1 register_element def head(**attributes, &content) = nil
  214. # Outputs a `<header>` tag.
  215. #
  216. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/header)
  217. # [Spec](https://html.spec.whatwg.org/#the-header-element)
  218. 1 register_element def header(**attributes, &content) = nil
  219. # Outputs an `<hgroup>` tag.
  220. #
  221. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/hgroup)
  222. # [Spec](https://html.spec.whatwg.org/#the-hgroup-element)
  223. 1 register_element def hgroup(**attributes, &content) = nil
  224. # Outputs an `<html>` tag.
  225. #
  226. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/html)
  227. # [Spec](https://html.spec.whatwg.org/#the-html-element)
  228. 1 register_element def html(**attributes, &content) = nil
  229. # Outputs an `<i>` tag.
  230. #
  231. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/i)
  232. # [Spec](https://html.spec.whatwg.org/#the-i-element)
  233. 1 register_element def i(**attributes, &content) = nil
  234. # Outputs an `<iframe>` tag.
  235. #
  236. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/iframe)
  237. # [Spec](https://html.spec.whatwg.org/#the-iframe-element)
  238. 1 register_element def iframe(**attributes, &content) = nil
  239. # Outputs an `<ins>` tag.
  240. #
  241. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ins)
  242. # [Spec](https://html.spec.whatwg.org/#the-ins-element)
  243. 1 register_element def ins(**attributes, &content) = nil
  244. # Outputs a `<kbd>` tag.
  245. #
  246. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/kbd)
  247. # [Spec](https://html.spec.whatwg.org/#the-kbd-element)
  248. 1 register_element def kbd(**attributes, &content) = nil
  249. # Outputs a `<label>` tag.
  250. #
  251. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/label)
  252. # [Spec](https://html.spec.whatwg.org/#the-label-element)
  253. 1 register_element def label(**attributes, &content) = nil
  254. # Outputs a `<legend>` tag.
  255. #
  256. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/legend)
  257. # [Spec](https://html.spec.whatwg.org/#the-legend-element)
  258. 1 register_element def legend(**attributes, &content) = nil
  259. # Outputs a `<li>` tag.
  260. #
  261. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/li)
  262. # [Spec](https://html.spec.whatwg.org/#the-li-element)
  263. 1 register_element def li(**attributes, &content) = nil
  264. # Outputs a `<main>` tag.
  265. #
  266. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/main)
  267. # [Spec](https://html.spec.whatwg.org/#the-main-element)
  268. 1 register_element def main(**attributes, &content) = nil
  269. # Outputs a `<map>` tag.
  270. #
  271. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/map)
  272. # [Spec](https://html.spec.whatwg.org/#the-map-element)
  273. 1 register_element def map(**attributes, &content) = nil
  274. # Outputs a `<mark>` tag.
  275. #
  276. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/mark)
  277. # [Spec](https://html.spec.whatwg.org/#the-mark-element)
  278. 1 register_element def mark(**attributes, &content) = nil
  279. # Outputs a `<menu>` tag.
  280. #
  281. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/menu)
  282. # [Spec](https://html.spec.whatwg.org/#the-menu-element)
  283. 1 register_element def menu(**attributes, &content) = nil
  284. # Outputs a `<meter>` tag.
  285. #
  286. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/meter)
  287. # [Spec](https://html.spec.whatwg.org/#the-meter-element)
  288. 1 register_element def meter(**attributes, &content) = nil
  289. # Outputs a `<nav>` tag.
  290. #
  291. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/nav)
  292. # [Spec](https://html.spec.whatwg.org/#the-nav-element)
  293. 1 register_element def nav(**attributes, &content) = nil
  294. # Outputs a `<noscript>` tag.
  295. #
  296. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/noscript)
  297. # [Spec](https://html.spec.whatwg.org/#the-noscript-element)
  298. 1 register_element def noscript(**attributes, &content) = nil
  299. # Outputs an `<object>` tag.
  300. #
  301. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/object)
  302. # [Spec](https://html.spec.whatwg.org/#the-object-element)
  303. 1 register_element def object(**attributes, &content) = nil
  304. # Outputs an `<ol>` tag.
  305. #
  306. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ol)
  307. # [Spec](https://html.spec.whatwg.org/#the-ol-element)
  308. 1 register_element def ol(**attributes, &content) = nil
  309. # Outputs an `<optgroup>` tag.
  310. #
  311. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/optgroup)
  312. # [Spec](https://html.spec.whatwg.org/#the-optgroup-element)
  313. 1 register_element def optgroup(**attributes, &content) = nil
  314. # Outputs an `<option>` tag.
  315. #
  316. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/option)
  317. # [Spec](https://html.spec.whatwg.org/#the-option-element)
  318. 1 register_element def option(**attributes, &content) = nil
  319. # Outputs an `<output>` tag.
  320. #
  321. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/output)
  322. # [Spec](https://html.spec.whatwg.org/#the-output-element)
  323. 1 register_element def output(**attributes, &content) = nil
  324. # Outputs a `<p>` tag.
  325. #
  326. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/p)
  327. # [Spec](https://html.spec.whatwg.org/#the-p-element)
  328. 1 register_element def p(**attributes, &content) = nil
  329. # Outputs a `<picture>` tag.
  330. #
  331. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/picture)
  332. # [Spec](https://html.spec.whatwg.org/#the-picture-element)
  333. # [Can I Use?](https://caniuse.com/picture)
  334. 1 register_element def picture(**attributes, &content) = nil
  335. # Outputs a `<pre>` tag.
  336. #
  337. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/pre)
  338. # [Spec](https://html.spec.whatwg.org/#the-pre-element)
  339. 1 register_element def pre(**attributes, &content) = nil
  340. # Outputs a `<progress>` tag.
  341. #
  342. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/progress)
  343. # [Spec](https://html.spec.whatwg.org/#the-progress-element)
  344. 1 register_element def progress(**attributes, &content) = nil
  345. # Outputs a `<q>` tag.
  346. #
  347. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/q)
  348. # [Spec](https://html.spec.whatwg.org/#the-q-element)
  349. 1 register_element def q(**attributes, &content) = nil
  350. # Outputs an `<rp>` tag.
  351. #
  352. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/rp)
  353. # [Spec](https://html.spec.whatwg.org/#the-rp-element)
  354. 1 register_element def rp(**attributes, &content) = nil
  355. # Outputs an `<rt>` tag.
  356. #
  357. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/rt)
  358. # [Spec](https://html.spec.whatwg.org/#the-rt-element)
  359. 1 register_element def rt(**attributes, &content) = nil
  360. # Outputs a `<ruby>` tag. (The best tag ever!)
  361. #
  362. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ruby)
  363. # [Spec](https://html.spec.whatwg.org/#the-ruby-element)
  364. 1 register_element def ruby(**attributes, &content) = nil
  365. # Outputs an `<s>` tag.
  366. #
  367. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/s)
  368. # [Spec](https://html.spec.whatwg.org/#the-s-element)
  369. 1 register_element def s(**attributes, &content) = nil
  370. # Outputs a `<samp>` tag.
  371. #
  372. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/samp)
  373. # [Spec](https://html.spec.whatwg.org/#the-samp-element)
  374. 1 register_element def samp(**attributes, &content) = nil
  375. # Outputs a `<script>` tag.
  376. #
  377. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/script)
  378. # [Spec](https://html.spec.whatwg.org/#the-script-element)
  379. 1 register_element def script(**attributes, &content) = nil
  380. # Outputs a `<search>` tag.
  381. #
  382. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/search)
  383. # [Spec](https://html.spec.whatwg.org/#the-search-element)
  384. # [Can I Use?](https://caniuse.com/mdn-html_elements_search)
  385. 1 register_element def search(**attributes, &content) = nil
  386. # Outputs a `<section>` tag.
  387. #
  388. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/section)
  389. # [Spec](https://html.spec.whatwg.org/#the-section-element)
  390. 1 register_element def section(**attributes, &content) = nil
  391. # Outputs a `<select>` tag.
  392. #
  393. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/select)
  394. # [Spec](https://html.spec.whatwg.org/#the-select-element)
  395. 1 register_element def select(**attributes, &content) = nil
  396. # [EXPERIMENTAL] Outputs a `<selectedcontent>` tag.
  397. #
  398. # [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/selectedcontent)
  399. # [Draft Spec](https://github.com/whatwg/html/pull/10633)
  400. # [Can I Use?](https://caniuse.com/mdn-html_elements_selectedcontent)
  401. 1 register_element def selectedcontent(**attributes, &content) = nil
  402. # Outputs a `<slot>` tag.
  403. #
  404. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/slot)
  405. # [Spec](https://html.spec.whatwg.org/#the-slot-element)
  406. 1 register_element def slot(**attributes, &content) = nil
  407. # Outputs a `<small>` tag.
  408. #
  409. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/small)
  410. # [Spec](https://html.spec.whatwg.org/#the-small-element)
  411. 1 register_element def small(**attributes, &content) = nil
  412. # Outputs a `<span>` tag.
  413. #
  414. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/span)
  415. # [Spec](https://html.spec.whatwg.org/#the-span-element)
  416. 1 register_element def span(**attributes, &content) = nil
  417. # Outputs a `<strong>` tag.
  418. #
  419. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/strong)
  420. # [Spec](https://html.spec.whatwg.org/#the-strong-element)
  421. 1 register_element def strong(**attributes, &content) = nil
  422. # Outputs a `<style>` tag.
  423. #
  424. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/style)
  425. # [Spec](https://html.spec.whatwg.org/#the-style-element)
  426. 1 register_element def style(**attributes, &content) = nil
  427. # Outputs a `<sub>` tag.
  428. #
  429. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/sub)
  430. # [Spec](https://html.spec.whatwg.org/#the-sub-element)
  431. 1 register_element def sub(**attributes, &content) = nil
  432. # Outputs a `<summary>` tag.
  433. #
  434. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/summary)
  435. # [Spec](https://html.spec.whatwg.org/#the-summary-element)
  436. # [Can I Use?](https://caniuse.com/details)
  437. 1 register_element def summary(**attributes, &content) = nil
  438. # Outputs a `<sup>` tag.
  439. #
  440. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/sup)
  441. # [Spec](https://html.spec.whatwg.org/#the-sup-element)
  442. 1 register_element def sup(**attributes, &content) = nil
  443. # Outputs an `<svg>` tag.
  444. #
  445. # [MDN Docs](https://developer.mozilla.org/docs/Web/SVG/Element/svg)
  446. # [Spec](https://html.spec.whatwg.org/#the-svg-element)
  447. 1 register_element def svg(**attributes, &content) = nil
  448. # Outputs a `<table>` tag.
  449. #
  450. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/table)
  451. # [Spec](https://html.spec.whatwg.org/#the-table-element)
  452. 1 register_element def table(**attributes, &content) = nil
  453. # Outputs a `<tbody>` tag.
  454. #
  455. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tbody)
  456. # [Spec](https://html.spec.whatwg.org/#the-tbody-element)
  457. 1 register_element def tbody(**attributes, &content) = nil
  458. # Outputs a `<td>` tag.
  459. #
  460. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/td)
  461. # [Spec](https://html.spec.whatwg.org/#the-td-element)
  462. 1 register_element def td(**attributes, &content) = nil
  463. # Outputs a `<template>` tag.
  464. #
  465. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/template)
  466. # [Spec](https://html.spec.whatwg.org/#the-template-element)
  467. 1 register_element def template(**attributes, &content) = nil
  468. # Outputs a `<textarea>` tag.
  469. #
  470. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/textarea)
  471. # [Spec](https://html.spec.whatwg.org/#the-textarea-element)
  472. 1 register_element def textarea(**attributes, &content) = nil
  473. # Outputs a `<tfoot>` tag.
  474. #
  475. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tfoot)
  476. # [Spec](https://html.spec.whatwg.org/#the-tfoot-element)
  477. 1 register_element def tfoot(**attributes, &content) = nil
  478. # Outputs a `<th>` tag.
  479. #
  480. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/th)
  481. # [Spec](https://html.spec.whatwg.org/#the-th-element)
  482. 1 register_element def th(**attributes, &content) = nil
  483. # Outputs a `<thead>` tag.
  484. #
  485. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/thead)
  486. # [Spec](https://html.spec.whatwg.org/#the-thead-element)
  487. 1 register_element def thead(**attributes, &content) = nil
  488. # Outputs a `<time>` tag.
  489. #
  490. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/time)
  491. # [Spec](https://html.spec.whatwg.org/#the-time-element)
  492. 1 register_element def time(**attributes, &content) = nil
  493. # Outputs a `<title>` tag.
  494. #
  495. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/title)
  496. # [Spec](https://html.spec.whatwg.org/#the-title-element)
  497. 1 register_element def title(**attributes, &content) = nil
  498. # Outputs a `<tr>` tag.
  499. #
  500. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/tr)
  501. # [Spec](https://html.spec.whatwg.org/#the-tr-element)
  502. 1 register_element def tr(**attributes, &content) = nil
  503. # Outputs a `<u>` tag.
  504. #
  505. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/u)
  506. # [Spec](https://html.spec.whatwg.org/#the-u-element)
  507. 1 register_element def u(**attributes, &content) = nil
  508. # Outputs a `<ul>` tag.
  509. #
  510. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/ul)
  511. # [Spec](https://html.spec.whatwg.org/#the-ul-element)
  512. 1 register_element def ul(**attributes, &content) = nil
  513. # Outputs a `<var>` tag.
  514. #
  515. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/var)
  516. # [Spec](https://html.spec.whatwg.org/#the-var-element)
  517. 1 register_element def var(**attributes, &content) = nil
  518. # Outputs a `<video>` tag.
  519. #
  520. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/video)
  521. # [Spec](https://html.spec.whatwg.org/#the-video-element)
  522. # [Can I Use?](https://caniuse.com/video)
  523. 1 register_element def video(**attributes, &content) = nil
  524. # Outputs a `<wbr>` tag.
  525. #
  526. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/wbr)
  527. # [Spec](https://html.spec.whatwg.org/#the-wbr-element)
  528. 1 register_element def wbr(**attributes, &content) = nil
  529. end

lib/phlex/html/void_elements.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # Void HTML elements don't accept content and never have a closing tag.
  3. 1 module Phlex::HTML::VoidElements
  4. 1 extend Phlex::SGML::Elements
  5. # Outputs an `<area>` tag.
  6. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/area)
  7. # [Spec](https://html.spec.whatwg.org/#the-area-element)
  8. 1 __register_void_element__ def area(**attributes) = nil
  9. # Outputs a `<base>` tag.
  10. # [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
  11. # [Spec](https://html.spec.whatwg.org/#the-base-element)
  12. 1 __register_void_element__ def base(**attributes) = nil
  13. # Outputs a `<br>` tag.
  14. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/br)
  15. # [Spec](https://html.spec.whatwg.org/#the-br-element)
  16. 1 __register_void_element__ def br(**attributes) = nil
  17. # Outputs a `<col>` tag.
  18. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/col)
  19. # [Spec](https://html.spec.whatwg.org/#the-col-element)
  20. 1 __register_void_element__ def col(**attributes) = nil
  21. # Outputs an `<embed>` tag.
  22. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/embed)
  23. # [Spec](https://html.spec.whatwg.org/#the-embed-element)
  24. 1 __register_void_element__ def embed(**attributes) = nil
  25. # Outputs an `<hr>` tag.
  26. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/hr)
  27. # [Spec](https://html.spec.whatwg.org/#the-hr-element)
  28. 1 __register_void_element__ def hr(**attributes) = nil
  29. # Outputs an `<img>` tag.
  30. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/img)
  31. # [Spec](https://html.spec.whatwg.org/#the-img-element)
  32. 1 __register_void_element__ def img(**attributes) = nil
  33. # Outputs an `<input>` tag.
  34. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/input)
  35. # [Spec](https://html.spec.whatwg.org/#the-input-element)
  36. 1 __register_void_element__ def input(**attributes) = nil
  37. # Outputs a `<link>` tag.
  38. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/link)
  39. # [Spec](https://html.spec.whatwg.org/#the-link-element)
  40. 1 __register_void_element__ def link(**attributes) = nil
  41. # Outputs a `<meta>` tag.
  42. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/meta)
  43. # [Spec](https://html.spec.whatwg.org/#the-meta-element)
  44. 1 __register_void_element__ def meta(**attributes) = nil
  45. # Outputs a `<source>` tag.
  46. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/source)
  47. # [Spec](https://html.spec.whatwg.org/#the-source-element)
  48. 1 __register_void_element__ def source(**attributes) = nil
  49. # Outputs a `<track>` tag.
  50. # [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/track)
  51. # [Spec](https://html.spec.whatwg.org/#the-track-element)
  52. 1 __register_void_element__ def track(**attributes) = nil
  53. end

lib/phlex/kit.rb

85.0% lines covered

66.67% branches covered

40 relevant lines. 34 lines covered and 6 lines missed.
15 total branches, 10 branches covered and 5 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::Kit
  3. 1 module LazyLoader
  4. 1 def method_missing(name, ...)
  5. 2 mod = self.class
  6. 2 then: 2 if name[0] == name[0].upcase && mod.constants.include?(name) && mod.const_get(name) && methods.include?(name)
  7. 2 public_send(name, ...)
  8. else: 0 else
  9. super
  10. end
  11. end
  12. 1 def respond_to_missing?(name, include_private = false)
  13. mod = self.class
  14. (name[0] == name[0].upcase && mod.constants.include?(name) && mod.const_get(name) && methods.include?(name)) || super
  15. end
  16. end
  17. 1 def self.extended(mod)
  18. 3 case mod
  19. when: 0 when Class
  20. raise Phlex::ArgumentError.new(<<~MESSAGE)
  21. `Phlex::Kit` was extended into #{mod.name}.
  22. You should only extend modules with `Phlex::Kit` as it is not compatible with classes.
  23. MESSAGE
  24. else: 3 else
  25. 3 mod.include(LazyLoader)
  26. end
  27. end
  28. 1 def method_missing(name, ...)
  29. 1 then: 1 if name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)
  30. 1 public_send(name, ...)
  31. else: 0 else
  32. super
  33. end
  34. end
  35. 1 def respond_to_missing?(name, include_private = false)
  36. (name[0] == name[0].upcase && constants.include?(name) && const_get(name) && methods.include?(name)) || super
  37. end
  38. 1 def const_added(name)
  39. 9 then: 2 else: 7 return if autoload?(name)
  40. 7 me = self
  41. 7 constant = const_get(name)
  42. 7 else: 0 case constant
  43. when: 6 when Class
  44. 6 then: 6 else: 0 if constant < Phlex::SGML
  45. 6 constant.include(me)
  46. 6 constant = nil
  47. 6 define_method(name) do |*args, **kwargs, &block|
  48. 5 constant = me.const_get(name)
  49. 5 render(constant.new(*args, **kwargs), &block)
  50. end
  51. 6 define_singleton_method(name) do |*args, **kwargs, &block|
  52. 3 component = Thread.current[:__phlex_component__]
  53. 3 then: 2 if component
  54. 2 component.instance_exec do
  55. 2 constant = me.const_get(name)
  56. 2 render(constant.new(*args, **kwargs), &block)
  57. end
  58. else: 1 else
  59. 1 raise "You can't call `#{name}' outside of a Phlex rendering context."
  60. end
  61. end
  62. end
  63. when: 1 when Module
  64. 1 constant.extend(Phlex::Kit)
  65. end
  66. 7 super
  67. end
  68. end

lib/phlex/sgml.rb

86.54% lines covered

73.12% branches covered

208 relevant lines. 180 lines covered and 28 lines missed.
93 total branches, 68 branches covered and 25 branches missed.
    
  1. # frozen_string_literal: true
  2. # **Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}.
  3. 1 class Phlex::SGML
  4. 1 include Phlex::Helpers
  5. 1 class << self
  6. # Render the view to a String. Arguments are delegated to {.new}.
  7. 1 def call(...)
  8. 1362 new(...).call
  9. end
  10. # Create a new instance of the component.
  11. # @note The block will not be delegated to {#initialize}. Instead, it will be sent to {#view_template} when rendering.
  12. 1 def new(*a, **k, &block)
  13. 1637 then: 360 if block
  14. 360 object = super(*a, **k, &nil)
  15. 720 object.instance_exec { @_content_block = block }
  16. 360 object
  17. else: 1277 else
  18. 1277 super
  19. end
  20. end
  21. end
  22. 1 def view_template
  23. 209 then: 206 if block_given?
  24. 206 yield
  25. else: 3 else
  26. 3 plain "Phlex Warning: Your `#{self.class.name}` class doesn't define a `view_template` method. If you are upgrading to Phlex 2.x make sure to rename your `template` method to `view_template`. See: https://beta.phlex.fun/guides/v2-upgrade.html"
  27. end
  28. end
  29. 1 def to_proc
  30. 2 proc { |c| c.render(self) }
  31. end
  32. 1 def call(buffer = +"", context: {}, fragments: nil, &)
  33. 1614 state = Phlex::SGML::State.new(
  34. user_context: context,
  35. output_buffer: buffer,
  36. then: 14 else: 1600 fragments: fragments&.to_set,
  37. )
  38. 1614 internal_call(parent: nil, state:, &)
  39. 1560 state.output_buffer << state.buffer
  40. end
  41. 1 def internal_call(parent: nil, state: nil, &block)
  42. 1635 then: 1 else: 1634 if @_state
  43. 1 raise Phlex::DoubleRenderError.new(
  44. "You can't render a #{self.class.name} more than once."
  45. )
  46. end
  47. 1634 @_state = state
  48. 1634 else: 1632 then: 2 return "" unless render?
  49. 1632 block ||= @_content_block
  50. 1632 previous_phlex_component = Thread.current[:__phlex_component__]
  51. 1632 Thread.current[:__phlex_component__] = self
  52. 1632 state.around_render(self) do
  53. 1632 before_template(&block)
  54. 1632 around_template do
  55. 1632 then: 564 if block
  56. 564 view_template do |*args|
  57. 552 then: 344 if args.length > 0
  58. 344 __yield_content_with_args__(*args, &block)
  59. else: 208 else
  60. 208 __yield_content__(&block)
  61. end
  62. end
  63. else: 1068 else
  64. 1068 view_template
  65. end
  66. end
  67. 1573 after_template(&block)
  68. end
  69. ensure
  70. 1635 Thread.current[:__phlex_component__] = previous_phlex_component
  71. end
  72. 1 def context
  73. 6 then: 5 if rendering?
  74. 5 @_state.user_context
  75. else: 1 else
  76. 1 raise Phlex::ArgumentError.new(<<~MESSAGE)
  77. You can’t access the context before the component has started rendering.
  78. MESSAGE
  79. end
  80. end
  81. # Returns `false` before rendering and `true` once the component has started rendering.
  82. # It will not reset back to false after rendering.
  83. 1 def rendering?
  84. 6 !!@_state
  85. end
  86. # Output plain text.
  87. 1 def plain(content)
  88. 22 else: 21 then: 1 unless __text__(content)
  89. 1 raise Phlex::ArgumentError.new("You've passed an object to plain that is not handled by format_object. See https://rubydoc.info/gems/phlex/Phlex/SGML#format_object-instance_method for more information")
  90. end
  91. 21 nil
  92. end
  93. # Output a single space character. If a block is given, a space will be output before and after the block.
  94. 1 def whitespace(&)
  95. 8 state = @_state
  96. 8 else: 4 then: 4 return unless state.should_render?
  97. 4 buffer = state.buffer
  98. 4 buffer << " "
  99. 4 then: 2 else: 2 if block_given?
  100. 2 __yield_content__(&)
  101. 2 buffer << " "
  102. end
  103. 4 nil
  104. end
  105. # Wrap the output in an HTML comment.
  106. #
  107. # [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Comments)
  108. 1 def comment(&)
  109. 12 state = @_state
  110. 12 else: 4 then: 8 return unless state.should_render?
  111. 4 buffer = state.buffer
  112. 4 buffer << "<!-- "
  113. 4 __yield_content__(&)
  114. 4 buffer << " -->"
  115. 4 nil
  116. end
  117. # Output the given safe object as-is. You may need to use `safe` to mark a string as a safe object.
  118. 1 def raw(content)
  119. 6 case content
  120. when: 3 when Phlex::SGML::SafeObject
  121. 3 state = @_state
  122. 3 else: 3 then: 0 return unless state.should_render?
  123. 3 when: 2 state.buffer << content.to_s
  124. when nil, "" # do nothing
  125. else: 1 else
  126. 1 raise Phlex::ArgumentError.new("You passed an unsafe object to `raw`.")
  127. end
  128. 5 nil
  129. end
  130. # Capture the output of the block and returns it as a string.
  131. 1 def capture(*args, &block)
  132. 5 else: 5 then: 0 return "" unless block
  133. 5 then: 1 if args.length > 0
  134. 2 @_state.capture { __yield_content_with_args__(*args, &block) }
  135. else: 4 else
  136. 8 @_state.capture { __yield_content__(&block) }
  137. end
  138. end
  139. # Define a named fragment that can be selectively rendered.
  140. 1 def fragment(name)
  141. 39 state = @_state
  142. 39 state.begin_fragment(name)
  143. 39 yield
  144. 39 state.end_fragment(name)
  145. 33 nil
  146. end
  147. # Mark the given string as safe for HTML output.
  148. 1 def safe(value)
  149. 7 case value
  150. when: 6 when String
  151. 6 Phlex::SGML::SafeValue.new(value)
  152. else: 1 else
  153. 1 raise Phlex::ArgumentError.new("Expected a String.")
  154. end
  155. end
  156. 1 alias_method :🦺, :safe
  157. # Flush the current state to the output buffer.
  158. 1 def flush
  159. 6 @_state.flush
  160. end
  161. 1 def render(renderable = nil, &)
  162. 23 case renderable
  163. when: 21 when Phlex::SGML
  164. 21 renderable.internal_call(state: @_state, parent: self, &)
  165. when: 1 when Class
  166. 1 then: 1 else: 0 if renderable < Phlex::SGML
  167. 1 render(renderable.new, &)
  168. end
  169. when: 0 when Enumerable
  170. renderable.each { |r| render(r, &) }
  171. when: 0 when Proc, Method
  172. then: 0 if renderable.arity == 0
  173. __yield_content_with_no_yield_args__(&renderable)
  174. else: 0 else
  175. __yield_content__(&renderable)
  176. end
  177. when: 0 when String
  178. plain(renderable)
  179. when: 1 when nil
  180. 1 then: 1 else: 0 __yield_content__(&) if block_given?
  181. else: 0 else
  182. raise Phlex::ArgumentError.new("You can't render a #{renderable.inspect}.")
  183. end
  184. 23 nil
  185. end
  186. # Cache a block of content.
  187. #
  188. # ```ruby
  189. # @products.each do |product|
  190. # cache product do
  191. # h1 { product.name }
  192. # end
  193. # end
  194. # ```
  195. 1 def cache(*cache_key, **, &content)
  196. 25 location = caller_locations(1, 1)[0]
  197. full_key = [
  198. 25 app_version_key, # invalidates the key when deploying new code in case of changes
  199. self.class.name, # prevents collisions between classes
  200. 25 then: 0 else: 25 (self.class.object_id if enable_cache_reloading?), # enables reloading
  201. location.base_label, # prevents collisions between different methods
  202. location.lineno, # prevents collisions between different lines
  203. cache_key, # allows for custom cache keys
  204. ].freeze
  205. 25 low_level_cache(full_key, **, &content)
  206. 25 nil
  207. end
  208. # Cache a block of content where you control the entire cache key.
  209. # If you really know what you’re doing and want to take full control
  210. # and responsibility for the cache key, use this method.
  211. #
  212. # ```ruby
  213. # low_level_cache([Commonmarker::VERSION, Digest::MD5.hexdigest(@content)]) do
  214. # markdown(@content)
  215. # end
  216. # ```
  217. #
  218. # Note: To allow you more control, this method does not take a splat of cache keys.
  219. # If you need to pass multiple cache keys, you should pass an array.
  220. 1 def low_level_cache(cache_key, **options, &content)
  221. 25 state = @_state
  222. 44 cached_buffer, fragment_map = cache_store.fetch(cache_key, **options) { state.caching(&content) }
  223. 25 then: 18 if state.should_render?
  224. 18 fragment_map.each do |fragment_name, (offset, length, nested_fragments)|
  225. 38 state.record_fragment(fragment_name, offset, length, nested_fragments)
  226. end
  227. 18 state.buffer << cached_buffer
  228. else: 7 else
  229. 7 fragment_map.each do |fragment_name, (offset, length, nested_fragments)|
  230. 21 then: 7 else: 14 if state.fragments.include?(fragment_name)
  231. 7 state.fragments.delete(fragment_name)
  232. 7 state.fragments.subtract(nested_fragments)
  233. 7 state.buffer << cached_buffer.byteslice(offset, length)
  234. end
  235. end
  236. end
  237. 25 nil
  238. end
  239. 1 def json_escape(string)
  240. ERB::Util.json_escape(string)
  241. end
  242. # Override this method to use a different deployment key.
  243. 1 private def app_version_key
  244. 25 Phlex::DEPLOYED_AT
  245. end
  246. # Override this method to use a different cache store.
  247. 1 private def cache_store
  248. raise "Cache store not implemented."
  249. end
  250. 1 private def enable_cache_reloading?
  251. 25 false
  252. end
  253. 1 private def vanish(...)
  254. capture(...)
  255. nil
  256. end
  257. 1 private def render?
  258. 1630 true
  259. end
  260. 1 private def format_object(object)
  261. 29 else: 3 case object
  262. when: 26 when Float, Integer
  263. 26 object.to_s
  264. end
  265. end
  266. 1 private def around_template
  267. 1632 yield
  268. 1573 nil
  269. end
  270. 1 private def before_template
  271. 1631 nil
  272. end
  273. 1 private def after_template
  274. 1572 nil
  275. end
  276. 1 private def __yield_content__
  277. 554 else: 554 then: 0 return unless block_given?
  278. 554 buffer = @_state.buffer
  279. 554 original_length = buffer.bytesize
  280. 554 content = yield(self)
  281. 518 then: 10 else: 508 __implicit_output__(content) if original_length == buffer.bytesize
  282. 518 nil
  283. end
  284. 1 private def __yield_content_with_no_yield_args__
  285. else: 0 then: 0 return unless block_given?
  286. buffer = @_state.buffer
  287. original_length = buffer.bytesize
  288. content = yield # <-- doesn’t yield self 😉
  289. then: 0 else: 0 __implicit_output__(content) if original_length == buffer.bytesize
  290. nil
  291. end
  292. 1 private def __yield_content_with_args__(*a)
  293. 345 else: 345 then: 0 return unless block_given?
  294. 345 buffer = @_state.buffer
  295. 345 original_length = buffer.bytesize
  296. 345 content = yield(*a)
  297. 345 then: 338 else: 7 __implicit_output__(content) if original_length == buffer.bytesize
  298. 345 nil
  299. end
  300. 1 private def __implicit_output__(content)
  301. 348 state = @_state
  302. 348 else: 345 then: 3 return true unless state.should_render?
  303. 345 case content
  304. when: 0 when Phlex::SGML::SafeObject
  305. state.buffer << content.to_s
  306. when: 344 when String
  307. 344 state.buffer << Phlex::Escape.html_escape(content)
  308. when: 0 when Symbol
  309. state.buffer << Phlex::Escape.html_escape(content.name)
  310. when: 1 when nil
  311. nil
  312. else: 0 else
  313. then: 0 if (formatted_object = format_object(content))
  314. state.buffer << Phlex::Escape.html_escape(formatted_object)
  315. else: 0 else
  316. return false
  317. end
  318. end
  319. 345 true
  320. end
  321. # same as __implicit_output__ but escapes even `safe` objects
  322. 1 private def __text__(content)
  323. 22 state = @_state
  324. 22 else: 21 then: 1 return true unless state.should_render?
  325. 21 case content
  326. when: 16 when String
  327. 16 state.buffer << Phlex::Escape.html_escape(content)
  328. when: 1 when Symbol
  329. 1 state.buffer << Phlex::Escape.html_escape(content.name)
  330. when: 1 when nil
  331. nil
  332. else: 3 else
  333. 3 then: 2 if (formatted_object = format_object(content))
  334. 2 state.buffer << Phlex::Escape.html_escape(formatted_object)
  335. else: 1 else
  336. 1 return false
  337. end
  338. end
  339. 20 true
  340. end
  341. 1 private def __render_attributes__(attributes)
  342. 1 state = @_state
  343. 1 else: 1 then: 0 return unless state.should_render?
  344. 1 state.buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  345. end
  346. 1 private_class_method def self.method_added(method_name)
  347. 737 then: 704 else: 33 if method_name == :view_template
  348. 704 location = instance_method(method_name).source_location[0]
  349. 704 then: 703 else: 1 if location[0] in "/" | "."
  350. 703 Phlex.__expand_attribute_cache__(location)
  351. end
  352. end
  353. 737 super
  354. end
  355. 1 def self.__compile__(method_name)
  356. path, line = instance_method(method_name).source_location
  357. Phlex::Compiler::Method.new(self, path, line, method_name).compile
  358. end
  359. 1 def __map_exception__(exception)
  360. exception.set_backtrace(
  361. exception.backtrace_locations.map do |loc|
  362. then: 0 if ((map = Phlex::Compiler::MAP[loc.path]) && (line = map[loc.lineno]))
  363. "[Phlex] #{loc.path}:#{line}:#{loc.label}"
  364. else: 0 else
  365. "#{loc.path}:#{loc.lineno}:#{loc.label}"
  366. end
  367. end
  368. )
  369. exception
  370. end
  371. end

lib/phlex/sgml/attributes.rb

95.95% lines covered

94.96% branches covered

148 relevant lines. 142 lines covered and 6 lines missed.
119 total branches, 113 branches covered and 6 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::SGML::Attributes
  3. 1 extend self
  4. 1 UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze
  5. 1 REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping xlinkhref]).freeze
  6. 1 NAMED_CHARACTER_REFERENCES = {
  7. "colon" => ":",
  8. "tab" => "\t",
  9. "newline" => "\n",
  10. }.freeze
  11. 1 UNSAFE_ATTRIBUTE_NAME_CHARS = %r([<>&"'/=\s\x00])
  12. 1 def generate_attributes(attributes, buffer = +"")
  13. 237 attributes.each do |k, v|
  14. 297 else: 285 then: 12 next unless v
  15. 285 when: 20 name = case k
  16. 20 when: 264 when String then k
  17. 264 else: 1 when Symbol then k.name.tr("_", "-")
  18. 1 else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols.")
  19. end
  20. 284 value = case v
  21. when: 21 when true
  22. 21 true
  23. when: 122 when String
  24. 122 v.gsub('"', "&quot;")
  25. when: 12 when Symbol
  26. 12 v.name.tr("_", "-").gsub('"', "&quot;")
  27. when: 11 when Integer, Float
  28. 11 v.to_s
  29. when: 1 when Date
  30. 1 v.iso8601
  31. when: 1 when Time
  32. 1 then: 1 else: 0 v.respond_to?(:iso8601) ? v.iso8601 : v.strftime("%Y-%m-%dT%H:%M:%S%:z")
  33. when: 66 when Hash
  34. 66 case k
  35. when: 10 when :style
  36. 10 generate_styles(v).gsub('"', "&quot;")
  37. else: 56 else
  38. 56 generate_nested_attributes(v, "#{name}-", buffer)
  39. end
  40. when: 27 when Array
  41. 27 case k
  42. when: 8 when :style
  43. 8 generate_styles(v).gsub('"', "&quot;")
  44. else: 19 else
  45. 19 generate_nested_tokens(v)
  46. end
  47. when: 19 when Set
  48. 19 case k
  49. when: 2 when :style
  50. 2 generate_styles(v).gsub('"', "&quot;")
  51. else: 17 else
  52. 17 generate_nested_tokens(v.to_a)
  53. end
  54. when: 2 when Phlex::SGML::SafeObject
  55. 2 v.to_s.gsub('"', "&quot;")
  56. else: 2 else
  57. 2 then: 1 if v.respond_to?(:to_h)
  58. 1 generate_nested_attributes(v.to_h, "#{name}-", buffer)
  59. else: 1 else
  60. 1 raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
  61. end
  62. end
  63. 266 lower_name = name.downcase
  64. 266 else: 2 then: 264 unless Phlex::SGML::SafeObject === v
  65. 264 normalized_name = lower_name.delete("^a-z-")
  66. 264 then: 28 else: 236 if value != true && REF_ATTRIBUTES.include?(normalized_name)
  67. 28 case value
  68. when: 27 when String
  69. 27 decoded_value = decode_html_character_references(value)
  70. 27 else: 12 if decoded_value.downcase.delete("^a-z:").start_with?("javascript:")
  71. then: 15 # We just ignore these because they were likely not specified by the developer.
  72. 15 next
  73. end
  74. else: 1 else
  75. 1 raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.")
  76. end
  77. end
  78. 248 then: 1 else: 247 if normalized_name.bytesize > 2 && normalized_name.start_with?("on") && !normalized_name.include?("-")
  79. 1 raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  80. end
  81. 247 then: 0 else: 247 if UNSAFE_ATTRIBUTES.include?(normalized_name)
  82. raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  83. end
  84. end
  85. 249 then: 9 else: 240 if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
  86. 9 raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  87. end
  88. 240 then: 3 else: 237 if lower_name.to_sym == :id && k != :id
  89. 3 raise Phlex::ArgumentError.new(":id attribute should only be passed as a lowercase symbol.")
  90. end
  91. 237 else: 55 case value
  92. when: 11 when true
  93. 11 buffer << " " << name
  94. when: 171 when String
  95. 171 buffer << " " << name << '="' << value << '"'
  96. end
  97. end
  98. 204 buffer
  99. end
  100. # Provides the nested-attributes case for serializing out attributes.
  101. # This allows us to skip many of the checks the `__attributes__` method must perform.
  102. 1 def generate_nested_attributes(attributes, base_name, buffer = +"")
  103. 59 attributes.each do |k, v|
  104. 60 else: 57 then: 3 next unless v
  105. 57 then: 2 if (root_key = (:_ == k))
  106. 2 name = ""
  107. 2 original_base_name = base_name
  108. 2 base_name = base_name.delete_suffix("-")
  109. else: 55 else
  110. 55 when: 7 name = case k
  111. 7 when: 47 when String then k
  112. 47 else: 1 when Symbol then k.name.tr("_", "-")
  113. 1 else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols")
  114. end
  115. 54 then: 10 else: 44 if name.match?(UNSAFE_ATTRIBUTE_NAME_CHARS)
  116. 10 raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.")
  117. end
  118. end
  119. 46 case v
  120. when: 3 when true
  121. 3 buffer << " " << base_name << name
  122. when: 17 when String
  123. 17 buffer << " " << base_name << name << '="' << v.gsub('"', "&quot;") << '"'
  124. when: 6 when Symbol
  125. 6 buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', "&quot;") << '"'
  126. when: 8 when Integer, Float
  127. 8 buffer << " " << base_name << name << '="' << v.to_s << '"'
  128. when: 2 when Hash
  129. 2 generate_nested_attributes(v, "#{base_name}#{name}-", buffer)
  130. when: 3 when Array
  131. 3 then: 1 else: 2 if (value = generate_nested_tokens(v))
  132. 1 buffer << " " << base_name << name << '="' << value << '"'
  133. end
  134. when: 5 when Set
  135. 5 then: 1 else: 4 if (value = generate_nested_tokens(v.to_a))
  136. 1 buffer << " " << base_name << name << '="' << value << '"'
  137. end
  138. when: 1 when Phlex::SGML::SafeObject
  139. 1 buffer << " " << base_name << name << '="' << v.to_s.gsub('"', "&quot;") << '"'
  140. else: 1 else
  141. 1 raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.")
  142. end
  143. 45 then: 2 else: 43 if root_key
  144. 2 base_name = original_base_name
  145. end
  146. 45 buffer
  147. end
  148. end
  149. 1 def decode_html_character_references(value)
  150. 27 value
  151. .gsub(/&#x([0-9a-f]+);?/i) {
  152. begin
  153. 1 [$1.to_i(16)].pack("U*")
  154. rescue
  155. ""
  156. end
  157. }
  158. .gsub(/&#(\d+);?/) {
  159. begin
  160. 5 [$1.to_i].pack("U*")
  161. rescue
  162. ""
  163. end
  164. }
  165. .gsub(/&([a-z][a-z0-9]+);?/i) {
  166. 2 NAMED_CHARACTER_REFERENCES[$1.downcase] || ""
  167. }
  168. end
  169. 1 def generate_nested_tokens(tokens, sep = " ", gsub_from = nil, gsub_to = "")
  170. 71 buffer = +""
  171. 71 i, length = 0, tokens.length
  172. 71 body: 126 while i < length
  173. 126 token = tokens[i]
  174. 126 case token
  175. when: 44 when String
  176. 44 then: 23 else: 21 token = token.gsub(gsub_from, gsub_to) if gsub_from
  177. 44 then: 22 if i > 0
  178. 22 buffer << sep << token
  179. else: 22 else
  180. 22 buffer << token
  181. end
  182. when: 18 when Symbol
  183. 18 then: 12 if i > 0
  184. 12 buffer << sep << token.name.tr("_", "-")
  185. else: 6 else
  186. 6 buffer << token.name.tr("_", "-")
  187. end
  188. when: 24 when Integer, Float, Phlex::SGML::SafeObject
  189. 24 then: 14 if i > 0
  190. 14 buffer << sep << token.to_s
  191. else: 10 else
  192. 10 buffer << token.to_s
  193. end
  194. when: 12 when Array
  195. 12 then: 5 else: 7 if token.length > 0 && (value = generate_nested_tokens(token, sep, gsub_from, gsub_to))
  196. 5 then: 4 if i > 0
  197. 4 buffer << sep << value
  198. else: 1 else
  199. 1 buffer << value
  200. end
  201. end
  202. when: 6 when Set
  203. 6 then: 0 else: 6 if token.length > 0 && (value = generate_nested_tokens(token.to_a, sep, gsub_from, gsub_to))
  204. then: 0 if i > 0
  205. buffer << sep << value
  206. else: 0 else
  207. buffer << value
  208. end
  209. when: 20 end
  210. when nil
  211. # Do nothing
  212. else: 2 else
  213. 2 raise Phlex::ArgumentError.new("Invalid token type: #{token.class}.")
  214. end
  215. 124 i += 1
  216. end
  217. 69 then: 28 else: 41 return if buffer.empty?
  218. 41 buffer.gsub('"', "&quot;")
  219. end
  220. # The result is unsafe so should be escaped.
  221. 1 def generate_styles(styles)
  222. 22 else: 0 case styles
  223. when: 10 when Array, Set
  224. 10 styles.filter_map do |s|
  225. 14 case s
  226. when: 5 when String
  227. 5 then: 1 if s == "" || s.end_with?(";")
  228. 1 s
  229. else: 4 else
  230. 4 "#{s};"
  231. end
  232. when: 2 when Phlex::SGML::SafeObject
  233. 2 value = s.to_s
  234. 2 then: 1 else: 1 value.end_with?(";") ? value : "#{value};"
  235. when: 2 when Hash
  236. 2 next generate_styles(s)
  237. when: 4 when nil
  238. 4 next nil
  239. else: 1 else
  240. 1 raise Phlex::ArgumentError.new("Invalid style: #{s.inspect}.")
  241. end
  242. end.join(" ")
  243. when: 12 when Hash
  244. 12 buffer = +""
  245. 12 i = 0
  246. 12 styles.each do |k, v|
  247. 14 prop = case k
  248. when: 1 when String
  249. 1 k
  250. when: 12 when Symbol
  251. 12 k.name.tr("_", "-")
  252. else: 1 else
  253. 1 raise Phlex::ArgumentError.new("Style keys should be Strings or Symbols.")
  254. end
  255. 13 value = case v
  256. when: 5 when String
  257. 5 v
  258. when: 2 when Symbol
  259. 2 v.name.tr("_", "-")
  260. when: 4 when Integer, Float, Phlex::SGML::SafeObject
  261. 4 v.to_s
  262. when: 1 when nil
  263. 1 nil
  264. else: 1 else
  265. 1 raise Phlex::ArgumentError.new("Invalid style value: #{v.inspect}")
  266. end
  267. 12 then: 11 else: 1 if value
  268. 11 then: 9 if i == 0
  269. 9 buffer << prop << ": " << value << ";"
  270. else: 2 else
  271. 2 buffer << " " << prop << ": " << value << ";"
  272. end
  273. end
  274. 12 i += 1
  275. end
  276. 10 buffer
  277. end
  278. end
  279. end

lib/phlex/sgml/elements.rb

94.29% lines covered

24.83% branches covered

70 relevant lines. 66 lines covered and 4 lines missed.
3488 total branches, 866 branches covered and 2622 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::SGML::Elements
  3. 1 COMMA_SEPARATED_TOKENS = {
  4. img: <<~RUBY,
  5. if Array === (srcset_attribute = attributes[:srcset])
  6. attributes[:srcset] = Phlex::SGML::Attributes.generate_nested_tokens(srcset_attribute, ", ", ",", "%2C")
  7. end
  8. RUBY
  9. link: <<~RUBY,
  10. if Array === (media_attribute = attributes[:media])
  11. attributes[:media] = Phlex::SGML::Attributes.generate_nested_tokens(media_attribute, ", ", ",", "%2C")
  12. end
  13. if Array === (sizes_attribute = attributes[:sizes])
  14. attributes[:sizes] = Phlex::SGML::Attributes.generate_nested_tokens(sizes_attribute, ", ", ",", "%2C")
  15. end
  16. if Array === (imagesrcset_attribute = attributes[:imagesrcset])
  17. rel_attribute = attributes[:rel] || attributes["rel"]
  18. as_attribute = attributes[:as] || attributes["as"]
  19. if ("preload" == rel_attribute || :preload == rel_attribute) && ("image" == as_attribute || :image == as_attribute)
  20. attributes[:imagesrcset] = Phlex::SGML::Attributes.generate_nested_tokens(imagesrcset_attribute, ", ", ",", "%2C")
  21. end
  22. end
  23. RUBY
  24. input: <<~RUBY,
  25. if Array === (accept_attribute = attributes[:accept])
  26. type_attribute = attributes[:type] || attributes["type"]
  27. if "file" == type_attribute || :file == type_attribute
  28. attributes[:accept] = Phlex::SGML::Attributes.generate_nested_tokens(accept_attribute, ", ", ",", "%2C")
  29. end
  30. end
  31. RUBY
  32. }.freeze
  33. 1 def __registered_elements__
  34. 1037 @__registered_elements__ ||= {}
  35. end
  36. 1 def register_element(method_name, tag: method_name.name.tr("_", "-"))
  37. 167 class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
  38. # frozen_string_literal: true
  39. 1 def #{method_name}(**attributes)
  40. 2235 state = @_state
  41. 2235 buffer = state.buffer
  42. 2235 block_given = block_given?
  43. 2235 else: 21 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 10 else: 4 then: 0 else: 4 then: 0 else: 41 then: 0 else: 4 then: 0 else: 15 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 5 then: 0 else: 4 then: 0 else: 4 then: 0 else: 9 then: 0 else: 4 then: 0 else: 4 then: 0 else: 4 then: 0 else: 10 then: 0 else: 4 then: 0 else: 8 then: 0 else: 14 then: 0 else: 4 then: 0 else: 23 then: 2 else: 4 then: 0 else: 4 then: 0 else: 10 then: 0 else: 8 then: 0 else: 504 then: 0 else: 4 then: 0 else: 14 then: 0 else: 4 then: 0 else: 6 then: 0 else: 6 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 6 then: 0 else: 3 then: 0 else: 9 then: 0 else: 6 then: 0 else: 12 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 6 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 4 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 else: 3 then: 0 unless state.should_render?
  44. 20 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 13 else: 0 then: 0 else: 0 then: 0 else: 0 then: 13 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 2 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 6 else: 0 then: 0 else: 0 then: 0 else: 0 then: 7 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 12 else: 0 then: 3 else: 0 then: 3 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 yield(self) if block_given
  45. 15 return nil
  46. end
  47. 2215 then: 21 then: 4 then: 8 then: 12 then: 4 then: 8 then: 4 then: 4 then: 4 then: 12 then: 4 then: 166 then: 4 then: 4 then: 4 then: 4 then: 4 then: 11 then: 4 then: 4 then: 12 then: 15 then: 8 then: 4 then: 4 then: 4 then: 11 then: 4 then: 7 then: 4 then: 16 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 8 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 5 then: 12 then: 4 then: 511 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 104 then: 7 then: 4 then: 4 then: 4 then: 10 then: 4 then: 7 then: 4 then: 4 then: 4 then: 4 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 3 then: 6 then: 3 then: 9 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 6 then: 6 then: 6 then: 3 then: 3 then: 6 then: 3 then: 6 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 3 then: 3 then: 3 then: 3 if attributes.length > 0 # with attributes
  48. 1399 then: 7 then: 4 then: 20 then: 4 then: 7 then: 4 then: 11 then: 14 then: 4 then: 8 then: 4 then: 7 then: 16 then: 4 then: 7 then: 8 then: 4 then: 7 then: 4 then: 11 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 7 then: 4 then: 4 then: 4 then: 4 then: 7 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 9 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 4 then: 11 then: 4 then: 8 then: 4 then: 104 then: 504 then: 4 then: 4 then: 4 then: 4 then: 12 then: 4 then: 4 then: 4 then: 4 then: 7 then: 6 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 9 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 6 then: 3 then: 6 then: 3 then: 6 then: 6 then: 3 then: 3 then: 3 then: 3 then: 9 then: 3 then: 3 then: 6 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 then: 3 if block_given # with content block
  49. 1235 buffer << "<#{tag}"
  50. begin
  51. #{COMMA_SEPARATED_TOKENS[method_name]}
  52. 1235 buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  53. ensure
  54. 1235 buffer << ">"
  55. end
  56. begin
  57. 1235 original_length = buffer.bytesize
  58. 1235 content = yield(self)
  59. 1235 then: 14 else: 4 then: 3 else: 1 then: 14 else: 5 then: 3 else: 1 then: 7 else: 1 then: 7 else: 1 then: 6 else: 2 then: 7 else: 16 then: 3 else: 4 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 7 else: 4 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 7 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 4 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 4 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 2 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 6 else: 2 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 110 else: 2 then: 4 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 0 else: 4 then: 3 else: 101 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 7 else: 1 then: 3 else: 1 then: 3 else: 1 then: 3 else: 1 then: 6 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 6 else: 0 then: 3 else: 0 then: 6 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 3 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 if original_length == buffer.bytesize
  60. 510 case content
  61. when: 5 when: 0 when: 5 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 144 when: 10 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 509 when: 0 when: 12 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 104 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 6 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when ::Phlex::SGML::SafeObject
  62. buffer << content.to_s
  63. when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 6 when: 4 when: 3 when: 3 when: 12 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 6 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when: 3 when String
  64. 509 buffer << ::Phlex::Escape.html_escape(content)
  65. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when Symbol
  66. buffer << ::Phlex::Escape.html_escape(content.name)
  67. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when nil
  68. nil
  69. else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else
  70. 1 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 12 else: 0 then: 0 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 8 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 6 else: 2 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 if (formatted_object = format_object(content))
  71. buffer << ::Phlex::Escape.html_escape(formatted_object)
  72. end
  73. end
  74. end
  75. ensure
  76. 1235 buffer << "</#{tag}>"
  77. end
  78. else: 14 else: 0 else: 0 else: 0 else: 1 else: 0 else: 4 else: 3 else: 0 else: 0 else: 0 else: 1 else: 146 else: 0 else: 4 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 501 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else # without content
  79. 164 buffer << "<#{tag}"
  80. begin
  81. #{COMMA_SEPARATED_TOKENS[method_name]}
  82. 164 buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  83. ensure
  84. 164 buffer << "></#{tag}>"
  85. end
  86. end
  87. else: 0 else: 0 else: 0 else: 12 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 8 else: 0 else: 0 else: 0 else: 0 else: 0 else: 133 else: 2 else: 1 else: 0 else: 4 else: 7 else: 2 else: 0 else: 0 else: 27 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 5 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 2 else: 0 else: 8 else: 0 else: 0 else: 0 else: 1 else: 500 else: 0 else: 0 else: 0 else: 0 else: 2 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 100 else: 0 else: 10 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else # without attributes
  88. 816 then: 0 then: 3 then: 0 then: 0 then: 0 then: 4 then: 0 then: 0 then: 0 then: 4 then: 0 then: 4 then: 0 then: 0 then: 0 then: 3 then: 0 then: 4 then: 1 then: 0 then: 0 then: 0 then: 0 then: 2 then: 0 then: 132 then: 3 then: 0 then: 0 then: 0 then: 2 then: 0 then: 0 then: 2 then: 7 then: 0 then: 0 then: 0 then: 0 then: 0 then: 24 then: 0 then: 0 then: 0 then: 5 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 5 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 2 then: 0 then: 8 then: 0 then: 0 then: 0 then: 0 then: 500 then: 3 then: 10 then: 0 then: 0 then: 0 then: 7 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 2 then: 100 then: 0 then: 10 then: 0 then: 0 then: 0 then: 3 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 3 then: 0 then: 0 then: 0 then: 1 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 then: 0 if block_given # with content block
  89. 809 buffer << "<#{tag}>"
  90. begin
  91. 809 original_length = buffer.bytesize
  92. 809 content = yield(self)
  93. 810 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 181 else: 12 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 3 else: 1 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 5 else: 2 then: 4 else: 0 then: 4 else: 0 then: 5 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 132 else: 0 then: 1 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 2 then: 0 else: 0 then: 0 else: 2 then: 7 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 24 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 1 else: 0 then: 0 else: 0 then: 0 else: 8 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 500 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 2 else: 0 then: 0 else: 100 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 3 else: 0 then: 3 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 3 else: 0 then: 0 else: 1 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 if original_length == buffer.bytesize
  94. 680 case content
  95. when: 0 when: 0 when: 0 when: 15 when: 0 when: 4 when: 4 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 4 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 7 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 0 when: 1 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 7 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when ::Phlex::SGML::SafeObject
  96. 1 buffer << content.to_s
  97. when: 0 when: 0 when: 0 when: 5 when: 0 when: 3 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 1 when: 0 when: 0 when: 1 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 134 when: 1 when: 0 when: 3 when: 0 when: 1 when: 0 when: 0 when: 7 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 4 when: 3 when: 0 when: 0 when: 1 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 500 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 2 when: 3 when: 2 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 3 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when String
  98. 652 buffer << ::Phlex::Escape.html_escape(content)
  99. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when Symbol
  100. buffer << ::Phlex::Escape.html_escape(content.name)
  101. when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 2 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when: 0 when nil
  102. nil
  103. else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 24 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else
  104. 25 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 12 else: 0 then: 0 else: 0 then: 7 else: 4 else: 0 then: 0 else: 3 then: 13 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 7 else: 1 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 24 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 4 else: 3 then: 0 else: 0 then: 0 else: 0 then: 4 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 1 else: 9 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 3 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 then: 0 else: 0 if (formatted_object = format_object(content))
  105. 24 buffer << ::Phlex::Escape.html_escape(formatted_object)
  106. end
  107. end
  108. end
  109. ensure
  110. 809 buffer << "</#{tag}>"
  111. end
  112. else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 5 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 1 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 3 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else: 0 else # without content
  113. 7 buffer << "<#{tag}></#{tag}>"
  114. end
  115. end
  116. 6 then: 5 else: 166 #{'flush' if tag == 'head'}
  117. 2181 nil
  118. end
  119. RUBY
  120. 167 __registered_elements__[method_name] = tag
  121. 167 method_name
  122. end
  123. 1 def __register_void_element__(method_name, tag: method_name.name.tr("_", "-"))
  124. 12 class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
  125. # frozen_string_literal: true
  126. 1 def #{method_name}(**attributes)
  127. 68 state = @_state
  128. 68 else: 21 then: 4 else: 17 then: 0 else: 19 then: 0 else: 16 then: 0 return unless state.should_render?
  129. 64 buffer = state.buffer
  130. 64 then: 7 then: 6 then: 7 then: 3 then: 11 then: 8 then: 13 then: 14 then: 3 if attributes.length > 0 # with attributes
  131. 61 buffer << "<#{tag}"
  132. begin
  133. 32 then: 7 else: 11 then: 4 else: 4 then: 3 else: 10 #{COMMA_SEPARATED_TOKENS[method_name]}
  134. 39 buffer << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes))
  135. ensure
  136. 33 then: 11 else: 0 buffer << ">"
  137. 28 then: 2 else: 11 end
  138. 2 else: 3 else: 0 else: 0 else: 0 else: 8 else: 0 else # without attributes
  139. 14 buffer << "<#{tag}>"
  140. end
  141. 21 else: 0 then: 10 else: 10
  142. 36 nil
  143. 12 end
  144. RUBY
  145. 15 else: 0 then: 4 else: 0
  146. 8 __registered_elements__[method_name] = tag
  147. 12 method_name
  148. 8 end
  149. 13 end

lib/phlex/sgml/safe_object.rb

100.0% lines covered

100.0% branches covered

1 relevant lines. 1 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. # @api private
  3. 1 module Phlex::SGML::SafeObject
  4. # This is included in objects that are safe to render in an SGML context.
  5. # They must implement a `to_s` method that returns a string.
  6. end

lib/phlex/sgml/safe_value.rb

100.0% lines covered

100.0% branches covered

5 relevant lines. 5 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::SGML::SafeValue
  3. 1 include Phlex::SGML::SafeObject
  4. 1 def initialize(to_s)
  5. 12 @to_s = to_s
  6. end
  7. 1 attr_reader :to_s
  8. end

lib/phlex/sgml/state.rb

100.0% lines covered

95.45% branches covered

74 relevant lines. 74 lines covered and 0 lines missed.
22 total branches, 21 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::SGML::State
  3. 1 def initialize(user_context: {}, output_buffer:, fragments:)
  4. 1614 @buffer = +""
  5. 1614 @capturing = false
  6. 1614 @user_context = user_context
  7. 1614 @fragments = fragments
  8. 1614 @fragment_depth = 0
  9. 1614 @cache_stack = []
  10. 1614 @halt_signal = nil
  11. 1614 @output_buffer = output_buffer
  12. end
  13. 1 attr_accessor :capturing, :user_context
  14. 1 attr_reader :fragments, :fragment_depth, :output_buffer, :buffer
  15. 1 def around_render(component)
  16. 1632 stack = @stack
  17. 1632 then: 1618 if !@fragments || @halt_signal
  18. 1618 yield
  19. else: 14 else
  20. 14 catch do |signal|
  21. 14 @halt_signal = signal
  22. 14 yield
  23. end
  24. end
  25. end
  26. 1 def should_render?
  27. 3454 !@fragments || @fragment_depth > 0
  28. end
  29. 1 def begin_fragment(id)
  30. 39 then: 10 else: 29 then: 7 else: 32 @fragment_depth += 1 if @fragments&.include?(id)
  31. 39 then: 26 else: 13 if caching?
  32. 26 current_byte_offset = 0 # Start tracking the byte offset of this fragment from the start of the cache buffer
  33. 26 @cache_stack.reverse_each do |(cache_buffer, fragment_map)| # We'll iterate deepest to shallowest
  34. 42 current_byte_offset += cache_buffer.bytesize # Add the length of the cache buffer to the current byte offset
  35. 42 fragment_map[id] = [current_byte_offset, nil, []] # Record the byte offset, length, and store a list of the nested fragments
  36. 42 fragment_map.each do |name, (_offset, length, nested_fragments)| # Iterate over the other fragments
  37. 74 then: 42 else: 32 next if name == id || length # Skip if it's the current fragment, or if the fragment has already ended
  38. 32 nested_fragments << id # Add the current fragment to the list of nested fragments
  39. end
  40. end
  41. end
  42. end
  43. 1 def end_fragment(id)
  44. 39 then: 26 else: 13 if caching?
  45. 26 byte_length = nil
  46. 26 @cache_stack.reverse_each do |(cache_buffer, fragment_map)| # We'll iterate deepest to shallowest
  47. 42 byte_length ||= cache_buffer.bytesize - fragment_map[id][0] # The byte length is the difference between the current byte offset and the byte offset of the fragment
  48. 42 fragment_map[id][1] = byte_length # All cache contexts will use the same by
  49. end
  50. end
  51. 39 then: 10 else: 29 else: 7 then: 32 return unless @fragments&.include?(id)
  52. 7 @fragments.delete(id)
  53. 7 @fragment_depth -= 1
  54. 7 then: 6 else: 1 throw @halt_signal if @fragments.length == 0
  55. end
  56. 1 def record_fragment(id, offset, length, nested_fragments)
  57. 38 else: 20 then: 18 return unless caching?
  58. 20 @cache_stack.reverse_each do |(cache_buffer, fragment_map)|
  59. 20 offset += cache_buffer.bytesize
  60. 20 fragment_map[id] = [offset, length, nested_fragments]
  61. end
  62. end
  63. 1 def caching(&)
  64. 19 result = nil
  65. 19 capture do
  66. 19 @cache_stack.push([buffer, {}].freeze)
  67. 19 yield
  68. 19 result = @cache_stack.pop
  69. end
  70. 19 result
  71. end
  72. 1 def caching?
  73. 116 @cache_stack.length > 0
  74. end
  75. 1 def capture
  76. 24 new_buffer = +""
  77. 24 original_buffer = @buffer
  78. 24 original_capturing = @capturing
  79. 24 original_fragments = @fragments
  80. begin
  81. 24 @buffer = new_buffer
  82. 24 @capturing = true
  83. 24 @fragments = nil
  84. 24 yield
  85. ensure
  86. 24 @buffer = original_buffer
  87. 24 @capturing = original_capturing
  88. 24 @fragments = original_fragments
  89. end
  90. 24 new_buffer
  91. end
  92. 1 def flush
  93. 6 then: 0 else: 6 return if capturing
  94. 6 buffer = @buffer
  95. 6 @output_buffer << buffer.dup
  96. 6 buffer.clear
  97. 6 nil
  98. end
  99. end

lib/phlex/svg.rb

86.11% lines covered

70.0% branches covered

36 relevant lines. 31 lines covered and 5 lines missed.
20 total branches, 14 branches covered and 6 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Phlex::SVG < Phlex::SGML
  3. 1 include StandardElements
  4. # Returns the string "image/svg+xml"
  5. 1 def content_type
  6. 1 "image/svg+xml"
  7. end
  8. # Override to provide a filename for the SVG file
  9. 1 def filename
  10. nil
  11. end
  12. 1 def cdata(content = nil, &block)
  13. 2 state = @_state
  14. 2 else: 2 then: 0 return unless state.should_render?
  15. 2 then: 1 if !block && String === content
  16. 1 else: 1 state.buffer << "<![CDATA[" << content.gsub("]]>", "]]>]]<![CDATA[") << "]]>"
  17. 1 then: 1 elsif block && nil == content
  18. 1 state.buffer << "<![CDATA[" << capture(&block).gsub("]]>", "]]>]]<![CDATA[") << "]]>"
  19. else
  20. else: 0
  21. raise Phlex::ArgumentError.new("Expected a String or block.")
  22. end
  23. end
  24. 1 def tag(name, **attributes, &)
  25. 257 state = @_state
  26. 257 block_given = block_given?
  27. 257 buffer = state.buffer
  28. 257 else: 257 then: 0 unless state.should_render?
  29. then: 0 else: 0 yield(self) if block_given
  30. return nil
  31. end
  32. 257 else: 257 then: 0 unless Symbol === name
  33. raise Phlex::ArgumentError.new("Expected the tag name to be a Symbol.")
  34. end
  35. 257 then: 256 if (tag = StandardElements.__registered_elements__[name]) || ((tag = name.name.tr("_", "-")).include?("-") && tag.match?(/\A[a-z0-9-]+\z/))
  36. 256 then: 128 if attributes.length > 0 # with attributes
  37. 128 then: 64 if block_given # with content block
  38. 64 buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
  39. 64 __yield_content__(&)
  40. 64 buffer << "</#{tag}>"
  41. else: 64 else # without content
  42. 64 buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
  43. end
  44. else: 128 else # without attributes
  45. 128 then: 64 if block_given # with content block
  46. 64 buffer << ("<#{tag}>")
  47. 64 __yield_content__(&)
  48. 64 buffer << "</#{tag}>"
  49. else: 64 else # without content
  50. 64 buffer << "<#{tag}></#{tag}>"
  51. end
  52. end
  53. else: 1 else
  54. 1 raise Phlex::ArgumentError.new("Invalid SVG tag: #{name}")
  55. end
  56. end
  57. end

lib/phlex/svg/standard_elements.rb

100.0% lines covered

100.0% branches covered

66 relevant lines. 66 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Phlex::SVG::StandardElements
  3. 1 extend Phlex::SGML::Elements
  4. # Outputs an `<a>` tag.
  5. # See https://developer.mozilla.org/docs/Web/SVG/Element/a
  6. 1 register_element def a(
  7. download: nil,
  8. href: nil,
  9. hreflang: nil,
  10. ping: nil,
  11. referrerpolicy: nil,
  12. rel: nil,
  13. target: nil,
  14. type: nil,
  15. **attributes,
  16. &content
  17. ) = nil
  18. # Outputs an `<animate>` tag.
  19. # See https://developer.mozilla.org/docs/Web/SVG/Element/animate
  20. 1 register_element def animate(
  21. attributeName: nil,
  22. values: nil,
  23. dur: nil,
  24. repeatCount: nil,
  25. **attributes,
  26. &content
  27. ) = nil
  28. # Outputs an `<animateMotion>` tag.
  29. # See https://developer.mozilla.org/docs/Web/SVG/Element/animateMotion
  30. 1 register_element def animateMotion(
  31. keyPoints: nil,
  32. path: nil,
  33. rotate: nil,
  34. begin: nil,
  35. dur: nil,
  36. end: nil,
  37. repeatCount: nil,
  38. repeatDur: nil,
  39. fill: nil,
  40. calcMode: nil,
  41. values: nil,
  42. keyTimes: nil,
  43. keySplines: nil,
  44. from: nil,
  45. to: nil,
  46. by: nil,
  47. attributeName: nil,
  48. additive: nil,
  49. accumulate: nil,
  50. **attributes,
  51. &content
  52. ) = nil
  53. # Outputs an `<animateTransform>` tag.
  54. # See https://developer.mozilla.org/docs/Web/SVG/Element/animateTransform
  55. 1 register_element def animateTransform(
  56. **attributes,
  57. &content
  58. ) = nil
  59. # Outputs a `<circle>` tag.
  60. # See https://developer.mozilla.org/docs/Web/SVG/Element/circle
  61. 1 register_element def circle(
  62. **attributes,
  63. &content
  64. ) = nil
  65. # Outputs a `<clipPath>` tag.
  66. # See https://developer.mozilla.org/docs/Web/SVG/Element/clipPath
  67. 1 register_element def clipPath(
  68. **attributes,
  69. &content
  70. ) = nil
  71. # Outputs a `<defs>` tag.
  72. # See https://developer.mozilla.org/docs/Web/SVG/Element/defs
  73. 1 register_element def defs(
  74. **attributes,
  75. &content
  76. ) = nil
  77. # Outputs a `<desc>` tag.
  78. # See https://developer.mozilla.org/docs/Web/SVG/Element/desc
  79. 1 register_element def desc(
  80. **attributes,
  81. &content
  82. ) = nil
  83. # Outputs a `<discard>` tag.
  84. # See https://developer.mozilla.org/docs/Web/SVG/Element/discard
  85. 1 register_element def discard(
  86. **attributes,
  87. &content
  88. ) = nil
  89. # Outputs an `<ellipse>` tag.
  90. # See https://developer.mozilla.org/docs/Web/SVG/Element/ellipse
  91. 1 register_element def ellipse(
  92. **attributes,
  93. &content
  94. ) = nil
  95. # Outputs an `<feBlend>` tag.
  96. # See https://developer.mozilla.org/docs/Web/SVG/Element/feBlend
  97. 1 register_element def feBlend(
  98. **attributes,
  99. &content
  100. ) = nil
  101. # Outputs an `<feColorMatrix>` tag.
  102. # See https://developer.mozilla.org/docs/Web/SVG/Element/feColorMatrix
  103. 1 register_element def feColorMatrix(
  104. **attributes,
  105. &content
  106. ) = nil
  107. # Outputs an `<feComponentTransfer>` tag.
  108. # See https://developer.mozilla.org/docs/Web/SVG/Element/feComponentTransfer
  109. 1 register_element def feComponentTransfer(
  110. **attributes,
  111. &content
  112. ) = nil
  113. # Outputs an `<feComposite>` tag.
  114. # See https://developer.mozilla.org/docs/Web/SVG/Element/feComposite
  115. 1 register_element def feComposite(
  116. **attributes,
  117. &content
  118. ) = nil
  119. # Outputs an `<feConvolveMatrix>` tag.
  120. # See https://developer.mozilla.org/docs/Web/SVG/Element/feConvolveMatrix
  121. 1 register_element def feConvolveMatrix(
  122. **attributes,
  123. &content
  124. ) = nil
  125. # Outputs an `<feDiffuseLighting>` tag.
  126. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDiffuseLighting
  127. 1 register_element def feDiffuseLighting(
  128. **attributes,
  129. &content
  130. ) = nil
  131. # Outputs an `<feDisplacementMap>` tag.
  132. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDisplacementMap
  133. 1 register_element def feDisplacementMap(
  134. **attributes,
  135. &content
  136. ) = nil
  137. # Outputs an `<feDistantLight>` tag.
  138. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDistantLight
  139. 1 register_element def feDistantLight(
  140. **attributes,
  141. &content
  142. ) = nil
  143. # Outputs an `<feDropShadow>` tag.
  144. # See https://developer.mozilla.org/docs/Web/SVG/Element/feDropShadow
  145. 1 register_element def feDropShadow(
  146. **attributes,
  147. &content
  148. ) = nil
  149. # Outputs an `<feFlood>` tag.
  150. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFlood
  151. 1 register_element def feFlood(
  152. **attributes,
  153. &content
  154. ) = nil
  155. # Outputs an `<feFuncA>` tag.
  156. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncA
  157. 1 register_element def feFuncA(
  158. **attributes,
  159. &content
  160. ) = nil
  161. # Outputs an `<feFuncB>` tag.
  162. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncB
  163. 1 register_element def feFuncB(
  164. **attributes,
  165. &content
  166. ) = nil
  167. # Outputs an `<feFuncG>` tag.
  168. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncG
  169. 1 register_element def feFuncG(
  170. **attributes,
  171. &content
  172. ) = nil
  173. # Outputs an `<feFuncR>` tag.
  174. # See https://developer.mozilla.org/docs/Web/SVG/Element/feFuncR
  175. 1 register_element def feFuncR(
  176. **attributes,
  177. &content
  178. ) = nil
  179. # Outputs an `<feGaussianBlur>` tag.
  180. # See https://developer.mozilla.org/docs/Web/SVG/Element/feGaussianBlur
  181. 1 register_element def feGaussianBlur(
  182. **attributes,
  183. &content
  184. ) = nil
  185. # Outputs an `<feImage>` tag.
  186. # See https://developer.mozilla.org/docs/Web/SVG/Element/feImage
  187. 1 register_element def feImage(
  188. **attributes,
  189. &content
  190. ) = nil
  191. # Outputs an `<feMerge>` tag.
  192. # See https://developer.mozilla.org/docs/Web/SVG/Element/feMerge
  193. 1 register_element def feMerge(
  194. **attributes,
  195. &content
  196. ) = nil
  197. # Outputs an `<feMergeNode>` tag.
  198. # See https://developer.mozilla.org/docs/Web/SVG/Element/feMergeNode
  199. 1 register_element def feMergeNode(
  200. **attributes,
  201. &content
  202. ) = nil
  203. # Outputs an `<feMorphology>` tag.
  204. # See https://developer.mozilla.org/docs/Web/SVG/Element/feMorphology
  205. 1 register_element def feMorphology(
  206. **attributes,
  207. &content
  208. ) = nil
  209. # Outputs an `<feOffset>` tag.
  210. # See https://developer.mozilla.org/docs/Web/SVG/Element/feOffset
  211. 1 register_element def feOffset(
  212. **attributes,
  213. &content
  214. ) = nil
  215. # Outputs an `<fePointLight>` tag.
  216. # See https://developer.mozilla.org/docs/Web/SVG/Element/fePointLight
  217. 1 register_element def fePointLight(
  218. **attributes,
  219. &content
  220. ) = nil
  221. # Outputs an `<feSpecularLighting>` tag.
  222. # See https://developer.mozilla.org/docs/Web/SVG/Element/feSpecularLighting
  223. 1 register_element def feSpecularLighting(
  224. **attributes,
  225. &content
  226. ) = nil
  227. # Outputs an `<feSpotLight>` tag.
  228. # See https://developer.mozilla.org/docs/Web/SVG/Element/feSpotLight
  229. 1 register_element def feSpotLight(
  230. **attributes,
  231. &content
  232. ) = nil
  233. # Outputs an `<feTile>` tag.
  234. # See https://developer.mozilla.org/docs/Web/SVG/Element/feTile
  235. 1 register_element def feTile(
  236. **attributes,
  237. &content
  238. ) = nil
  239. # Outputs an `<feTurbulence>` tag.
  240. # See https://developer.mozilla.org/docs/Web/SVG/Element/feTurbulence
  241. 1 register_element def feTurbulence(
  242. **attributes,
  243. &content
  244. ) = nil
  245. # Outputs a `<filter>` tag.
  246. # See https://developer.mozilla.org/docs/Web/SVG/Element/filter
  247. 1 register_element def filter(
  248. **attributes,
  249. &content
  250. ) = nil
  251. # Outputs a `<foreignObject>` tag.
  252. # See https://developer.mozilla.org/docs/Web/SVG/Element/foreignObject
  253. 1 register_element def foreignObject(
  254. **attributes,
  255. &content
  256. ) = nil
  257. # Outputs a `<g>` tag.
  258. # See https://developer.mozilla.org/docs/Web/SVG/Element/g
  259. 1 register_element def g(
  260. **attributes,
  261. &content
  262. ) = nil
  263. # Outputs an `<image>` tag.
  264. # See https://developer.mozilla.org/docs/Web/SVG/Element/image
  265. 1 register_element def image(
  266. **attributes,
  267. &content
  268. ) = nil
  269. # Outputs a `<line>` tag.
  270. # See https://developer.mozilla.org/docs/Web/SVG/Element/line
  271. 1 register_element def line(
  272. **attributes,
  273. &content
  274. ) = nil
  275. # Outputs a `<linearGradient>` tag.
  276. # See https://developer.mozilla.org/docs/Web/SVG/Element/linearGradient
  277. 1 register_element def linearGradient(
  278. **attributes,
  279. &content
  280. ) = nil
  281. # Outputs a `<marker>` tag.
  282. # See https://developer.mozilla.org/docs/Web/SVG/Element/marker
  283. 1 register_element def marker(
  284. **attributes,
  285. &content
  286. ) = nil
  287. # Outputs a `<mask>` tag.
  288. # See https://developer.mozilla.org/docs/Web/SVG/Element/mask
  289. 1 register_element def mask(
  290. **attributes,
  291. &content
  292. ) = nil
  293. # Outputs a `<metadata>` tag.
  294. # See https://developer.mozilla.org/docs/Web/SVG/Element/metadata
  295. 1 register_element def metadata(
  296. **attributes,
  297. &content
  298. ) = nil
  299. # Outputs an `<mpath>` tag.
  300. # See https://developer.mozilla.org/docs/Web/SVG/Element/mpath
  301. 1 register_element def mpath(
  302. **attributes,
  303. &content
  304. ) = nil
  305. # Outputs a `<path>` tag.
  306. # See https://developer.mozilla.org/docs/Web/SVG/Element/path
  307. 1 register_element def path(
  308. **attributes,
  309. &content
  310. ) = nil
  311. # Outputs a `<pattern>` tag.
  312. # See https://developer.mozilla.org/docs/Web/SVG/Element/pattern
  313. 1 register_element def pattern(
  314. **attributes,
  315. &content
  316. ) = nil
  317. # Outputs a `<polygon>` tag.
  318. # See https://developer.mozilla.org/docs/Web/SVG/Element/polygon
  319. 1 register_element def polygon(
  320. **attributes,
  321. &content
  322. ) = nil
  323. # Outputs a `<polyline>` tag.
  324. # See https://developer.mozilla.org/docs/Web/SVG/Element/polyline
  325. 1 register_element def polyline(
  326. **attributes,
  327. &content
  328. ) = nil
  329. # Outputs a `<radialGradient>` tag.
  330. # See https://developer.mozilla.org/docs/Web/SVG/Element/radialGradient
  331. 1 register_element def radialGradient(
  332. **attributes,
  333. &content
  334. ) = nil
  335. # Outputs a `<rect>` tag.
  336. # See https://developer.mozilla.org/docs/Web/SVG/Element/rect
  337. 1 register_element def rect(
  338. **attributes,
  339. &content
  340. ) = nil
  341. # Outputs a `<script>` tag.
  342. # See https://developer.mozilla.org/docs/Web/SVG/Element/script
  343. 1 register_element def script(
  344. **attributes,
  345. &content
  346. ) = nil
  347. # Outputs a `<set>` tag.
  348. # See https://developer.mozilla.org/docs/Web/SVG/Element/set
  349. 1 register_element def set(
  350. **attributes,
  351. &content
  352. ) = nil
  353. # Outputs a `<stop>` tag.
  354. # See https://developer.mozilla.org/docs/Web/SVG/Element/stop
  355. 1 register_element def stop(
  356. **attributes,
  357. &content
  358. ) = nil
  359. # Outputs a `<style>` tag.
  360. # See https://developer.mozilla.org/docs/Web/SVG/Element/style
  361. 1 register_element def style(
  362. **attributes,
  363. &content
  364. ) = nil
  365. # Outputs an `<svg>` tag.
  366. # See https://developer.mozilla.org/docs/Web/SVG/Element/svg
  367. 1 register_element def svg(
  368. **attributes,
  369. &content
  370. ) = nil
  371. # Outputs a `<switch>` tag.
  372. # See https://developer.mozilla.org/docs/Web/SVG/Element/switch
  373. 1 register_element def switch(
  374. **attributes,
  375. &content
  376. ) = nil
  377. # Outputs a `<symbol>` tag.
  378. # See https://developer.mozilla.org/docs/Web/SVG/Element/symbol
  379. 1 register_element def symbol(
  380. **attributes,
  381. &content
  382. ) = nil
  383. # Outputs a `<text>` tag.
  384. # See https://developer.mozilla.org/docs/Web/SVG/Element/text
  385. 1 register_element def text(
  386. **attributes,
  387. &content
  388. ) = nil
  389. # Outputs a `<textPath>` tag.
  390. # See https://developer.mozilla.org/docs/Web/SVG/Element/textPath
  391. 1 register_element def textPath(
  392. **attributes,
  393. &content
  394. ) = nil
  395. # Outputs a `<title>` tag.
  396. # See https://developer.mozilla.org/docs/Web/SVG/Element/title
  397. 1 register_element def title(
  398. **attributes,
  399. &content
  400. ) = nil
  401. # Outputs a `<tspan>` tag.
  402. # See https://developer.mozilla.org/docs/Web/SVG/Element/tspan
  403. 1 register_element def tspan(
  404. **attributes,
  405. &content
  406. ) = nil
  407. # Outputs a `<use>` tag.
  408. # See https://developer.mozilla.org/docs/Web/SVG/Element/use
  409. 1 register_element def use(
  410. **attributes,
  411. &content
  412. ) = nil
  413. # Outputs a `<view>` tag.
  414. # See https://developer.mozilla.org/docs/Web/SVG/Element/view
  415. 1 register_element def view(
  416. **attributes,
  417. &content
  418. ) = nil
  419. end

quickdraw/attribute_cache_expansion.test.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class AttributeCacheExpansionTest < Quickdraw::Test
  3. 1 test "using a component without a source location" do
  4. 1 refute_raises do
  5. # Intentionally not passing a source location here.
  6. 1 eval <<~RUBY
  7. class Example < Phlex::HTML
  8. def view_template
  9. end
  10. end
  11. RUBY
  12. end
  13. end
  14. end

quickdraw/caching.test.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class CachingTest < Quickdraw::Test
  3. 1 CacheStore = Phlex::FIFOCacheStore.new
  4. 1 class CacheTest < Phlex::HTML
  5. 1 def cache_store = CacheStore
  6. 1 def initialize(execution_watcher)
  7. 2 @execution_watcher = execution_watcher
  8. end
  9. 1 def view_template
  10. 2 cache do
  11. 1 @execution_watcher.call
  12. 1 "OK"
  13. end
  14. end
  15. end
  16. 1 test "caching a block only executes once" do
  17. 1 run_count = 0
  18. 2 monitor = -> { run_count += 1 }
  19. 1 CacheTest.new(monitor).call
  20. 1 assert_equal run_count, 1
  21. 1 CacheTest.new(monitor).call
  22. 1 assert_equal run_count, 1
  23. end
  24. end

quickdraw/compilation_equivalence.test.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class CompilationEquivalenceTest < Quickdraw::Test
  3. 1 Dir["./compilation_equivalence_cases/*.rb", base: File.dirname(__FILE__)].each do |file|
  4. 9 test File.basename(file) do
  5. 9 load File.expand_path(file, File.dirname(__FILE__))
  6. 9 class_name = File.basename(file, ".rb").split("_").map(&:capitalize).join
  7. 9 component = Object.const_get(class_name)
  8. 9 before = component.new.call
  9. 9 Phlex::Compiler.compile(component)
  10. 9 after = component.new.call
  11. 9 assert_equal after, before
  12. end
  13. end
  14. 1 require_relative "../fixtures/page"
  15. 1 require_relative "../fixtures/layout"
  16. 1 test "benchmark fixtures" do
  17. 1 before = Example::Page.new.call
  18. 1 Phlex::Compiler.compile(Example::LayoutComponent)
  19. 1 Phlex::Compiler.compile(Example::Page)
  20. 1 after = Example::Page.new.call
  21. 1 assert_equal after, before
  22. end
  23. end

quickdraw/compilation_equivalence_cases/attributes.rb

66.67% lines covered

50.0% branches covered

3 relevant lines. 2 lines covered and 1 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Attributes < Phlex::HTML
  3. 1 def view_template
  4. div(
  5. a: nil,
  6. b: 1,
  7. c: :two,
  8. d: "three",
  9. e: 4.5,
  10. f: false,
  11. g: true,
  12. h: [nil, 1, :two, "three", 4.5, [1]],
  13. i: Set[nil, 1, :two, "three", 4.5, [1]],
  14. j: {
  15. k: 1,
  16. "l" => 2
  17. },
  18. "m" => 3
  19. )
  20. end
  21. end

quickdraw/compilation_equivalence_cases/basic.rb

40.0% lines covered

50.0% branches covered

5 relevant lines. 2 lines covered and 3 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Basic < Phlex::HTML
  3. 1 def view_template
  4. h1 { "Hello" }
  5. br
  6. br(class: "my-class")
  7. end
  8. end

quickdraw/compilation_equivalence_cases/comment.rb

50.0% lines covered

50.0% branches covered

4 relevant lines. 2 lines covered and 2 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Comment < Phlex::HTML
  3. 1 def view_template
  4. comment { "hello world" }
  5. comment { "Begin rendering #{self.class.name}" }
  6. end
  7. end

quickdraw/compilation_equivalence_cases/heredoc.rb

57.14% lines covered

50.0% branches covered

7 relevant lines. 4 lines covered and 3 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. 1 class Heredoc < Phlex::HTML
  2. 1 def view_template
  3. lines = 3
  4. unknown {
  5. p { <<~FIRST }
  6. This is a heredoc.
  7. It has #{lines} lines of text.
  8. And it's all going in this <p> tag.
  9. FIRST
  10. }
  11. end
  12. 1 def unknown
  13. 1 yield
  14. end
  15. end

quickdraw/compilation_equivalence_cases/interpolated_string.rb

28.57% lines covered

50.0% branches covered

7 relevant lines. 2 lines covered and 5 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class InterpolatedString < Phlex::HTML
  3. 1 def view_template
  4. name = "Joel"
  5. @ivar = "Will"
  6. h1 { "Hello, #{name}!" }
  7. h2 { "Hello, #@ivar!" } # rubocop:disable Style/VariableInterpolation
  8. h3 { "Hello #{"#{name}"}" }
  9. end
  10. end

quickdraw/compilation_equivalence_cases/override_elements.rb

33.33% lines covered

50.0% branches covered

9 relevant lines. 3 lines covered and 6 lines missed.
8 total branches, 4 branches covered and 4 branches missed.
    
  1. 1 class OverrideElements < Phlex::HTML
  2. 1 def view_template
  3. input(type: "email")
  4. h1
  5. input(type: "email")
  6. div do
  7. input(type: "email")
  8. end
  9. end
  10. 1 def input(**)
  11. plain("not-an-input")
  12. # super(class: "form-control", **)
  13. end
  14. end

quickdraw/compilation_equivalence_cases/plain.rb

33.33% lines covered

50.0% branches covered

6 relevant lines. 2 lines covered and 4 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Plain < Phlex::HTML
  3. 1 def view_template
  4. local_variable = "good"
  5. plain "Greetings "
  6. plain local_variable
  7. plain "sir!"
  8. end
  9. end

quickdraw/compilation_equivalence_cases/raw.rb

40.0% lines covered

50.0% branches covered

5 relevant lines. 2 lines covered and 3 lines missed.
4 total branches, 2 branches covered and 2 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Raw < Phlex::HTML
  3. 1 def view_template
  4. p { "output before" }
  5. raw(safe("<h1>raw output in the middle</h1>"))
  6. p { "output after" }
  7. end
  8. end

quickdraw/compilation_equivalence_cases/whitespace.rb

18.18% lines covered

50.0% branches covered

11 relevant lines. 2 lines covered and 9 lines missed.
2 total branches, 1 branches covered and 1 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class Whitespace < Phlex::HTML
  3. 1 def view_template
  4. h1 { "Hello" }
  5. whitespace
  6. h2 { "world" }
  7. br
  8. br
  9. p do
  10. plain "This sentence has"
  11. whitespace { em { "emphasis" } }
  12. plain "in it."
  13. end
  14. end
  15. end

quickdraw/compiler.test.rb

80.0% lines covered

100.0% branches covered

5 relevant lines. 4 lines covered and 1 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class CompilerTest < Quickdraw::Test
  3. # test "standard element, no args, no block" do
  4. # snippet = Prism.parse(<<~RUBY).value.statements.body.first
  5. # def foo
  6. # h1
  7. # end
  8. # RUBY
  9. # compiled = Phlex::Compiler::MethodCompiler.new(Phlex::HTML).compile(snippet)
  10. # assert_equal_ruby compiled.strip, out = <<~RUBY.strip
  11. # def foo
  12. # __phlex_buffer__ = @_state.buffer
  13. # __phlex_buffer__ << "<h1></h1>"
  14. # end
  15. # RUBY
  16. # end
  17. 1 test "sequential elements", skip: true do
  18. 1 snippet = Prism.parse(<<~RUBY).value.statements.body.first
  19. def foo
  20. div do
  21. "Hello"
  22. end
  23. end
  24. RUBY
  25. 1 compiled = Phlex::Compiler::MethodCompiler.new(Phlex::HTML).compile(snippet)
  26. # puts compiled
  27. assert_equal compiled.strip, out = <<~RUBY.strip
  28. def foo
  29. __phlex_buffer__ = @_state.buffer
  30. __phlex_buffer__ << "<h1></h1><h2></h2>"
  31. end
  32. RUBY
  33. end
  34. end

quickdraw/context.test.rb

100.0% lines covered

100.0% branches covered

20 relevant lines. 20 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class ContextTest < Quickdraw::Test
  3. 1 test "user context passed in from the outside" do
  4. 1 example = Class.new(Phlex::HTML) do
  5. 1 define_method :view_template do
  6. 2 h1 { context[:heading] }
  7. end
  8. end
  9. 1 context = { heading: "Hello, World!" }
  10. 1 assert_equal example.new.call(context:), %(<h1>Hello, World!</h1>)
  11. end
  12. 1 test "user context passed down" do
  13. 1 a = Class.new(Phlex::HTML) do
  14. 1 define_method :view_template do
  15. 2 h1 { context[:heading] }
  16. end
  17. end
  18. 1 b = Class.new(Phlex::HTML) do
  19. 1 define_method :view_template do
  20. 1 context[:heading] = "Hello, World!"
  21. 1 render a.new
  22. end
  23. end
  24. 1 assert_equal b.new.call, %(<h1>Hello, World!</h1>)
  25. end
  26. 1 test "raises an ArgumentError if you access the context before rendering" do
  27. 1 component = Phlex::HTML.new
  28. 2 error = assert_raises(Phlex::ArgumentError) { component.context }
  29. 1 assert_equal error.message, <<~MESSAGE
  30. You can’t access the context before the component has started rendering.
  31. MESSAGE
  32. end
  33. end

quickdraw/csv.test.rb

100.0% lines covered

100.0% branches covered

94 relevant lines. 94 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class CSVTest < Quickdraw::Test
  3. 1 Product = Struct.new(:name, :price)
  4. 1 class Base < Phlex::CSV
  5. 1 def row_template(product)
  6. 35 column("name", product.name)
  7. 35 column("price", product.price)
  8. end
  9. end
  10. products = [
  11. 1 Product.new("Apple", 1.00),
  12. Product.new(" Banana ", 2.00),
  13. Product.new(:strawberry, "Three pounds"),
  14. Product.new("=SUM(A1:B1)", "=SUM(A1:B1)"),
  15. Product.new("Abc, \"def\"", "Foo\nbar \"baz\""),
  16. Product.new("", ""),
  17. Product.new(nil, nil),
  18. ]
  19. 1 test "don’t escape csv injection or trim whitespace" do
  20. 1 example = Class.new(Base) do
  21. 3 define_method(:escape_csv_injection?) { false }
  22. 2 define_method(:trim_whitespace?) { false }
  23. end
  24. 1 assert_equal example.new(products).call, <<~CSV
  25. name,price
  26. Apple,1.0
  27. " Banana ",2.0
  28. strawberry,Three pounds
  29. =SUM(A1:B1),=SUM(A1:B1)
  30. "Abc, ""def""","Foo\nbar ""baz"""
  31. "",""
  32. "",""
  33. CSV
  34. end
  35. 1 test "don’t escape csv injection, but do trim whitespace" do
  36. 1 example = Class.new(Base) do
  37. 3 define_method(:escape_csv_injection?) { false }
  38. 2 define_method(:trim_whitespace?) { true }
  39. end
  40. 1 assert_equal example.new(products).call, <<~CSV
  41. name,price
  42. Apple,1.0
  43. Banana,2.0
  44. strawberry,Three pounds
  45. =SUM(A1:B1),=SUM(A1:B1)
  46. Abc, "def",Foo\nbar "baz"
  47. ,
  48. ,
  49. CSV
  50. end
  51. 1 test "escape csv injection, but don’t trim whitespace" do
  52. 1 example = Class.new(Base) do
  53. 3 define_method(:escape_csv_injection?) { true }
  54. 2 define_method(:trim_whitespace?) { false }
  55. end
  56. 1 assert_equal example.new(products).call, <<~CSV
  57. name,price
  58. Apple,1.0
  59. " Banana ",2.0
  60. strawberry,Three pounds
  61. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  62. "Abc, ""def""","Foo
  63. bar ""baz"""
  64. "",""
  65. "",""
  66. CSV
  67. end
  68. 1 test "escape csv injection and trim whitespace" do
  69. 1 example = Class.new(Base) do
  70. 3 define_method(:escape_csv_injection?) { true }
  71. 2 define_method(:trim_whitespace?) { true }
  72. end
  73. 1 assert_equal example.new(products).call, <<~CSV
  74. name,price
  75. Apple,1.0
  76. Banana,2.0
  77. strawberry,Three pounds
  78. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  79. "Abc, ""def""","Foo
  80. bar ""baz"""
  81. ,
  82. ,
  83. CSV
  84. end
  85. 1 test "no headers" do
  86. 1 example = Class.new(Base) do
  87. 2 define_method(:render_headers?) { false }
  88. 3 define_method(:escape_csv_injection?) { false }
  89. end
  90. 1 assert_equal example.new(products).call, <<~CSV
  91. Apple,1.0
  92. " Banana ",2.0
  93. strawberry,Three pounds
  94. =SUM(A1:B1),=SUM(A1:B1)
  95. "Abc, ""def""","Foo
  96. bar ""baz"""
  97. "",""
  98. "",""
  99. CSV
  100. end
  101. 1 test "with a custom around_row" do
  102. 1 example = Class.new(Phlex::CSV) do
  103. 1 def escape_csv_injection? = true
  104. 1 def around_row(item)
  105. 7 super(item.name, item.price)
  106. end
  107. 1 def row_template(name, price)
  108. 7 column "Name", name
  109. 7 column "Price", price
  110. end
  111. end
  112. 1 assert_equal example.new(products).call, <<~CSV
  113. Name,Price
  114. Apple,1.0
  115. " Banana ",2.0
  116. strawberry,Three pounds
  117. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  118. "Abc, ""def""","Foo
  119. bar ""baz"""
  120. "",""
  121. "",""
  122. CSV
  123. end
  124. 1 test "with an around_row that calls super more than once" do
  125. 1 example = Class.new(Phlex::CSV) do
  126. 1 def escape_csv_injection? = true
  127. 1 def trim_whitespace? = false
  128. 1 def around_row(item)
  129. 7 super(item.name, item.price)
  130. 7 super(item.name, item.price)
  131. end
  132. 1 def row_template(name, price)
  133. 14 column "Name", name
  134. 14 column "Price", price
  135. end
  136. end
  137. 1 assert_equal example.new(products).call, <<~CSV
  138. Name,Price
  139. Apple,1.0
  140. Apple,1.0
  141. " Banana ",2.0
  142. " Banana ",2.0
  143. strawberry,Three pounds
  144. strawberry,Three pounds
  145. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  146. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  147. "Abc, ""def""","Foo\nbar ""baz"""
  148. "Abc, ""def""","Foo\nbar ""baz"""
  149. "",""
  150. "",""
  151. "",""
  152. "",""
  153. CSV
  154. end
  155. 1 test "with a yielder that yields more than once" do
  156. 1 example = Class.new(Phlex::CSV) do
  157. 1 def escape_csv_injection? = true
  158. 1 def trim_whitespace? = false
  159. 1 def yielder(item)
  160. 7 yield(item.name, item.price)
  161. 7 yield(item.name, item.price)
  162. end
  163. 1 def row_template(name, price)
  164. 14 column "Name", name
  165. 14 column "Price", price
  166. end
  167. end
  168. 1 assert_equal example.new(products).call, <<~CSV
  169. Name,Price
  170. Apple,1.0
  171. Apple,1.0
  172. " Banana ",2.0
  173. " Banana ",2.0
  174. strawberry,Three pounds
  175. strawberry,Three pounds
  176. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  177. "'=SUM(A1:B1)","'=SUM(A1:B1)"
  178. "Abc, ""def""","Foo\nbar ""baz"""
  179. "Abc, ""def""","Foo\nbar ""baz"""
  180. "",""
  181. "",""
  182. "",""
  183. "",""
  184. CSV
  185. end
  186. 1 test "with a custom delimiter defined as a method" do
  187. 1 example = Class.new(Phlex::CSV) do
  188. 3 define_method(:escape_csv_injection?) { true }
  189. 2 define_method(:trim_whitespace?) { true }
  190. 2 define_method(:delimiter) { ";" }
  191. 1 define_method(:row_template) do |product|
  192. 7 column "Name", product.name
  193. 7 column "Price", product.price
  194. end
  195. end
  196. 1 assert_equal example.new(products).call, <<~CSV
  197. Name;Price
  198. Apple;1.0
  199. Banana;2.0
  200. strawberry;Three pounds
  201. "'=SUM(A1:B1)";"'=SUM(A1:B1)"
  202. "Abc, ""def""";"Foo
  203. bar ""baz"""
  204. ;
  205. ;
  206. CSV
  207. end
  208. 1 test "with a custom delimiter passed in as an argument" do
  209. 1 example = Class.new(Phlex::CSV) do
  210. 3 define_method(:escape_csv_injection?) { true }
  211. 2 define_method(:trim_whitespace?) { true }
  212. 1 define_method(:row_template) do |product|
  213. 7 column "Name", product.name
  214. 7 column "Price", product.price
  215. end
  216. end
  217. 1 assert_equal example.new(products).call(delimiter: ";"), <<~CSV
  218. Name;Price
  219. Apple;1.0
  220. Banana;2.0
  221. strawberry;Three pounds
  222. "'=SUM(A1:B1)";"'=SUM(A1:B1)"
  223. "Abc, ""def""";"Foo
  224. bar ""baz"""
  225. ;
  226. ;
  227. CSV
  228. end
  229. 1 test "with an invalid custom delimiter" do
  230. 1 example = Class.new(Base) do
  231. 3 define_method(:escape_csv_injection?) { true }
  232. end
  233. 1 error = assert_raises(Phlex::ArgumentError) do
  234. 1 example.new([]).call(delimiter: "invalid")
  235. end
  236. 1 assert_equal error.message, "Delimiter must be a single character"
  237. end
  238. 1 test "content type" do
  239. 1 assert_equal Base.new([]).content_type, "text/csv"
  240. end
  241. 1 test "filename is nil by default" do
  242. 1 assert_equal Base.new([]).filename, nil
  243. end
  244. 1 test "raises an error if there's no escape plan" do
  245. 1 error = assert_raises(RuntimeError) do
  246. 1 Base.new([]).call
  247. end
  248. 1 assert_includes error.message, "escape_csv_injection?"
  249. end
  250. end

quickdraw/fifo.test.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class FIFOTest < Quickdraw::Test
  3. 1 test "expires keys" do
  4. 1 fifo = Phlex::FIFO.new(max_bytesize: 3)
  5. 1 100.times do |i|
  6. 100 fifo[i] = "a"
  7. end
  8. 1 assert_equal fifo.size, 3
  9. end
  10. 1 test "reading a key" do
  11. 1 fifo = Phlex::FIFO.new(max_bytesize: 3)
  12. 1 fifo[0] = "a"
  13. 1 assert_equal fifo[0], "a"
  14. end
  15. 1 test "ignores values that are too large" do
  16. 1 fifo = Phlex::FIFO.new(max_bytesize: 100, max_value_bytesize: 10)
  17. 1 fifo[1] = "a" * 10
  18. 1 fifo[2] = "a" * 11
  19. 1 assert_equal fifo.size, 1
  20. end
  21. end

quickdraw/fifo_cache_store.test.rb

90.0% lines covered

100.0% branches covered

20 relevant lines. 18 lines covered and 2 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class FIFOCacheStoreTest < Quickdraw::Test
  3. 1 test "fetch caches the yield" do
  4. 1 store = Phlex::FIFOCacheStore.new
  5. 1 count = 0
  6. 1 first_read = store.fetch("a") do
  7. 1 count += 1
  8. 1 "A"
  9. end
  10. 1 assert_equal first_read, "A"
  11. 1 assert_equal count, 1
  12. 1 second_read = store.fetch("a") do
  13. failure! { "This block should not have been called." }
  14. "B"
  15. end
  16. 1 assert_equal second_read, "A"
  17. 1 assert_equal count, 1
  18. end
  19. 1 test "nested caches do not lead to contention" do
  20. 1 store = Phlex::FIFOCacheStore.new
  21. 1 result = store.fetch("a") do
  22. [
  23. 1 "A",
  24. 1 store.fetch("b") { "B" },
  25. ].join(", ")
  26. end
  27. 1 assert_equal result, "A, B"
  28. end
  29. end

quickdraw/helpers/grab.test.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class GrabTest < Quickdraw::Test
  3. 1 include Phlex::Helpers
  4. 1 test "single binding" do
  5. 1 output = grab(class: "foo")
  6. 1 assert_equal output, "foo"
  7. end
  8. 1 test "multiple bindings" do
  9. 1 output = grab(class: "foo", if: "bar")
  10. 1 assert_equal output, ["foo", "bar"]
  11. end
  12. end

quickdraw/helpers/mix.test.rb

100.0% lines covered

100.0% branches covered

47 relevant lines. 47 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class MixTest < Quickdraw::Test
  3. 1 include Phlex::Helpers
  4. 1 test "nil + string" do
  5. 1 output = mix({ class: nil }, { class: "a" })
  6. 1 assert_equal output, { class: "a" }
  7. end
  8. 1 test "string + nil" do
  9. 1 output = mix({ class: "a" }, { class: nil })
  10. 1 assert_equal output, { class: "a" }
  11. end
  12. 1 test "array + nil" do
  13. 1 output = mix({ class: ["foo", "bar"] }, { class: nil })
  14. 1 assert_equal output, { class: ["foo", "bar"] }
  15. end
  16. 1 test "array + array" do
  17. 1 output = mix({ class: ["foo"] }, { class: ["bar"] })
  18. 1 assert_equal output, { class: ["foo", "bar"] }
  19. end
  20. 1 test "array + set" do
  21. 1 output = mix({ class: ["foo"] }, { class: Set["bar"] })
  22. 1 assert_equal output, { class: ["foo", "bar"] }
  23. end
  24. 1 test "array + hash" do
  25. 1 output = mix(
  26. { data: ["foo"] },
  27. { data: { controller: "bar" } },
  28. )
  29. 1 assert_equal output, { data: { _: ["foo"], controller: "bar" } }
  30. end
  31. 1 test "array + string" do
  32. 1 output = mix({ class: ["foo"] }, { class: "bar" })
  33. 1 assert_equal output, { class: ["foo", "bar"] }
  34. end
  35. 1 test "hash + hash" do
  36. 1 output = mix(
  37. { data: { controller: "foo" } },
  38. { data: { controller: "bar" } },
  39. )
  40. 1 assert_equal output, { data: { controller: "foo bar" } }
  41. end
  42. 1 test "string + array" do
  43. 1 output = mix({ class: "foo" }, { class: ["bar"] })
  44. 1 assert_equal output, { class: ["foo", "bar"] }
  45. end
  46. 1 test "string + string" do
  47. 1 output = mix({ class: "foo" }, { class: "bar" })
  48. 1 assert_equal output, { class: "foo bar" }
  49. end
  50. 1 test "string + set" do
  51. 1 output = mix({ class: "foo" }, { class: Set["bar"] })
  52. 1 assert_equal output, { class: ["foo", "bar"] }
  53. end
  54. 1 test "override" do
  55. 1 output = mix({ class: "foo" }, { class!: "bar" })
  56. 1 assert_equal output, { class: "bar" }
  57. end
  58. 1 test "set + set" do
  59. 1 output = mix({ class: Set["foo"] }, { class: Set["bar"] })
  60. 1 assert_equal output, { class: Set["foo", "bar"] }
  61. end
  62. 1 test "set + array" do
  63. 1 output = mix({ class: Set["foo"] }, { class: ["bar"] })
  64. 1 assert_equal output, { class: ["foo", "bar"] }
  65. end
  66. 1 test "set + string" do
  67. 1 output = mix({ class: Set["foo"] }, { class: "bar" })
  68. 1 assert_equal output, { class: ["foo", "bar"] }
  69. end
  70. end

quickdraw/html.test.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class HTMLTest < Quickdraw::Test
  3. 1 test "content type" do
  4. 1 component = Class.new(Phlex::HTML)
  5. 1 assert_equal component.new.content_type, "text/html"
  6. end
  7. end

quickdraw/html/doctype.test.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class DoctypeTest < Quickdraw::Test
  3. 1 class Example < Phlex::HTML
  4. 1 def view_template
  5. 1 html {
  6. 1 head {
  7. 1 doctype
  8. }
  9. }
  10. end
  11. end
  12. 1 test do
  13. 1 assert_equal Example.call, "<html><head><!doctype html></head></html>"
  14. end
  15. end

quickdraw/html/standard_elements.test.rb

100.0% lines covered

100.0% branches covered

22 relevant lines. 22 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class StandardElementsTest < Quickdraw::Test
  3. 1 Phlex::HTML::StandardElements.__registered_elements__.each do |method_name, tag|
  4. 103 test "<#{tag}> with block content and attributes" do
  5. 103 example = Class.new(Phlex::HTML) do
  6. 103 define_method :view_template do
  7. 309 __send__(method_name, class: "class", id: "id", disabled: true, selected: false) { h1 { "Hello" } }
  8. end
  9. end
  10. 103 assert_equal example.call, %(<#{tag} class="class" id="id" disabled><h1>Hello</h1></#{tag}>)
  11. end
  12. 103 test "<#{tag}> with block text content and attributes" do
  13. 103 example = Class.new(Phlex::HTML) do
  14. 103 define_method :view_template do
  15. 206 __send__(method_name, class: "class", id: "id", disabled: true, selected: false) { "content" }
  16. end
  17. end
  18. 103 assert_equal example.call, %(<#{tag} class="class" id="id" disabled>content</#{tag}>)
  19. end
  20. 103 test "<#{tag}> with string attribute keys" do
  21. 103 example = Class.new(Phlex::HTML) do
  22. 103 define_method :view_template do
  23. 206 __send__(method_name, "attribute_with_underscore" => true) { "content" }
  24. end
  25. end
  26. 103 assert_equal example.call, %(<#{tag} attribute_with_underscore>content</#{tag}>)
  27. end
  28. 103 test "<#{tag}> with hash attribute values" do
  29. 103 example = Class.new(Phlex::HTML) do
  30. 103 define_method :view_template do
  31. 206 __send__(method_name, aria: { hidden: true }, data_turbo: { frame: "_top" }) { "content" }
  32. end
  33. end
  34. 103 assert_equal example.call, %(<#{tag} aria-hidden data-turbo-frame="_top">content</#{tag}>)
  35. end
  36. end
  37. end

quickdraw/html/svg.test.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class HTMLSVGTest < Quickdraw::Test
  3. 1 class Example < Phlex::HTML
  4. 1 def view_template
  5. 1 svg do |s|
  6. 1 s.path(d: "321")
  7. end
  8. end
  9. end
  10. 1 class ExampleWithoutContent < Phlex::HTML
  11. 1 def view_template
  12. 1 svg
  13. end
  14. end
  15. 1 test "rendering SVG without content" do
  16. 1 assert_equal %(<svg></svg>), ExampleWithoutContent.call
  17. end
  18. 1 test "rendering SVG inside HTML components" do
  19. 1 assert_equal %(<svg><path d="321"></path></svg>), Example.call
  20. end
  21. end

quickdraw/html/void_elements.test.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class VoidElementsTest < Quickdraw::Test
  3. 1 Phlex::HTML::VoidElements.__registered_elements__.each do |method_name, tag|
  4. 12 test "<#{tag}> with attributes" do
  5. 12 example = Class.new(Phlex::HTML) do
  6. 12 define_method :view_template do
  7. 12 __send__(method_name, class: "class", id: "id", disabled: true, selected: false)
  8. end
  9. end
  10. 12 assert_equal(example.call, %(<#{tag} class="class" id="id" disabled>))
  11. end
  12. 12 test "<#{tag}> with string attribute keys" do
  13. 12 example = Class.new(Phlex::HTML) do
  14. 12 define_method :view_template do
  15. 12 __send__(method_name, "attribute_with_underscore" => true)
  16. end
  17. end
  18. 12 assert_equal(example.call, %(<#{tag} attribute_with_underscore>))
  19. end
  20. 12 test "<#{tag}> with hash attribute values" do
  21. 12 example = Class.new(Phlex::HTML) do
  22. 12 define_method :view_template do
  23. 12 __send__(method_name, aria: { hidden: true }, data_turbo: { frame: "_top" })
  24. end
  25. end
  26. 12 assert_equal(example.call, %(<#{tag} aria-hidden data-turbo-frame="_top">))
  27. end
  28. end
  29. end

quickdraw/inline.test.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class InlineTest < Quickdraw::Test
  3. 1 test "inline html with no param" do
  4. 1 output = Phlex.html do
  5. 2 h1 { "Hi" }
  6. end
  7. 1 assert_equal output, <<~HTML.strip
  8. <h1>Hi</h1>
  9. HTML
  10. end
  11. 1 def title = "Hello"
  12. 1 test "inline html with a yield param" do
  13. 1 @ivar = "Hi"
  14. 1 h1 = "foo"
  15. 1 output = Phlex.html do |receiver|
  16. 2 h1 { h1 }
  17. 2 h1 { @ivar }
  18. 2 title { receiver.title }
  19. end
  20. 1 assert_equal output, <<~HTML.strip
  21. <h1>foo</h1><h1>Hi</h1><title>Hello</title>
  22. HTML
  23. end
  24. end

quickdraw/kit.test.rb

100.0% lines covered

100.0% branches covered

16 relevant lines. 16 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class KitTest < Quickdraw::Test
  3. 1 require "components"
  4. 1 require "sgml_helper"
  5. 1 include SGMLHelper
  6. 1 class Example < Phlex::HTML
  7. 1 include Components
  8. 1 def view_template
  9. 2 SayHi("Joel", times: 2) { "Inside" }
  10. 2 Components::SayHi("Will", times: 1) { "Inside" }
  11. end
  12. end
  13. 1 test "raises when you try to render a component outside of a rendering context" do
  14. 2 error = assert_raises(RuntimeError) { Components::SayHi("Joel") }
  15. 1 assert_equal error.message, "You can't call `SayHi' outside of a Phlex rendering context."
  16. end
  17. 1 test "defines methods for its components" do
  18. 1 assert_equal Example.new.call, %(<article><h1>Hi Joel</h1><h1>Hi Joel</h1>Inside</article><article><h1>Hi Will</h1>Inside</article>)
  19. end
  20. 1 test "nested kits" do
  21. 2 assert_equal phlex { Components::Foo::Bar() }, "<h1>Bar</h1>"
  22. end
  23. end

quickdraw/selective_rendering_from_cache.test.rb

100.0% lines covered

100.0% branches covered

58 relevant lines. 58 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class SelectiveRenderingFromCacheTest < Quickdraw::Test
  3. 1 class CacheTest < Phlex::HTML
  4. 1 attr_reader :cache_store
  5. 1 def initialize(page_id, cache_store:)
  6. 13 @page_id = page_id
  7. 13 @cache_store = cache_store
  8. end
  9. 1 def view_template
  10. 13 cache(@page_id) do
  11. 20 h1 { "Page #{@page_id}" }
  12. 10 fragment("outer") do
  13. 10 div(id: "page") do
  14. 10 cache do
  15. 8 section do
  16. 8 fragment("list") do
  17. 8 ul do
  18. 24 fragment("foo") { li { 1 } }
  19. 16 li { 2 }
  20. 16 li { 3 }
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end
  27. end
  28. end
  29. end
  30. 1 test "rendering a component with caches and fragments" do
  31. 1 cache_store = Phlex::FIFOCacheStore.new
  32. 1 output = CacheTest.new(1, cache_store:).call
  33. 1 assert_equal output, "<h1>Page 1</h1><div id=\"page\"><section><ul><li>1</li><li>2</li><li>3</li></ul></section></div>"
  34. 1 output = CacheTest.new(1, cache_store:).call
  35. 1 assert_equal output, "<h1>Page 1</h1><div id=\"page\"><section><ul><li>1</li><li>2</li><li>3</li></ul></section></div>"
  36. end
  37. 1 test "rendering a component with caches and fragments" do
  38. 1 cache_store = Phlex::FIFOCacheStore.new
  39. 1 output = CacheTest.new(1, cache_store:).call
  40. 1 assert_equal output, "<h1>Page 1</h1><div id=\"page\"><section><ul><li>1</li><li>2</li><li>3</li></ul></section></div>"
  41. 1 output = CacheTest.new(2, cache_store:).call
  42. 1 assert_equal output, "<h1>Page 2</h1><div id=\"page\"><section><ul><li>1</li><li>2</li><li>3</li></ul></section></div>"
  43. end
  44. 1 test "rendering a specific fragment from within a cache" do
  45. 1 cache_store = Phlex::FIFOCacheStore.new
  46. 1 2.times do
  47. 2 output = CacheTest.new(2, cache_store:).call(fragments: ["list"])
  48. 2 assert_equal output, "<ul><li>1</li><li>2</li><li>3</li></ul>"
  49. end
  50. end
  51. 1 test "rendering a nested fragment from within a cache" do
  52. 1 cache_store = Phlex::FIFOCacheStore.new
  53. 1 output = CacheTest.new(1, cache_store:).call(fragments: ["foo"])
  54. 1 assert_equal output, "<li>1</li>"
  55. end
  56. 1 test "rendering multiple fragments from within a cache" do
  57. 1 cache_store = Phlex::FIFOCacheStore.new
  58. 1 output = CacheTest.new(1, cache_store:).call(fragments: ["list", "foo"])
  59. 1 assert_equal output, "<ul><li>1</li><li>2</li><li>3</li></ul>"
  60. end
  61. 1 test "rendering multiple fragments out of order from within a cache" do
  62. 1 cache_store = Phlex::FIFOCacheStore.new
  63. 1 output = CacheTest.new(1, cache_store:).call(fragments: ["foo", "list"])
  64. 1 assert_equal output, "<ul><li>1</li><li>2</li><li>3</li></ul>"
  65. end
  66. 1 test "cache contains full value if initially rendered as a fragment" do
  67. 1 cache_store = Phlex::FIFOCacheStore.new
  68. 1 output = CacheTest.new(1, cache_store:).call(fragments: ["foo"])
  69. 1 assert_equal output, "<li>1</li>"
  70. 1 output = CacheTest.new(1, cache_store:).call
  71. 1 assert_equal output, "<h1>Page 1</h1><div id=\"page\"><section><ul><li>1</li><li>2</li><li>3</li></ul></section></div>"
  72. end
  73. 1 test "fetching a nested fragment from a cached value" do
  74. 1 cache_store = Phlex::FIFOCacheStore.new
  75. 1 CacheTest.new(1, cache_store:).call # Cache the outer cache for key = 1, and the inner cache for all other keys
  76. 1 output = CacheTest.new(2, cache_store:).call(fragments: ["foo"])
  77. 1 assert_equal output, "<li>1</li>"
  78. end
  79. end

quickdraw/sgml.test.rb

94.44% lines covered

100.0% branches covered

18 relevant lines. 17 lines covered and 1 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class SGMLTest < Quickdraw::Test
  3. 1 test "components render with a default blank view_template" do
  4. 1 component = Class.new(Phlex::HTML) do
  5. 1 def view_template
  6. end
  7. end
  8. 1 assert_equal component.new.call, ""
  9. end
  10. 1 test "components with old `template` method render warning" do
  11. 1 component = Class.new(Phlex::HTML) do
  12. 1 def template
  13. span "old template"
  14. end
  15. end
  16. 1 assert_equal component.new.call, Phlex::Escape.html_escape(
  17. %(Phlex Warning: Your `#{component.name}` class doesn't define a `view_template` method. If you are upgrading to Phlex 2.x make sure to rename your `template` method to `view_template`. See: https://beta.phlex.fun/guides/v2-upgrade.html)
  18. )
  19. end
  20. 1 test "components with no `view_template` method render warning" do
  21. 1 component = Class.new(Phlex::HTML)
  22. 1 assert_equal component.new.call, Phlex::Escape.html_escape(
  23. %(Phlex Warning: Your `#{component.name}` class doesn't define a `view_template` method. If you are upgrading to Phlex 2.x make sure to rename your `template` method to `view_template`. See: https://beta.phlex.fun/guides/v2-upgrade.html)
  24. )
  25. end
  26. 1 test "can't render a component more than once" do
  27. 1 component = Class.new(Phlex::HTML)
  28. 1 instance = component.new
  29. 1 instance.call
  30. 2 assert_raises(Phlex::DoubleRenderError) { instance.call }
  31. end
  32. end

quickdraw/sgml/attributes.test.rb

100.0% lines covered

100.0% branches covered

439 relevant lines. 439 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class AttributesTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 test "id attributes must be lower case symbols" do
  6. 3 assert_raises(Phlex::ArgumentError) { phlex { div("id" => "abc") } }
  7. 3 assert_raises(Phlex::ArgumentError) { phlex { div("ID" => "abc") } }
  8. 3 assert_raises(Phlex::ArgumentError) { phlex { div(:ID => "abc") } }
  9. 2 output = phlex { div(id: "abc") }
  10. 1 assert_equal output, %(<div id="abc"></div>)
  11. end
  12. 1 test "*invalid*, _" do
  13. 1 error = assert_raises(Phlex::ArgumentError) do
  14. 2 phlex { div(Object.new => "abc") }
  15. end
  16. 1 assert_equal error.message, "Attribute keys should be Strings or Symbols."
  17. end
  18. 1 test "unsafe event attribute" do
  19. 1 error = assert_raises(Phlex::ArgumentError) do
  20. 2 phlex { div("onclick" => true) }
  21. end
  22. 1 assert_equal error.message, "Unsafe attribute name detected: onclick."
  23. end
  24. 1 test "data with hash" do
  25. 2 data = phlex { div(data: { attr: "test" }) }
  26. 1 assert_equal data, %(<div data-attr="test"></div>)
  27. end
  28. 1 test "data with object which responds to to_h" do
  29. 2 data = phlex { div(data: Data.define(:attr).new(attr: "test")) }
  30. 1 assert_equal data, %(<div data-attr="test"></div>)
  31. end
  32. 1 test "href with hash" do
  33. 1 error = assert_raises(Phlex::ArgumentError) do
  34. 2 phlex { a(href: {}) }
  35. end
  36. 1 assert_equal error.message, "Invalid attribute value for href: #{{}.inspect}."
  37. end
  38. 1 test "unsafe href attribute" do
  39. [
  40. 2 phlex { a(href: "javascript:alert('hello')") },
  41. 1 phlex { a(href: "Javascript:alert('hello')") },
  42. 1 phlex { a("href" => "javascript:alert('hello')") },
  43. 1 phlex { a("Href" => "javascript:alert('hello')") },
  44. 1 phlex { a("Href" => "javascript:javascript:alert('hello')") },
  45. 1 phlex { a(href: " \t\njavascript:alert('hello')") },
  46. 1 phlex { a(href: "java&#x73;cript:alert(1)") },
  47. 1 phlex { a(href: "javascript&#58;alert(1)") },
  48. 1 phlex { a(href: "java&#115;cript:alert(1)") },
  49. 1 phlex { a(href: "&#106;avascript:alert(1)") },
  50. 1 phlex { a(href: "javascript&#58alert(1)") },
  51. 1 phlex { a(href: "javascript&colon;alert(1)") },
  52. ].each do |output|
  53. 12 assert_equal output, %(<a></a>)
  54. end
  55. end
  56. 1 test "unsafe xlink:href attribute" do
  57. [
  58. 3 phlex(Phlex::SVG) { a("xlink:href": "javascript:alert(1)") { "x" } },
  59. 2 phlex(Phlex::SVG) { a("xlink:href": "javascript&colon;alert(1)") { "x" } },
  60. 2 phlex(Phlex::SVG) { a("xlink:href": "javascript&#58alert(1)") { "x" } },
  61. ].each do |output|
  62. 3 assert_equal output, %(<a>x</a>)
  63. end
  64. end
  65. 1 test "unsafe attribute name <" do
  66. 1 error = assert_raises(Phlex::ArgumentError) do
  67. 2 phlex { div("<" => true) }
  68. end
  69. 1 assert_equal error.message, "Unsafe attribute name detected: <."
  70. end
  71. 1 test "unsafe attribute name >" do
  72. 1 error = assert_raises(Phlex::ArgumentError) do
  73. 2 phlex { div(">" => true) }
  74. end
  75. 1 assert_equal error.message, "Unsafe attribute name detected: >."
  76. end
  77. 1 test "unsafe attribute name &" do
  78. 1 error = assert_raises(Phlex::ArgumentError) do
  79. 2 phlex { div("&" => true) }
  80. end
  81. 1 assert_equal error.message, "Unsafe attribute name detected: &."
  82. end
  83. 1 test "unsafe attribute name '" do
  84. 1 error = assert_raises(Phlex::ArgumentError) do
  85. 2 phlex { div("'" => true) }
  86. end
  87. 1 assert_equal error.message, "Unsafe attribute name detected: '."
  88. end
  89. 1 test "unsafe attribute name \"" do
  90. 1 error = assert_raises(Phlex::ArgumentError) do
  91. 2 phlex { div('"' => true) }
  92. end
  93. 1 assert_equal error.message, "Unsafe attribute name detected: \"."
  94. end
  95. 1 test "unsafe attribute name with space (String)" do
  96. 1 error = assert_raises(Phlex::ArgumentError) do
  97. 2 phlex { div("foo bar" => true) }
  98. end
  99. 1 assert_equal error.message, "Unsafe attribute name detected: foo bar."
  100. end
  101. 1 test "unsafe attribute name with space (Symbol)" do
  102. 1 error = assert_raises(Phlex::ArgumentError) do
  103. 2 phlex { div("foo bar": true) }
  104. end
  105. 1 assert_equal error.message, "Unsafe attribute name detected: foo bar."
  106. end
  107. 1 test "unsafe attribute name with slash (String)" do
  108. 1 error = assert_raises(Phlex::ArgumentError) do
  109. 2 phlex { div("foo/bar" => true) }
  110. end
  111. 1 assert_equal error.message, "Unsafe attribute name detected: foo/bar."
  112. end
  113. 1 test "unsafe attribute name with slash (Symbol)" do
  114. 1 error = assert_raises(Phlex::ArgumentError) do
  115. 2 phlex { div("foo/bar": true) }
  116. end
  117. 1 assert_equal error.message, "Unsafe attribute name detected: foo/bar."
  118. end
  119. 1 test "_, nil" do
  120. 2 output = phlex { div(attribute: nil) }
  121. 1 assert_equal output, %(<div></div>)
  122. end
  123. 1 test "_, true" do
  124. 2 output = phlex { div(attribute: true) }
  125. 1 assert_equal output, %(<div attribute></div>)
  126. end
  127. 1 test "_, false" do
  128. 2 output = phlex { div(attribute: false) }
  129. 1 assert_equal output, %(<div></div>)
  130. end
  131. 1 test "_, String" do
  132. 2 with_empty_string = phlex { div(attribute: "") }
  133. 1 assert_equal with_empty_string, %(<div attribute=""></div>)
  134. 2 with_regular_string = phlex { div(attribute: "test") }
  135. 1 assert_equal with_regular_string, %(<div attribute="test"></div>)
  136. 2 with_underscores = phlex { div(attribute: "with_underscores") }
  137. 1 assert_equal with_underscores, %(<div attribute="with_underscores"></div>)
  138. 2 with_dashes = phlex { div(attribute: "with-dashes") }
  139. 1 assert_equal with_dashes, %(<div attribute="with-dashes"></div>)
  140. 2 with_spaces = phlex { div(attribute: "with spaces") }
  141. 1 assert_equal with_spaces, %(<div attribute="with spaces"></div>)
  142. 2 with_single_quotes = phlex { div(attribute: "with 'single quotes'") }
  143. 1 assert_equal with_single_quotes, %(<div attribute="with 'single quotes'"></div>)
  144. 2 with_html = phlex { div(attribute: "with <html>") }
  145. 1 assert_equal with_html, %(<div attribute="with <html>"></div>)
  146. 2 with_double_quotes = phlex { div(attribute: 'with "double quotes"') }
  147. 1 assert_equal with_double_quotes, %(<div attribute="with &quot;double quotes&quot;"></div>)
  148. end
  149. 1 test "_, Symbol" do
  150. 2 empty_symbol = phlex { div(attribute: :"") }
  151. 1 assert_equal empty_symbol, %(<div attribute=""></div>)
  152. 2 simple_symbol = phlex { div(attribute: :test) }
  153. 1 assert_equal simple_symbol, %(<div attribute="test"></div>)
  154. 2 symbol_with_underscores = phlex { div(attribute: :with_underscores) }
  155. 1 assert_equal symbol_with_underscores, %(<div attribute="with-underscores"></div>)
  156. 2 symbol_with_dashes = phlex { div(attribute: :"with-dashes") }
  157. 1 assert_equal symbol_with_dashes, %(<div attribute="with-dashes"></div>)
  158. 2 symbol_with_spaces = phlex { div(attribute: :"with spaces") }
  159. 1 assert_equal symbol_with_spaces, %(<div attribute="with spaces"></div>)
  160. 2 symbol_with_single_quotes = phlex { div(attribute: :"with 'single quotes'") }
  161. 1 assert_equal symbol_with_single_quotes, %(<div attribute="with 'single quotes'"></div>)
  162. 2 symbol_with_html = phlex { div(attribute: :"with <html>") }
  163. 1 assert_equal symbol_with_html, %(<div attribute="with <html>"></div>)
  164. 2 symbol_with_double_quotes = phlex { div(attribute: :'with "double quotes"') }
  165. 1 assert_equal symbol_with_double_quotes, %(<div attribute="with &quot;double quotes&quot;"></div>)
  166. end
  167. 1 test "_, Integer" do
  168. 2 output = phlex { div(attribute: 0) }
  169. 1 assert_equal output, %(<div attribute="0"></div>)
  170. 2 output = phlex { div(attribute: 42) }
  171. 1 assert_equal output, %(<div attribute="42"></div>)
  172. end
  173. 1 test "_, Float" do
  174. 2 output = phlex { div(attribute: 0.0) }
  175. 1 assert_equal output, %(<div attribute="0.0"></div>)
  176. 2 output = phlex { div(attribute: 42.0) }
  177. 1 assert_equal output, %(<div attribute="42.0"></div>)
  178. 2 output = phlex { div(attribute: 1.234) }
  179. 1 assert_equal output, %(<div attribute="1.234"></div>)
  180. end
  181. 1 test "_, Date" do
  182. 2 output = phlex { div(attribute: Date.new(2023, 1, 15)) }
  183. 1 assert_equal output, %(<div attribute="2023-01-15"></div>)
  184. end
  185. 1 test "_, Time" do
  186. 2 output = phlex { div(attribute: Time.new(2023, 1, 15, 12, 30, 45, "+00:00")) }
  187. 1 assert_equal output, %(<div attribute="2023-01-15T12:30:45+00:00"></div>)
  188. end
  189. 1 test "_, *invalid*" do
  190. 1 assert_raises(Phlex::ArgumentError) do
  191. 2 phlex { div(attribute: Object.new) }
  192. end
  193. end
  194. 1 test "_, Array" do
  195. 2 output = phlex { div(attribute: []) }
  196. 1 assert_equal output, %(<div></div>)
  197. end
  198. 1 test "_, Array(Array)" do
  199. 2 output = phlex { div(attribute: [[], [], []]) }
  200. 1 assert_equal output, %(<div></div>)
  201. end
  202. 1 test "_, Array(nil)" do
  203. 2 output = phlex { div(attribute: [nil, nil, nil]) }
  204. 1 assert_equal output, %(<div></div>)
  205. end
  206. 1 test "_, Array(Array(nil))" do
  207. 2 output = phlex { div(attribute: [[nil, nil], [nil, nil]]) }
  208. 1 assert_equal output, %(<div></div>)
  209. end
  210. 1 test "_, Array(Array(nil), nil)" do
  211. 2 output = phlex { div(attribute: [[nil, nil], nil]) }
  212. 1 assert_equal output, %(<div></div>)
  213. end
  214. 1 test "_, Array(String)" do
  215. 2 output = phlex { div(attribute: ["Hello", "World"]) }
  216. 1 assert_equal output, %(<div attribute="Hello World"></div>)
  217. 2 output = phlex { div(attribute: ["with_underscores", "with-dashes", "with spaces"]) }
  218. 1 assert_equal output, %(<div attribute="with_underscores with-dashes with spaces"></div>)
  219. 2 output = phlex { div(attribute: ["with 'single quotes'", 'with "double quotes"']) }
  220. 1 assert_equal output, %(<div attribute="with 'single quotes' with &quot;double quotes&quot;"></div>)
  221. end
  222. 1 test "_, Array(Symbol)" do
  223. 2 output = phlex { div(attribute: [:hello, :world]) }
  224. 1 assert_equal output, %(<div attribute="hello world"></div>)
  225. 2 output = phlex { div(attribute: [:with_underscores, :"with-dashes", :"with spaces"]) }
  226. 1 assert_equal output, %(<div attribute="with-underscores with-dashes with spaces"></div>)
  227. 2 output = phlex { div(attribute: [:with, :"'single quotes'", :'"double quotes"']) }
  228. 1 assert_equal output, %(<div attribute="with 'single quotes' &quot;double quotes&quot;"></div>)
  229. end
  230. 1 test "_, Array(Integer)" do
  231. 2 output = phlex { div(attribute: [0, 42]) }
  232. 1 assert_equal output, %(<div attribute="0 42"></div>)
  233. end
  234. 1 test "_, Array(Float)" do
  235. 2 output = phlex { div(attribute: [0.0, 42.0, 1.234]) }
  236. 1 assert_equal output, %(<div attribute="0.0 42.0 1.234"></div>)
  237. end
  238. 1 test "_, Array(Phlex::SGML::SafeObject)" do
  239. 2 output = phlex { div(attribute: [Phlex::SGML::SafeValue.new("Hello")]) }
  240. 1 assert_equal output, %(<div attribute="Hello"></div>)
  241. end
  242. 1 test "_, Array(String | Array)" do
  243. 2 output = phlex { div(attribute: ["hello", ["world"]]) }
  244. 1 assert_equal output, %(<div attribute="hello world"></div>)
  245. end
  246. 1 test "_, Array(Array | String)" do
  247. 2 output = phlex { div(attribute: [["hello"], "world"]) }
  248. 1 assert_equal output, %(<div attribute="hello world"></div>)
  249. end
  250. 1 test "_, Array(String | EmptyArray)" do
  251. 2 output = phlex { div(attribute: ["hello", []]) }
  252. 1 assert_equal output, %(<div attribute="hello"></div>)
  253. end
  254. 1 test "_, Array(*invalid*)" do
  255. 1 assert_raises(Phlex::ArgumentError) do
  256. 2 phlex { div(attribute: [Object.new]) }
  257. end
  258. end
  259. 1 test "_, Hash(Symbol, _)" do
  260. 2 output = phlex { div(attribute: { a_b_c: "world" }) }
  261. 1 assert_equal output, %(<div attribute-a-b-c="world"></div>)
  262. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { :'"' => "a" }) } }
  263. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { :"'" => "a" }) } }
  264. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { :"&" => "a" }) } }
  265. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { :"<" => "a" }) } }
  266. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { :">" => "a" }) } }
  267. end
  268. 1 test "_, Hash(String, _)" do
  269. 2 output = phlex { div(attribute: { "a_b_c" => "world" }) }
  270. 1 assert_equal output, %(<div attribute-a_b_c="world"></div>)
  271. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { '"' => "a" }) } }
  272. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { "'" => "a" }) } }
  273. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { "&" => "a" }) } }
  274. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { "<" => "a" }) } }
  275. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { ">" => "a" }) } }
  276. end
  277. 1 test "_, Hash(:_, _)" do
  278. 2 by_itself = phlex { div(attribute: { _: "world" }) }
  279. 1 assert_equal by_itself, %(<div attribute="world"></div>)
  280. 2 with_others = phlex { div(data: { _: "test", controller: "hello" }) }
  281. 1 assert_equal with_others, %(<div data="test" data-controller="hello"></div>)
  282. end
  283. 1 test "_, Hash(*invalid*, _)" do
  284. 3 assert_raises(Phlex::ArgumentError) { phlex { div(attribute: { Object.new => "a" }) } }
  285. end
  286. 1 test "_, Hash(_, String)" do
  287. 2 with_underscores = phlex { div(data: { controller: "with_underscores" }) }
  288. 1 assert_equal with_underscores, %(<div data-controller="with_underscores"></div>)
  289. 2 with_dashes = phlex { div(data: { controller: "with-dashes" }) }
  290. 1 assert_equal with_dashes, %(<div data-controller="with-dashes"></div>)
  291. 2 with_spaces = phlex { div(data: { controller: "with spaces" }) }
  292. 1 assert_equal with_spaces, %(<div data-controller="with spaces"></div>)
  293. 2 with_single_quotes = phlex { div(data: { controller: "with 'single quotes'" }) }
  294. 1 assert_equal with_single_quotes, %(<div data-controller="with 'single quotes'"></div>)
  295. 2 with_html = phlex { div(data: { controller: "with <html>" }) }
  296. 1 assert_equal with_html, %(<div data-controller="with <html>"></div>)
  297. 2 with_double_quotes = phlex { div(data: { controller: 'with "double quotes"' }) }
  298. 1 assert_equal with_double_quotes, %(<div data-controller="with &quot;double quotes&quot;"></div>)
  299. end
  300. 1 test "_, Hash(_, Symbol)" do
  301. 2 output = phlex { div(data: { controller: :with_underscores }) }
  302. 1 assert_equal output, %(<div data-controller="with-underscores"></div>)
  303. 2 output = phlex { div(data: { controller: :"with-dashes" }) }
  304. 1 assert_equal output, %(<div data-controller="with-dashes"></div>)
  305. 2 output = phlex { div(data: { controller: :"with spaces" }) }
  306. 1 assert_equal output, %(<div data-controller="with spaces"></div>)
  307. 2 output = phlex { div(data: { controller: :"with 'single quotes'" }) }
  308. 1 assert_equal output, %(<div data-controller="with 'single quotes'"></div>)
  309. 2 output = phlex { div(data: { controller: :"with <html>" }) }
  310. 1 assert_equal output, %(<div data-controller="with <html>"></div>)
  311. 2 output = phlex { div(data: { controller: :'with "double quotes"' }) }
  312. 1 assert_equal output, %(<div data-controller="with &quot;double quotes&quot;"></div>)
  313. end
  314. 1 test "_, Hash(_, Integer)" do
  315. 2 output = phlex { div(data: { controller: 42 }) }
  316. 1 assert_equal output, %(<div data-controller="42"></div>)
  317. 2 output = phlex { div(data: { controller: 1_234 }) }
  318. 1 assert_equal output, %(<div data-controller="1234"></div>)
  319. 2 output = phlex { div(data: { controller: 0 }) }
  320. 1 assert_equal output, %(<div data-controller="0"></div>)
  321. end
  322. 1 test "_, Hash(_, Float)" do
  323. 2 output = phlex { div(data: { controller: 42.0 }) }
  324. 1 assert_equal output, %(<div data-controller="42.0"></div>)
  325. 2 output = phlex { div(data: { controller: 1.234 }) }
  326. 1 assert_equal output, %(<div data-controller="1.234"></div>)
  327. 2 output = phlex { div(data: { controller: 0.0 }) }
  328. 1 assert_equal output, %(<div data-controller="0.0"></div>)
  329. end
  330. 1 test "_, Hash(_, Array)" do
  331. 2 output = phlex { div(data: { controller: [1, 2, 3] }) }
  332. 1 assert_equal output, %(<div data-controller="1 2 3"></div>)
  333. end
  334. 1 test "_, Hash(_, Set)" do
  335. 2 output = phlex { div(data: { controller: Set[1, 2, 3] }) }
  336. 1 assert_equal output, %(<div data-controller="1 2 3"></div>)
  337. end
  338. 1 test "_, Hash(_, Hash)" do
  339. 2 output = phlex { div(data: { controller: { hello: "world" } }) }
  340. 1 assert_equal output, %(<div data-controller-hello="world"></div>)
  341. end
  342. 1 test "_, Hash(_, Hash(_, nil)" do
  343. 2 output = phlex { div(data: { controller: { hello: nil } }) }
  344. 1 assert_equal output, %(<div></div>)
  345. end
  346. 1 test "_, Hash(_, Phlex::SGML::SafeObject)" do
  347. 2 output = phlex { div(data: { controller: Phlex::SGML::SafeValue.new("Hello") }) }
  348. 1 assert_equal output, %(<div data-controller="Hello"></div>)
  349. end
  350. 1 test "_, Hash(_, false)" do
  351. 2 output = phlex { div(data: { controller: false }) }
  352. 1 assert_equal output, %(<div></div>)
  353. end
  354. 1 test "_, Hash(_, nil)" do
  355. 2 output = phlex { div(data: { controller: nil }) }
  356. 1 assert_equal output, %(<div></div>)
  357. end
  358. 1 test "_, Hash(_, Array)" do
  359. 2 output = phlex { div(data: { action: [] }) }
  360. 1 assert_equal output, %(<div></div>)
  361. end
  362. 1 test "_, Hash(_, Array(nil))" do
  363. 2 output = phlex { div(data: { action: [nil] }) }
  364. 1 assert_equal output, %(<div></div>)
  365. end
  366. 1 test "_, Hash(_, Set)" do
  367. 2 output = phlex { div(data: { action: Set[] }) }
  368. 1 assert_equal output, %(<div></div>)
  369. end
  370. 1 test "_, Hash(_, Set(Set))" do
  371. 2 output = phlex { div(data: { action: Set[Set[]] }) }
  372. 1 assert_equal output, %(<div></div>)
  373. end
  374. 1 test "_, Hash(_, Set(nil))" do
  375. 2 output = phlex { div(data: { action: Set[nil] }) }
  376. 1 assert_equal output, %(<div></div>)
  377. end
  378. 1 test "_, Hash(_, Set(Set(nil)))" do
  379. 2 output = phlex { div(data: { action: Set[Set[nil]] }) }
  380. 1 assert_equal output, %(<div></div>)
  381. end
  382. 1 test "_, Hash(_, *invalid*)" do
  383. 1 assert_raises(Phlex::ArgumentError) do
  384. 2 phlex { div(data: { controller: Object.new }) }
  385. end
  386. end
  387. 1 test "_, Set" do
  388. 2 output = phlex { div(attribute: Set[]) }
  389. 1 assert_equal output, %(<div></div>)
  390. end
  391. 1 test "_, Set(nil)" do
  392. 2 output = phlex { div(attribute: Set[nil, nil, nil]) }
  393. 1 assert_equal output, %(<div></div>)
  394. end
  395. 1 test "_, Set(Set)" do
  396. 2 output = phlex { div(attribute: Set[Set[]]) }
  397. 1 assert_equal output, %(<div></div>)
  398. end
  399. 1 test "_, Set(Set(nil))" do
  400. 2 output = phlex { div(attribute: Set[Set[nil, nil, nil]]) }
  401. 1 assert_equal output, %(<div></div>)
  402. end
  403. 1 test "_, Set(Set, nil)" do
  404. 2 output = phlex { div(attribute: Set[Set[], nil]) }
  405. 1 assert_equal output, %(<div></div>)
  406. end
  407. 1 test "_, Set(Set(nil), nil)" do
  408. 2 output = phlex { div(attribute: Set[Set[nil], nil]) }
  409. 1 assert_equal output, %(<div></div>)
  410. end
  411. 1 test "_, Set(String)" do
  412. 2 output = phlex { div(attribute: Set["Hello", "World"]) }
  413. 1 assert_equal output, %(<div attribute="Hello World"></div>)
  414. 2 output = phlex { div(attribute: Set["with_underscores", "with-dashes", "with spaces"]) }
  415. 1 assert_equal output, %(<div attribute="with_underscores with-dashes with spaces"></div>)
  416. 2 output = phlex { div(attribute: Set["with 'single quotes'", 'with "double quotes"']) }
  417. 1 assert_equal output, %(<div attribute="with 'single quotes' with &quot;double quotes&quot;"></div>)
  418. end
  419. 1 test "_, Set(Symbol)" do
  420. 2 output = phlex { div(attribute: Set[:hello, :world]) }
  421. 1 assert_equal output, %(<div attribute="hello world"></div>)
  422. 2 output = phlex { div(attribute: Set[:with_underscores, :"with-dashes", :"with spaces"]) }
  423. 1 assert_equal output, %(<div attribute="with-underscores with-dashes with spaces"></div>)
  424. 2 output = phlex { div(attribute: Set[:with, :"single quotes", :'"double quotes"']) }
  425. 1 assert_equal output, %(<div attribute="with single quotes &quot;double quotes&quot;"></div>)
  426. end
  427. 1 test "_, Set(Integer)" do
  428. 2 output = phlex { div(attribute: Set[0, 42]) }
  429. 1 assert_equal output, %(<div attribute="0 42"></div>)
  430. end
  431. 1 test "_, Set(Float)" do
  432. 2 output = phlex { div(attribute: Set[0.0, 42.0, 1.234]) }
  433. 1 assert_equal output, %(<div attribute="0.0 42.0 1.234"></div>)
  434. end
  435. 1 test "_, Set(Phlex::SGML::SafeObject)" do
  436. 1 output = phlex do
  437. 1 div(attribute: Set[Phlex::SGML::SafeValue.new("Hello")])
  438. end
  439. 1 assert_equal output, %(<div attribute="Hello"></div>)
  440. end
  441. 1 test "_, Set(*invalid*)" do
  442. 1 assert_raises(Phlex::ArgumentError) do
  443. 2 phlex { div(attribute: Set[Object.new]) }
  444. end
  445. end
  446. 1 test ":style, Array(nil)" do
  447. 2 output = phlex { div(style: [nil, nil, nil]) }
  448. 1 assert_equal output, %(<div style=""></div>)
  449. end
  450. 1 test ":style, Array(Symbol)" do
  451. 1 assert_raises(Phlex::ArgumentError) do
  452. 2 phlex { div(style: [:color_blue]) }
  453. end
  454. end
  455. 1 test ":style, Array(String)" do
  456. 2 output = phlex { div(style: ["color: blue;", "font-weight: bold"]) }
  457. 1 assert_equal output, %(<div style="color: blue; font-weight: bold;"></div>)
  458. 2 output = phlex { div(style: ["font-family: 'MonoLisa'"]) }
  459. 1 assert_equal output, %(<div style="font-family: 'MonoLisa';"></div>)
  460. 2 output = phlex { div(style: ['font-family: "MonoLisa"']) }
  461. 1 assert_equal output, %(<div style="font-family: &quot;MonoLisa&quot;;"></div>)
  462. end
  463. 1 test ":style, Array(Phlex::SGML::SafeObject)" do
  464. 2 output = phlex { div(style: [Phlex::SGML::SafeValue.new("color: blue")]) }
  465. 1 assert_equal output, %(<div style="color: blue;"></div>)
  466. 2 output = phlex { div(style: [Phlex::SGML::SafeValue.new("font-weight: bold;")]) }
  467. 1 assert_equal output, %(<div style="font-weight: bold;"></div>)
  468. end
  469. 1 test ":style, Array(Hash)" do
  470. 2 output = phlex { div(style: [{ color: "blue" }, { font_weight: "bold", line_height: 1.5 }]) }
  471. 1 assert_equal output, %(<div style="color: blue; font-weight: bold; line-height: 1.5;"></div>)
  472. end
  473. 1 test ":style, Set(nil)" do
  474. 2 output = phlex { div(style: Set[nil]) }
  475. 1 assert_equal output, %(<div style=""></div>)
  476. end
  477. 1 test ":style, Set(String)" do
  478. 2 output = phlex { div(style: Set["color: blue"]) }
  479. 1 assert_equal output, %(<div style="color: blue;"></div>)
  480. end
  481. 1 test ":style, Hash(Symbol, String)" do
  482. 2 output = phlex { div(style: { color: "blue", font_weight: "bold" }) }
  483. 1 assert_equal output, %(<div style="color: blue; font-weight: bold;"></div>)
  484. end
  485. 1 test ":style, Hash(Symbol, Integer)" do
  486. 2 output = phlex { div(style: { line_height: 2 }) }
  487. 1 assert_equal output, %(<div style="line-height: 2;"></div>)
  488. end
  489. 1 test ":style, Hash(Symbol, Float)" do
  490. 2 output = phlex { div(style: { line_height: 1.5 }) }
  491. 1 assert_equal output, %(<div style="line-height: 1.5;"></div>)
  492. end
  493. 1 test ":style, Hash(Symbol, Symbol)" do
  494. 2 output = phlex { div(style: { flex_direction: :column_reverse }) }
  495. 1 assert_equal output, %(<div style="flex-direction: column-reverse;"></div>)
  496. 2 output = phlex { div(style: { flex_direction: :'"double quotes"' }) }
  497. 1 assert_equal output, %(<div style="flex-direction: &quot;double quotes&quot;;"></div>)
  498. end
  499. 1 test ":style, Hash(Symbol, Phlex::SGML::SafeObject)" do
  500. 2 output = phlex { div(style: { color: Phlex::SGML::SafeValue.new("blue") }) }
  501. 1 assert_equal output, %(<div style="color: blue;"></div>)
  502. end
  503. 1 test ":style, Hash(Symbol, nil)" do
  504. 2 output = phlex { div(style: { color: nil }) }
  505. 1 assert_equal output, %(<div style=""></div>)
  506. end
  507. 1 test ":style, Hash(Symbol, *invalid*)" do
  508. 1 assert_raises(Phlex::ArgumentError) do
  509. 2 phlex { div(style: { color: Object.new }) }
  510. end
  511. end
  512. 1 test ":style, Hash(String, String)" do
  513. 2 output = phlex { div(style: { "color" => "blue" }) }
  514. 1 assert_equal output, %(<div style="color: blue;"></div>)
  515. end
  516. 1 test ":style, Hash(*invalid*, String)" do
  517. 1 assert_raises(Phlex::ArgumentError) do
  518. 2 phlex { div(style: { Object.new => "blue" }) }
  519. end
  520. end
  521. 1 test ":srcset on img with an Array" do
  522. 2 output = phlex { img(srcset: []) }
  523. 1 assert_equal output, %(<img>)
  524. 2 output = phlex { img(srcset: ["/width=400/image.jpg 1x", "/width=400,dpr=2/image.jpg 2x"]) }
  525. 1 assert_equal output, %(<img srcset="/width=400/image.jpg 1x, /width=400%2Cdpr=2/image.jpg 2x">)
  526. 2 output = phlex { img(srcset: ["/width=400/image.jpg 1x", ["/width=400,dpr=2/image.jpg 2x"]]) }
  527. 1 assert_equal output, %(<img srcset="/width=400/image.jpg 1x, /width=400%2Cdpr=2/image.jpg 2x">)
  528. end
  529. 1 test ":media on link with an Array" do
  530. 2 output = phlex { link(media: []) }
  531. 1 assert_equal output, %(<link>)
  532. 2 output = phlex { link(media: ["screen", "print"]) }
  533. 1 assert_equal output, %(<link media="screen, print">)
  534. 2 output = phlex { link(media: ["(max-width: 600px)", "(min-width: 1200px)"]) }
  535. 1 assert_equal output, %(<link media="(max-width: 600px), (min-width: 1200px)">)
  536. end
  537. 1 test ":sizes on link with an Array" do
  538. 2 output = phlex { link(sizes: []) }
  539. 1 assert_equal output, %(<link>)
  540. 2 output = phlex { link(sizes: ["16x16", "32x32", "64x64"]) }
  541. 1 assert_equal output, %(<link sizes="16x16, 32x32, 64x64">)
  542. end
  543. 1 test ":imagesrcset on link with an Array when rel is preload and as is image" do
  544. 2 output = phlex { link(imagesrcset: [], rel: "preload", as: "image") }
  545. 1 assert_equal output, %(<link rel="preload" as="image">)
  546. 2 output = phlex { link(imagesrcset: ["image.jpg 1x", "image@2x.jpg 2x"], rel: "preload", as: "image") }
  547. 1 assert_equal output, %(<link imagesrcset="image.jpg 1x, image@2x.jpg 2x" rel="preload" as="image">)
  548. 2 output = phlex { link(imagesrcset: ["image.jpg 1x", "image@2x.jpg 2x"], rel: :preload, as: :image) }
  549. 1 assert_equal output, %(<link imagesrcset="image.jpg 1x, image@2x.jpg 2x" rel="preload" as="image">)
  550. 2 output = phlex { link(:imagesrcset => ["/width=400/image.jpg 1x", "/width=400,dpr=2/image@2x.jpg 2x"], "rel" => "preload", "as" => "image") }
  551. 1 assert_equal output, %(<link imagesrcset="/width=400/image.jpg 1x, /width=400%2Cdpr=2/image@2x.jpg 2x" rel="preload" as="image">)
  552. end
  553. 1 test ":accept on input with array when type is file" do
  554. 2 output = phlex { input(accept: [], type: "file") }
  555. 1 assert_equal output, %(<input type="file">)
  556. 2 output = phlex { input(accept: ["image/jpeg", "image/png"], type: "file") }
  557. 1 assert_equal output, %(<input accept="image/jpeg, image/png" type="file">)
  558. 2 output = phlex { input(accept: ["image/jpeg", "image/png"], type: :file) }
  559. 1 assert_equal output, %(<input accept="image/jpeg, image/png" type="file">)
  560. 2 output = phlex { input(:accept => ["image/jpeg", "image/png"], "type" => "file") }
  561. 1 assert_equal output, %(<input accept="image/jpeg, image/png" type="file">)
  562. end
  563. end

quickdraw/sgml/callbacks.test.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class CallbacksTest < Quickdraw::Test
  3. 1 class Example < Phlex::HTML
  4. 1 def before_template
  5. 2 i { "1" }
  6. end
  7. 1 def around_template
  8. 2 i { "2" }
  9. 1 super do
  10. 2 i { "3" }
  11. 1 yield
  12. 2 i { "5" }
  13. end
  14. 2 i { "6" }
  15. end
  16. 1 def after_template
  17. 2 i { "7" }
  18. end
  19. 1 def view_template
  20. 2 i { "4" }
  21. end
  22. end
  23. 1 test "callbacks are called in the correct order" do
  24. 1 assert_equal Example.call, "<i>1</i><i>2</i><i>3</i><i>4</i><i>5</i><i>6</i><i>7</i>"
  25. end
  26. end

quickdraw/sgml/capture.test.rb

100.0% lines covered

100.0% branches covered

8 relevant lines. 8 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class CaptureTest < Quickdraw::Test
  3. 1 class ExampleCaptureWithArguments < Phlex::HTML
  4. 1 def view_template(&block)
  5. 2 h1 { capture("a", "b", "c", &block) }
  6. end
  7. end
  8. 1 test "arguments are passed to the capture block" do
  9. 1 output = ExampleCaptureWithArguments.new.call do |a, b, c|
  10. 1 "#{a} #{b} #{c}"
  11. end
  12. 1 assert_equal output, "<h1>a b c</h1>"
  13. end
  14. end

quickdraw/sgml/comment.test.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class CommentTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 test "text comment" do
  6. 1 output = phlex do
  7. 2 comment { "Hello World" }
  8. end
  9. 1 assert_equal output, "<!-- Hello World -->"
  10. end
  11. 1 test "block comment with markup" do
  12. 1 output = phlex do
  13. 1 comment do
  14. 2 h1 { "Hello World" }
  15. end
  16. end
  17. 1 assert_equal output, "<!-- <h1>Hello World</h1> -->"
  18. end
  19. end

quickdraw/sgml/conditional_rendering.test.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class ConditionalRenderingTest < Quickdraw::Test
  3. 1 class Example < Phlex::HTML
  4. 1 def initialize(render:)
  5. 2 @render = render
  6. end
  7. 1 def render? = @render
  8. 1 def view_template
  9. 2 h1 { "Hello" }
  10. end
  11. end
  12. 1 test do
  13. 1 assert_equal Example.new(render: true).call, "<h1>Hello</h1>"
  14. 1 assert_equal Example.new(render: false).call, ""
  15. end
  16. 1 class ExampleWithContext < Phlex::HTML
  17. 1 def render? = context[:render]
  18. 1 def view_template
  19. 2 h1 { "Hello" }
  20. end
  21. end
  22. 1 test do
  23. 1 assert_equal ExampleWithContext.new.call(context: { render: true }), "<h1>Hello</h1>"
  24. 1 assert_equal ExampleWithContext.new.call(context: { render: false }), ""
  25. end
  26. end

quickdraw/sgml/content_yielding.test.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class ContentYieldingTest < Quickdraw::Test
  3. 1 class TestClass < Phlex::HTML
  4. 1 def view_template
  5. 1 select(name: "test") do
  6. 1 [].each { |i| option_tag(i, i) }
  7. end
  8. 1 input(type: "text", name: "other")
  9. end
  10. end
  11. 1 test "should render the test class" do
  12. 1 assert_equal TestClass.call, %q(<select name="test"></select><input type="text" name="other">)
  13. end
  14. 1 class OtherTestClass < Phlex::HTML
  15. 1 def view_template
  16. 1 ul do
  17. 1 [].each { |i| li { i } }
  18. end
  19. 2 p { "hi there" }
  20. end
  21. end
  22. 1 test "should render the test class" do
  23. 1 assert_equal OtherTestClass.call, %q(<ul></ul><p>hi there</p>)
  24. end
  25. end

quickdraw/sgml/plain.test.rb

100.0% lines covered

100.0% branches covered

21 relevant lines. 21 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class PlainTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 test "with string" do
  6. 2 output = phlex { plain "Hello, World!" }
  7. 1 assert_equal output, "Hello, World!"
  8. end
  9. 1 test "with symbol" do
  10. 2 output = phlex { plain :hello_world }
  11. 1 assert_equal output, "hello_world"
  12. end
  13. 1 test "with integer" do
  14. 2 output = phlex { plain 42 }
  15. 1 assert_equal output, "42"
  16. end
  17. 1 test "with float" do
  18. 2 output = phlex { plain 3.14 }
  19. 1 assert_equal output, "3.14"
  20. end
  21. 1 test "with nil" do
  22. 2 output = phlex { plain nil }
  23. 1 assert_equal output, ""
  24. end
  25. 1 test "with invalid arguments" do
  26. 1 assert_raises(Phlex::ArgumentError) do
  27. 2 phlex { plain [] }
  28. end
  29. end
  30. end

quickdraw/sgml/raw.test.rb

100.0% lines covered

100.0% branches covered

16 relevant lines. 16 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class RawTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 test "with an unsafe object" do
  6. 1 error = assert_raises(Phlex::ArgumentError) do
  7. 2 phlex { raw "<div></div>" }
  8. end
  9. 1 assert_equal error.message, "You passed an unsafe object to `raw`."
  10. end
  11. 1 test "with a safe object" do
  12. 2 output = phlex { raw safe %(<div class="hello">&</div>) }
  13. 1 assert_equal output, %(<div class="hello">&</div>)
  14. end
  15. 1 test "with nil" do
  16. 3 output = phlex { div { raw nil } }
  17. 1 assert_equal output, "<div></div>"
  18. end
  19. 1 test "with empty string" do
  20. 3 output = phlex { div { raw "" } }
  21. 1 assert_equal output, "<div></div>"
  22. end
  23. end

quickdraw/sgml/safe.test.rb

100.0% lines covered

100.0% branches covered

16 relevant lines. 16 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class SafeTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 test "safe attribute values" do
  6. 1 output = phlex do
  7. 1 a(
  8. onclick: safe("window.history.back()"),
  9. href: safe("javascript:window.history.back()"),
  10. )
  11. end
  12. 1 assert_equal output, %(<a onclick="window.history.back()" href="javascript:window.history.back()"></a>)
  13. end
  14. 1 test "element content blocks that return safe values" do
  15. 1 output = phlex do
  16. 1 script {
  17. 1 safe(%(console.log("Hello World");))
  18. }
  19. end
  20. 1 assert_equal output, %(<script>console.log("Hello World");</script>)
  21. end
  22. 1 test "with invalid input" do
  23. 1 error = assert_raises(Phlex::ArgumentError) do
  24. 3 phlex { script { safe(123) } }
  25. end
  26. 1 assert_equal error.message, "Expected a String."
  27. end
  28. end

quickdraw/sgml/selective_rendering.test.rb

96.0% lines covered

100.0% branches covered

75 relevant lines. 72 lines covered and 3 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class SelectiveRenderingTest < Quickdraw::Test
  3. 1 class ExampleComponent < Phlex::HTML
  4. 1 def view_template(&)
  5. 3 div(&)
  6. end
  7. end
  8. 1 class StandardElementExample < Phlex::HTML
  9. 1 def initialize(execution_checker = -> {})
  10. 3 @execution_checker = execution_checker
  11. end
  12. 1 def view_template
  13. 3 doctype
  14. 3 div {
  15. 3 comment { h1(id: "target") }
  16. 6 h1 { "Before" }
  17. 3 img(src: "before.jpg")
  18. 6 render ExampleComponent.new { "Should not render" }
  19. 3 whitespace
  20. 3 comment { "This is a comment" }
  21. 3 fragment("target") do
  22. 3 h1(id: "target") {
  23. 3 plain "Hello"
  24. 6 strong { "World" }
  25. 3 img(src: "image.jpg")
  26. }
  27. end
  28. 1 @execution_checker.call
  29. 2 strong { "Here" }
  30. 1 fragment("image") do
  31. 1 img(id: "image", src: "after.jpg")
  32. end
  33. h1(id: "target") { "After" }
  34. }
  35. end
  36. end
  37. 1 class VoidElementExample < Phlex::HTML
  38. 1 def view_template
  39. 1 doctype
  40. 1 div {
  41. 1 comment { h1(id: "target") }
  42. 2 h1 { "Before" }
  43. 1 img(src: "before.jpg")
  44. 1 whitespace
  45. 1 comment { "This is a comment" }
  46. 1 h1 {
  47. 1 plain "Hello"
  48. 2 strong { "World" }
  49. 1 fragment("target") do
  50. 1 img(id: "target", src: "image.jpg")
  51. end
  52. }
  53. img(src: "after.jpg")
  54. h1(id: "target") { "After" }
  55. }
  56. end
  57. end
  58. 1 class WithCaptureBlock < Phlex::HTML
  59. 1 def view_template
  60. 6 h1(id: "before") { "Before" }
  61. 3 fragment("around") do
  62. 3 div(id: "around") do
  63. 3 capture do
  64. 3 fragment("inside") do
  65. 6 h1(id: "inside") { "Inside" }
  66. end
  67. end
  68. end
  69. end
  70. 2 fragment("after") do
  71. 4 h1(id: "after") { "After" }
  72. end
  73. end
  74. end
  75. 1 test "renders the just the target fragment" do
  76. 1 output = StandardElementExample.new.call(fragments: ["target"])
  77. 1 assert_equal output, %(<h1 id="target">Hello<strong>World</strong><img src="image.jpg"></h1>)
  78. end
  79. 1 test "works with void elements" do
  80. 1 output = VoidElementExample.new.call(fragments: ["target"])
  81. 1 assert_equal output, %(<img id="target" src="image.jpg">)
  82. end
  83. 1 test "supports multiple fragments" do
  84. 1 output = StandardElementExample.new.call(fragments: ["target", "image"])
  85. 1 assert_equal output, %(<h1 id="target">Hello<strong>World</strong><img src="image.jpg"></h1><img id="image" src="after.jpg">)
  86. end
  87. 1 test "halts early after all fragments are found" do
  88. 1 called = false
  89. 1 checker = -> { called = true }
  90. 1 StandardElementExample.new(checker).call(fragments: ["target"])
  91. 1 refute called
  92. end
  93. 1 test "with a capture block doesn't render the capture block" do
  94. 1 output = WithCaptureBlock.new.call(fragments: ["after"])
  95. 1 assert_equal output, %(<h1 id="after">After</h1>)
  96. end
  97. 1 test "with a capture block renders the capture block when selected" do
  98. 1 output = WithCaptureBlock.new.call(fragments: ["around"])
  99. 1 assert_equal output, %(<div id="around">&lt;h1 id=&quot;inside&quot;&gt;Inside&lt;/h1&gt;</div>)
  100. end
  101. 1 test "with a capture block doesn't select from the capture block" do
  102. 1 output = WithCaptureBlock.new.call(fragments: ["inside"])
  103. 1 assert_equal output, ""
  104. end
  105. end

quickdraw/sgml/to_proc.test.rb

100.0% lines covered

100.0% branches covered

16 relevant lines. 16 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class ToProcTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 class Example < Phlex::HTML
  6. 1 def view_template(&)
  7. 1 article(&)
  8. end
  9. 1 def slot(&)
  10. 1 render(&)
  11. end
  12. end
  13. 1 class Sub < Phlex::HTML
  14. 1 def view_template
  15. 2 h1 { "Sub" }
  16. end
  17. end
  18. 1 test "rendering components via #to_proc" do
  19. 1 output = phlex do
  20. 1 render Example do |e|
  21. 1 e.slot(&Sub.new)
  22. end
  23. end
  24. 1 assert_equal output, %(<article><h1>Sub</h1></article>)
  25. end
  26. end

quickdraw/sgml/whitespace.test.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sgml_helper"
  3. 1 class WhitespaceTest < Quickdraw::Test
  4. 1 include SGMLHelper
  5. 1 test "whitespae between" do
  6. 1 output = phlex do
  7. 1 div
  8. 1 whitespace
  9. 1 div
  10. end
  11. 1 assert_equal output, %(<div></div> <div></div>)
  12. end
  13. 1 test "whitespae around" do
  14. 1 output = phlex do
  15. 1 div
  16. 2 whitespace { div }
  17. 1 div
  18. end
  19. 1 assert_equal output, %(<div></div> <div></div> <div></div>)
  20. end
  21. end

quickdraw/svg.test.rb

100.0% lines covered

100.0% branches covered

21 relevant lines. 21 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class SVGTest < Quickdraw::Test
  3. 1 class Example < Phlex::SVG
  4. 1 def view_template
  5. 1 svg do
  6. 1 path(d: "123")
  7. end
  8. end
  9. end
  10. 1 test do
  11. 1 assert_equal Example.call, %(<svg><path d="123"></path></svg>)
  12. end
  13. 1 test "content_type" do
  14. 1 component = Class.new(Phlex::SVG)
  15. 1 assert_equal component.new.content_type, "image/svg+xml"
  16. end
  17. 1 test "cdata with string" do
  18. 1 component = Class.new(Phlex::SVG) do
  19. 1 def view_template
  20. 1 cdata("Hello, <[[test]]> World!")
  21. end
  22. end
  23. 1 assert_equal component.call, %(<![CDATA[Hello, <[[test]]>]]<![CDATA[ World!]]>)
  24. end
  25. 1 test "cdata with block" do
  26. 1 component = Class.new(Phlex::SVG) do
  27. 1 def view_template
  28. 1 cdata do
  29. 1 path(d: "123")
  30. end
  31. end
  32. end
  33. 1 assert_equal component.call, %(<![CDATA[<path d="123"></path>]]>)
  34. end
  35. end

quickdraw/svg/standard_elements.test.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class SVGStandardElementsTest < Quickdraw::Test
  3. 1 Phlex::SVG::StandardElements.__registered_elements__.each do |method_name, tag|
  4. 64 test "<#{tag}> with block text content and attributes" do
  5. 64 example = Class.new(Phlex::SVG) do
  6. 64 define_method :view_template do
  7. 128 __send__(method_name, class: "class", id: "id", disabled: true, selected: false) { "content" }
  8. end
  9. end
  10. 64 assert_equal example.call, %(<#{tag} class="class" id="id" disabled>content</#{tag}>)
  11. end
  12. 64 test "<#{tag}> with string attribute keys" do
  13. 64 example = Class.new(Phlex::SVG) do
  14. 64 define_method :view_template do
  15. 128 __send__(method_name, "attribute_with_underscore" => true) { "content" }
  16. end
  17. end
  18. 64 assert_equal example.call, %(<#{tag} attribute_with_underscore>content</#{tag}>)
  19. end
  20. 64 test "<#{tag}> with hash attribute values" do
  21. 64 example = Class.new(Phlex::SVG) do
  22. 64 define_method :view_template do
  23. 128 __send__(method_name, aria: { hidden: true }, data_turbo: { frame: "_top" }) { "content" }
  24. end
  25. end
  26. 64 assert_equal example.call, %(<#{tag} aria-hidden data-turbo-frame="_top">content</#{tag}>)
  27. end
  28. end
  29. end

quickdraw/tag.test.rb

98.78% lines covered

100.0% branches covered

82 relevant lines. 81 lines covered and 1 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class TagTest < Quickdraw::Test
  3. 1 class HTMLComponent < Phlex::HTML
  4. 1 def initialize(tag, **attributes)
  5. 454 @tag = tag
  6. 454 @attributes = attributes
  7. end
  8. 1 def view_template(&)
  9. 454 tag(@tag, **@attributes, &)
  10. end
  11. end
  12. 1 class SVGComponent < Phlex::SVG
  13. 1 def initialize(tag, **attributes)
  14. 257 @tag = tag
  15. 257 @attributes = attributes
  16. end
  17. 1 def view_template(&)
  18. 257 tag(@tag, **@attributes, &)
  19. end
  20. end
  21. 1 Phlex::HTML::VoidElements.__registered_elements__.each do |method_name, tag|
  22. 12 test "<#{tag}> HTML tag without attributes" do
  23. 12 output = HTMLComponent.call(tag.to_sym)
  24. 12 assert_equal output, <<~HTML.strip
  25. <#{tag}>
  26. HTML
  27. end
  28. 12 test "<#{tag}> HTML tag with attributes" do
  29. 12 output = HTMLComponent.call(tag.to_sym, class: "class", id: "id", disabled: true)
  30. 12 assert_equal output, <<~HTML.strip
  31. <#{tag} class="class" id="id" disabled>
  32. HTML
  33. end
  34. 12 test "<#{tag}> HTML tag with content" do
  35. 12 error = assert_raises Phlex::ArgumentError do
  36. 12 HTMLComponent.call(tag.to_sym) do
  37. "Hello, world!"
  38. end
  39. end
  40. 12 assert_equal error.message, "Void elements cannot have content blocks."
  41. end
  42. end
  43. 1 Phlex::HTML::StandardElements.__registered_elements__.each do |method_name, tag|
  44. 103 test "<#{tag}> HTML tag without attributes" do
  45. 103 output = HTMLComponent.call(tag.to_sym)
  46. 103 assert_equal output, <<~HTML.strip
  47. <#{tag}></#{tag}>
  48. HTML
  49. end
  50. 103 test "<#{tag}> HTML tag with attributes" do
  51. 103 output = HTMLComponent.call(tag.to_sym, class: "class", id: "id", disabled: true)
  52. 103 assert_equal output, <<~HTML.strip
  53. <#{tag} class="class" id="id" disabled></#{tag}>
  54. HTML
  55. end
  56. 103 test "<#{tag}> HTML tag with content" do
  57. 103 output = HTMLComponent.call(tag.to_sym) do
  58. 103 "Hello, world!"
  59. end
  60. 103 assert_equal output, <<~HTML.strip
  61. <#{tag}>Hello, world!</#{tag}>
  62. HTML
  63. end
  64. 103 test "<#{tag}> HTML tag with content and attributes" do
  65. 103 output = HTMLComponent.call(tag.to_sym, class: "class", id: "id", disabled: true) do
  66. 103 "Hello, world!"
  67. end
  68. 103 assert_equal output, <<~HTML.strip
  69. <#{tag} class="class" id="id" disabled>Hello, world!</#{tag}>
  70. HTML
  71. end
  72. end
  73. 1 Phlex::SVG::StandardElements.__registered_elements__.each do |method_name, tag|
  74. 64 test "<#{tag}> SVG tag without attributes" do
  75. 64 output = SVGComponent.call(tag.to_sym)
  76. 64 assert_equal output, <<~HTML.strip
  77. <#{tag}></#{tag}>
  78. HTML
  79. end
  80. 64 test "<#{tag}> SVG tag with attributes" do
  81. 64 output = SVGComponent.call(tag.to_sym, class: "class", id: "id", disabled: true)
  82. 64 assert_equal output, <<~HTML.strip
  83. <#{tag} class="class" id="id" disabled></#{tag}>
  84. HTML
  85. end
  86. 64 test "<#{tag}> SVG tag with content" do
  87. 64 output = SVGComponent.call(tag.to_sym) do
  88. 64 "Hello, world!"
  89. end
  90. 64 assert_equal output, <<~HTML.strip
  91. <#{tag}>Hello, world!</#{tag}>
  92. HTML
  93. end
  94. 64 test "<#{tag}> SVG tag with content and attributes" do
  95. 64 output = SVGComponent.call(tag.to_sym, class: "class", id: "id", disabled: true) do
  96. 64 "Hello, world!"
  97. end
  98. 64 assert_equal output, <<~HTML.strip
  99. <#{tag} class="class" id="id" disabled>Hello, world!</#{tag}>
  100. HTML
  101. end
  102. end
  103. 1 test "svg tag in HTML" do
  104. 1 output = HTMLComponent.call(:svg) do |svg|
  105. 1 svg.circle(cx: 50, cy: 50, r: 40, fill: "red")
  106. end
  107. 1 assert_equal output, <<~HTML.strip
  108. <svg><circle cx="50" cy="50" r="40" fill="red"></circle></svg>
  109. HTML
  110. end
  111. 1 test "with invalid tag name" do
  112. 1 error = assert_raises Phlex::ArgumentError do
  113. 1 HTMLComponent.call(:invalidtag)
  114. end
  115. 1 assert_equal error.message, "Invalid HTML tag: invalidtag"
  116. end
  117. 1 test "with invalid tag name input type" do
  118. 1 error = assert_raises Phlex::ArgumentError do
  119. 1 HTMLComponent.call("div")
  120. end
  121. 1 assert_equal error.message, "Expected the tag name to be a Symbol."
  122. end
  123. 1 test "with custom tag name" do
  124. 1 output = HTMLComponent.call(:custom_tag)
  125. 1 assert_equal output, <<~HTML.strip
  126. <custom-tag></custom-tag>
  127. HTML
  128. end
  129. 1 test "with unsafe custom tag name containing a space" do
  130. 1 error = assert_raises Phlex::ArgumentError do
  131. 1 HTMLComponent.call(:"x-widget onclick=alert(1)")
  132. end
  133. 1 assert_equal error.message, "Invalid HTML tag: x-widget onclick=alert(1)"
  134. end
  135. 1 test "with unsafe custom tag name containing special characters" do
  136. 1 error = assert_raises Phlex::ArgumentError do
  137. 1 HTMLComponent.call(:"x-widget>")
  138. end
  139. 1 assert_equal error.message, "Invalid HTML tag: x-widget>"
  140. end
  141. 1 test "with unsafe SVG custom tag name containing a space" do
  142. 1 error = assert_raises Phlex::ArgumentError do
  143. 1 SVGComponent.call(:"x-widget onclick=alert(1)")
  144. end
  145. 1 assert_equal error.message, "Invalid SVG tag: x-widget onclick=alert(1)"
  146. end
  147. end

quickdraw/💪.test.rb

100.0% lines covered

100.0% branches covered

6 relevant lines. 6 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 class EmojiTest < Quickdraw::Test
  3. 1 class Example < 💪::HTML
  4. 1 def view_template
  5. 2 h1 { "💪" }
  6. end
  7. end
  8. 1 test "💪" do
  9. 1 assert_equal Example.new.call, %(<h1>💪</h1>)
  10. end
  11. end