diff --git a/lib/phlex/helpers.rb b/lib/phlex/helpers.rb index b5a7cd39..36b28169 100644 --- a/lib/phlex/helpers.rb +++ b/lib/phlex/helpers.rb @@ -5,37 +5,12 @@ end module Phlex::Helpers - private - - def tokens(*tokens, **conditional_tokens) - conditional_tokens.each do |condition, token| - case condition - when Symbol then next unless send(condition) - when Proc then next unless condition.call - else raise ArgumentError, - "The class condition must be a Symbol or a Proc." - end - - case token - when Symbol then tokens << token.name - when String then tokens << token - when Array then tokens.concat(token) - else raise ArgumentError, - "Conditional classes must be Symbols, Strings, or Arrays of Symbols or Strings." - end - end - - tokens.compact.join(" ") + def tokens(...) + Phlex::Helpers::TokenGenerator.new(self, ...).call end def classes(*tokens, **conditional_tokens) - tokens = self.tokens(*tokens, **conditional_tokens) - - if tokens.empty? - {} - else - { class: tokens } - end + Phlex::Helpers::TokenGenerator.new(self, *tokens, namespace: :class, **conditional_tokens).call end def mix(*args) diff --git a/lib/phlex/helpers/token_generator.rb b/lib/phlex/helpers/token_generator.rb new file mode 100644 index 00000000..c16cb566 --- /dev/null +++ b/lib/phlex/helpers/token_generator.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Phlex::Helpers + class TokenGenerator + def initialize(context, *tokens, namespace: nil, **conditional_tokens) + raise ArgumentError, "Tokens should not be passed as an array" if tokens[0].is_a?(Array) + + @tokens = tokens + @context = context + @conditional_tokens = conditional_tokens + @namespace = namespace + end + + # Converts non-hash token values into a :then/:else hash structure + def self.normalize_conditional_tokens(tokens) + {}.tap do |validated_tokens| + tokens.each do |condition, value| + validated_tokens[condition] = begin + case value + when Hash then validate_hash!(value) + else { then: value } + end + end + end + end + end + + # Ensures a hash token value structure has at minimum a :then entry + def self.validate_hash!(hash) + raise ArgumentError, "Token hash mush have a :then key" unless hash.key?(:then) + + hash + end + + def call + return {} if @namespace && all_tokens.empty? + return { @namespace.to_sym => all_tokens_value } if @namespace + + all_tokens_value + end + + private + + # Joins all_tokens array with separator + def all_tokens_value + all_tokens.join(" ") + end + + # Returns array of tokens and conditional tokens that passed checks + def all_tokens + @all_tokens ||= [ + *@tokens, + *normalized_conditional_tokens.map do |symbol_or_proc, token_hash| + result = case symbol_or_proc + when Symbol then @context.send(symbol_or_proc) + when Proc then symbol_or_proc.call + else raise ArgumentError, + "The class condition must be a Symbol or a Proc." + end + + result ? token_hash[:then] : token_hash[:else] + end + ].compact.uniq + end + + # Memoized hash of normalized conditional tokens + def normalized_conditional_tokens + @normalized_conditional_tokens ||= TokenGenerator.normalize_conditional_tokens(@conditional_tokens) + end + end +end diff --git a/test/phlex/view/tokens.rb b/test/phlex/view/tokens.rb index 8dac0e48..9cd381f1 100644 --- a/test/phlex/view/tokens.rb +++ b/test/phlex/view/tokens.rb @@ -43,5 +43,51 @@ def template %(Home) end end + + with "negative conditionals" do + view do + def template + a href: "/", **classes("a", "b", "c", + active?: { + then: "active", + else: "inactive" + }) do + "Home" + end + end + + def active? + false + end + end + + it "works" do + expect(output).to be == + %(Home) + end + end + + with "no truthy conditionals" do + view do + def template + a href: "/", **classes( + active?: { + then: "active" + } + ) do + "Home" + end + end + + def active? + false + end + end + + it "works" do + expect(output).to be == + %(Home) + end + end end end