# 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!(/]/, '') 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 = "Hello world\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 = "\n" assert expected_default_name, Greet.call expected_rodrigo_name = "\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 = "0\n" assert expected_number_zero, Number.call expected_number_one = "1\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 = "0\n" assert expected_number_zero, number.call expected_number_one = "1\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 "