-
-
Save danieldocki/ca81f8f67cdf9a30a2bd7f38111378d6 to your computer and use it in GitHub Desktop.
UI (UI::Component)
This file contains hidden or 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
| # frozen_string_literal: true | |
| # == Gemfile == | |
| require 'bundler/inline' | |
| gemfile do | |
| source 'https://rubygems.org' | |
| gem 'u-test', '0.9.0' | |
| gem 'nokogiri', '>= 1.5' | |
| end | |
| # == lib == | |
| module UI | |
| module DataAccessor | |
| def data=(value) | |
| @data = value | |
| end | |
| def data; @data; end | |
| end | |
| end | |
| module UI | |
| require 'cgi' | |
| require 'nokogiri/html' | |
| class Component | |
| def self.fn(&block) | |
| new(block) | |
| end | |
| def self.call(data = {}) | |
| new.call(data) | |
| end | |
| def self.to_proc | |
| -> (data = {}) { call(data) } | |
| end | |
| def self.template(&block) | |
| define_method(:__template) { block } | |
| end | |
| def self.defaults(data = {}) | |
| define_method(:__defaults) { data } | |
| end | |
| def initialize(fn = nil) | |
| @fn = fn || __template | |
| @defaults = respond_to?(:__defaults) ? __defaults : {} | |
| end | |
| def call(data = nil) | |
| CGI.unescapeHTML(build_html_with(data)) | |
| end | |
| def to_proc | |
| -> (data) { call(data) } | |
| end | |
| private | |
| def build_html_with(data) | |
| builder = Nokogiri::HTML::Builder.new | |
| builder.context = builder | |
| builder.extend(DataAccessor) | |
| builder.data = apply_defaults_to(data) | |
| builder.instance_eval(&@fn) | |
| builder.to_html.sub!(/<!DOCTYPE.*[^>]/, '') | |
| end | |
| def apply_defaults_to(data) | |
| return @defaults.merge(data) if [data, @defaults].all?(Hash) | |
| data || @defaults | |
| end | |
| end | |
| end | |
| module UI | |
| def self.component(&block) | |
| UI::Component.fn(&block) | |
| end | |
| end | |
| # == test == | |
| class UITest < Microtest::Test | |
| class HelloWorld < UI::Component | |
| template do | |
| span.bar.foo! { | |
| text("Hello World") | |
| } | |
| end | |
| end | |
| def test_hello_world_component | |
| expected = "<span class=\"bold\">Hello world</span>\n" | |
| assert expected, HelloWorld.call | |
| end | |
| class Greet < UI::Component | |
| defaults name: 'John Doe' | |
| template do | |
| span.bar.foo! { | |
| text("Hello #{data[:name]}") | |
| } | |
| end | |
| end | |
| def test_greet_component | |
| expected_default_name = "<span class=\"bar\" id=\"foo\">Hello John Doe</span>\n" | |
| assert expected_default_name, Greet.call | |
| expected_rodrigo_name = "<span class=\"bar\" id=\"foo\">Hello Rodrigo</span>\n" | |
| assert expected_rodrigo_name, Greet.call(name: 'Rodrigo') | |
| expected_collection = [expected_default_name, expected_rodrigo_name] | |
| assert expected_collection, [{}, {name: 'Rodrigo'}].map(&Greet) | |
| end | |
| class Number < UI::Component | |
| defaults 0 | |
| template do | |
| strong { text(data) } | |
| end | |
| end | |
| def test_number_component | |
| expected_number_zero = "<strong>0</strong>\n" | |
| assert expected_number_zero, Number.call | |
| expected_number_one = "<strong>1</strong>\n" | |
| assert expected_number_one, Number.call(1) | |
| expected_collection = [expected_number_zero, expected_number_one] | |
| assert expected_collection, [nil, 1].map(&Number) | |
| end | |
| def test_fn_component | |
| number = UI::Component.fn do | |
| strong { text(data) } | |
| end | |
| expected_number_zero = "<strong>0</strong>\n" | |
| assert expected_number_zero, number.call | |
| expected_number_one = "<strong>1</strong>\n" | |
| assert expected_number_one, number.call(1) | |
| expected_collection = [expected_number_zero, expected_number_one] | |
| assert expected_collection, [nil, 1].map(&number) | |
| end | |
| def test_component_composition | |
| container = UI.component { div data } | |
| number = UI.component do | |
| strong { text(data) } | |
| end | |
| assert "<div><strong>0</strong>\n</div>\n", container.call(number.call) | |
| assert "<div><strong>1</strong>\n</div>\n", container.call(number.(1)) | |
| assert ["<div><strong>1</strong>\n</div>\n"], [1].map(&number).map(&container) | |
| end | |
| end | |
| # == test runner == | |
| Microtest.call |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment