-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
252 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
require "../../spec_helper" | ||
|
||
private class ButtonComponent | ||
include Blueprint::HTML | ||
|
||
style_builder do | ||
base "btn" | ||
|
||
variants do | ||
color { | ||
blue "btn-blue" | ||
red "btn-red" | ||
} | ||
|
||
size { | ||
xs "btn-xs" | ||
md "btn-md" | ||
lg "btn-lg" | ||
} | ||
|
||
outline { | ||
yes "btn-outline" | ||
} | ||
|
||
ghost { | ||
yes "btn-ghost" | ||
no "btn-normal" | ||
} | ||
|
||
clicked { | ||
yes "btn-clicked" | ||
no "btn-unclick" | ||
} | ||
end | ||
|
||
defaults color: :blue, size: :md | ||
end | ||
|
||
private def blueprint(&) | ||
a class: build_style(size: :xs, outline: true) do | ||
yield | ||
end | ||
end | ||
end | ||
|
||
private class SingleVariantStyle | ||
include Blueprint::HTML | ||
|
||
style_builder do | ||
variants do | ||
color { | ||
red "red" | ||
blue "blue" | ||
} | ||
end | ||
end | ||
end | ||
|
||
describe "style building" do | ||
it "allows defining a base class" do | ||
classes = ButtonComponent.build_style | ||
|
||
classes.starts_with?("btn ").should be_true | ||
end | ||
|
||
it "allows definig default variant options" do | ||
classes = ButtonComponent.build_style | ||
|
||
classes.should contain "btn-blue btn-md" | ||
end | ||
|
||
it "allows picking variants" do | ||
classes = ButtonComponent.build_style(color: :red, size: :lg) | ||
|
||
classes.should eq "btn btn-red btn-lg" | ||
end | ||
|
||
it "allows boolean values for yes/no variants" do | ||
classes = ButtonComponent.build_style(outline: :yes, ghost: false, clicked: true) | ||
|
||
classes.should contain "btn-outline btn-normal btn-clicked" | ||
end | ||
|
||
it "raises error passing an invalid variant" do | ||
msg = "`shadow` variant was not defined inside style_builder `variants` block." | ||
expect_raises(Blueprint::HTML::StyleBuilder::InvalidVariantError, msg) do | ||
ButtonComponent.build_style(shadow: :lg) | ||
end | ||
end | ||
|
||
it "raises error passing an invalid variant option" do | ||
msg = "`yellow` is an invalid option for color variant. Valid options are [:blue, :red]." | ||
bool_msg = "`confirm` is an invalid option for ghost variant. Valid options are [:yes, true, :no, false]." | ||
|
||
expect_raises(Blueprint::HTML::StyleBuilder::InvalidVariantOptionError, msg) do | ||
ButtonComponent.build_style(color: :yellow) | ||
end | ||
|
||
expect_raises(Blueprint::HTML::StyleBuilder::InvalidVariantOptionError, bool_msg) do | ||
ButtonComponent.build_style(ghost: :confirm) | ||
end | ||
end | ||
|
||
it "allows to build style inside components" do | ||
actual_html = ButtonComponent.new.to_html { "Build Style!" } | ||
expected_html = normalize_html <<-HTML | ||
<a class="btn btn-xs btn-outline btn-blue">Build Style!</a> | ||
HTML | ||
|
||
actual_html.should eq expected_html | ||
end | ||
|
||
# this tests that style_builder macro works without base, defaults or more variants | ||
it "allows to define style build with a single variant" do | ||
SingleVariantStyle.build_style.should eq "" | ||
|
||
SingleVariantStyle.build_style(color: :red).should eq "red" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
module Blueprint::HTML::StyleBuilder | ||
class InvalidVariantError < Exception; end | ||
|
||
class InvalidVariantOptionError < Exception; end | ||
|
||
macro build_style(**styles) | ||
([ | ||
{% if @type.class.has_method?(:style_builder_base_classes) %} | ||
{{@type}}.style_builder_base_classes, | ||
{% end %} | ||
|
||
{% for variant, option in styles %} | ||
{% if @type.class.has_method?("style_builder_#{variant}_variant") %} | ||
{{@type}}.style_builder_{{variant}}_variant({{option}}), | ||
{% else %} | ||
raise({{@type}}::InvalidVariantError.new("`{{variant}}` variant was not defined inside style_builder `variants` block.")), | ||
{% end %} | ||
{% end %} | ||
|
||
{% if @type.has_constant?("STYLE_BUILDER_DEFAULT_VARIANT_OPTIONS") %} | ||
{% used_variants = styles.keys %} | ||
{% for variant, option in STYLE_BUILDER_DEFAULT_VARIANT_OPTIONS %} | ||
{% unless used_variants.includes?(variant) %} | ||
{{@type}}.style_builder_{{variant}}_variant({{option}}), | ||
{% end %} | ||
{% end %} | ||
{% end %} | ||
] of String).join(" ") | ||
end | ||
|
||
macro style_builder(&block) | ||
__parse_style_builer_top_level_body__ do | ||
{{ block.body }} | ||
end | ||
end | ||
|
||
private macro __parse_style_builer_top_level_body__(&block) | ||
{% node = block.body %} | ||
|
||
{% if node.is_a?(Expressions) %} | ||
{% for expressions in node.expressions %} | ||
__parse_style_builer_top_level_node__ { {{ expressions }} } | ||
{% end %} | ||
{% elsif node.is_a?(Call) %} | ||
__parse_style_builer_top_level_node__ { {{ node }} } | ||
{% end %} | ||
end | ||
|
||
private macro __parse_style_builer_top_level_node__(&block) | ||
{% node = block.body %} | ||
|
||
{% if node.name == "base" %} | ||
__parse_style_builder_base_node__ { {{ node }} } | ||
{% elsif node.name == "variants" %} | ||
{% variants_node = node.block.body %} | ||
{% if variants_node.is_a?(Expressions) %} | ||
{% for expression in variants_node.expressions %} | ||
__parse_style_builder_variant_node__ { {{ expression }} } | ||
{% end %} | ||
{% elsif variants_node.is_a?(Call) %} | ||
__parse_style_builder_variant_node__ { {{ variants_node }} } | ||
{% end %} | ||
{% elsif node.name == "defaults" %} | ||
__parse_style_builder_defaults_node__ { {{ node }} } | ||
{% end %} | ||
end | ||
|
||
private macro __parse_style_builder_base_node__(&block) | ||
{% node = block.body %} | ||
|
||
def self.style_builder_base_classes | ||
{{ node.args.join(" ") }} | ||
end | ||
end | ||
|
||
private macro __parse_style_builder_defaults_node__(&block) | ||
{% node = block.body %} | ||
|
||
STYLE_BUILDER_DEFAULT_VARIANT_OPTIONS = { | ||
{% for named_argument in node.named_args %} | ||
{{named_argument.name.id}}: {{named_argument.value}}, | ||
{% end %} | ||
} | ||
end | ||
|
||
private macro __parse_style_builder_variant_node__(&block) | ||
{% node = block.body %} | ||
{% body = node.block.body %} | ||
|
||
def self.style_builder_{{node.name}}_variant(value : Symbol | Bool) : String | ||
{% valid_options = [] of Symbol | Bool %} | ||
case value | ||
{% if body.is_a?(Call) %} | ||
{% valid_options << body.name.symbolize %} | ||
|
||
{% if body.name.id == "yes" %} | ||
{% valid_options << true %} | ||
when :{{body.name.id}}, true | ||
{% elsif body.name.id == "no" %} | ||
{% valid_options << false %} | ||
when :{{body.name.id}}, false | ||
{% else %} | ||
when :{{body.name.id}} | ||
{% end %} | ||
|
||
{{ body.args.join(" ") }} | ||
{% elsif body.is_a?(Expressions) %} | ||
{% for expression in body.expressions %} | ||
{% valid_options << expression.name.symbolize %} | ||
|
||
{% if expression.name.id == "yes" %} | ||
{% valid_options << true %} | ||
when :{{expression.name.id}}, true | ||
{% elsif expression.name.id == "no" %} | ||
{% valid_options << false %} | ||
when :{{expression.name.id}}, false | ||
{% else %} | ||
when :{{expression.name.id}} | ||
{% end %} | ||
|
||
{{ expression.args.join(" ") }} | ||
{% end %} | ||
{% end %} | ||
else | ||
raise InvalidVariantOptionError.new <<-TXT | ||
`#{value}` is an invalid option for {{node.name}} variant. Valid options are {{valid_options}}. | ||
TXT | ||
end | ||
end | ||
end | ||
end |