Skip to content

Instantly share code, notes, and snippets.

@danieldocki
Forked from serradura/u-ui.rb
Created April 28, 2022 12:09
Show Gist options
  • Select an option

  • Save danieldocki/ca81f8f67cdf9a30a2bd7f38111378d6 to your computer and use it in GitHub Desktop.

Select an option

Save danieldocki/ca81f8f67cdf9a30a2bd7f38111378d6 to your computer and use it in GitHub Desktop.
UI (UI::Component)
# 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