Skip to content

Commit

Permalink
Create Komponent::ComponentRenderer
Browse files Browse the repository at this point in the history
  • Loading branch information
florentferry committed Mar 6, 2018
1 parent 65e7821 commit aa8b823
Show file tree
Hide file tree
Showing 25 changed files with 176 additions and 60 deletions.
10 changes: 10 additions & 0 deletions .simplecov
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require 'coveralls'

SimpleCov.minimum_coverage 90
SimpleCov.formatter = Coveralls::SimpleCov::Formatter

SimpleCov.start do
add_filter "/fixtures/"
add_filter "/test/"
add_filter "/features/"
end
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ gem "rubocop", require: false
group :test do
gem "aruba"
gem "cucumber"
gem "simplecov", require: false
gem "coveralls", require: false
end
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/komposable/komponent.svg?branch=master)](https://travis-ci.org/komposable/komponent)
[![Gem](https://img.shields.io/gem/v/komponent.svg)](https://github.com/komposable/komponent)
[![Dependency Status](https://beta.gemnasium.com/badges/github.com/komposable/komponent.svg)](https://beta.gemnasium.com/projects/github.com/komposable/komponent)
[![Coverage Status](https://coveralls.io/repos/github/komposable/komponent/badge.svg?branch=master)](https://coveralls.io/github/komposable/komponent?branch=master)

**Komponent** implements an opinionated way of organizing front-end code in Ruby on Rails, based on _components_.

Expand Down
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace :test do
t.libs << "lib"
t.test_files = FileList["test/**/*_test.rb"]
t.verbose = true
t.warning = false
end

Cucumber::Rake::Task.new(:cucumber) do |t|
Expand Down
3 changes: 3 additions & 0 deletions features/support/env.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

require 'simplecov'
1 change: 1 addition & 0 deletions fixtures/my_app/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@

module MyApp
class Application < Rails::Application
config.i18n.load_path += Dir[config.root.join('frontend/components/**/*.yml')]
end
end
1 change: 1 addition & 0 deletions fixtures/my_app/frontend/components/foo/_foo.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="foo"><%= properties[:bar] %></div>
1 change: 1 addition & 0 deletions fixtures/my_app/frontend/components/foo/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./foo.scss";
2 changes: 2 additions & 0 deletions fixtures/my_app/frontend/components/foo/foo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.foo {
}
5 changes: 5 additions & 0 deletions fixtures/my_app/frontend/components/foo/foo_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module FooComponent
extend ComponentHelper

property :bar, default: "Foobar"
end
1 change: 1 addition & 0 deletions fixtures/my_app/frontend/components/hello/_hello.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="hello"><%= t(".hello") %></div>
3 changes: 3 additions & 0 deletions fixtures/my_app/frontend/components/hello/hello.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
en:
hello_component:
hello: "Hello"
3 changes: 3 additions & 0 deletions fixtures/my_app/frontend/components/hello/hello.fr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fr:
hello_component:
hello: "Bonjour"
1 change: 1 addition & 0 deletions fixtures/my_app/frontend/components/hello/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "./hello.scss";
2 changes: 2 additions & 0 deletions fixtures/my_app/frontend/components/hello/hello.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.hello {
}
3 changes: 3 additions & 0 deletions fixtures/my_app/frontend/components/hello/hello_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module HelloComponent
extend ComponentHelper
end
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,5 @@ def property(name, options = {})

def self.extended(component)
component.properties = {}
component.class_eval do
def block_given_to_component?
@block_given_to_component.present?
end
end
end
end
File renamed without changes.
65 changes: 65 additions & 0 deletions lib/komponent/component_renderer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

module Komponent
class Renderer
attr_reader :context

def initialize(controller)
@context = controller.view_context.dup
@view_renderer = @context.view_renderer = @context.view_renderer.dup
@lookup_context = @view_renderer.lookup_context = @view_renderer.lookup_context.dup
end

def render(component, locals = {}, &block)
parts = component.split("/")
component_name = parts.join("_")

component_module_path = resolved_component_path(component)
.join("#{component_name}_component")
require_dependency(component_module_path)
component_module = "#{component_name}_component".camelize.constantize

@context.class_eval { prepend component_module }
@context.class_eval { prepend Komponent::Translation }

@lookup_context.prefixes = ["components/#{component}"]

capture_block = proc { capture(&block) } if block

@context.instance_eval do
if component_module.respond_to?(:properties)
locals = locals.dup
component_module.properties.each do |name, options|
unless locals.has_key?(name)
if options.has_key?(:default)
locals[name] = options[:default]
elsif options[:required]
raise "Missing required component parameter: #{name}"
end
end
end
end

locals.each do |name, value|
instance_variable_set(:"@#{name}", locals[name])
end

define_singleton_method(:properties) { locals }
define_singleton_method(:block_given_to_component?) { !!block }
end

begin
@context.render("components/#{component}/#{parts.join('_')}", &capture_block)
rescue ActionView::MissingTemplate
warn "[DEPRECATION WARNING] `#{parts.last}` filename in namespace is deprecated in favor of `#{parts.join('_')}`."
@context.render("components/#{component}/#{parts.last}", &capture_block)
end
end

private

def resolved_component_path(component)
Komponent::ComponentPathResolver.new.resolve(component)
end
end
end
60 changes: 9 additions & 51 deletions lib/komponent/komponent_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,17 @@

module KomponentHelper
def component(component, locals = {}, &block)
component_path = Komponent::ComponentPathResolver.new.resolve(component)

parts = component.split("/")
component_name = parts.join("_")
component_path = component_path.join("#{component_name}_component")

require_dependency(component_path)

component_module = "#{component_name}_component".camelize.constantize
context = controller.view_context.dup

context.view_flow = view_flow

view_renderer = context.view_renderer = context.view_renderer.dup
lookup_context = view_renderer.lookup_context = view_renderer.lookup_context.dup
lookup_context.prefixes = ["components/#{component}"]

context.class_eval { prepend component_module }
context.class_eval { prepend Komponent::Translation }

capture_block = proc { capture(&block) } if block

context.instance_eval do
if component_module.respond_to?(:properties)
locals = locals.dup
component_module.properties.each do |name, options|
unless locals.has_key?(name)
if options.has_key?(:default)
locals[name] = options[:default]
elsif options[:required]
raise "Missing required component parameter: #{name}"
end
end
end
end

locals.each do |name, value|
instance_variable_set(:"@#{name}", locals[name])
end

define_singleton_method(:properties) { locals }

instance_variable_set(:"@block_given_to_component", block)
end

begin
context.render("components/#{component}/#{parts.join('_')}", &capture_block)
rescue ActionView::MissingTemplate
warn "[DEPRECATION WARNING] `#{parts.last}` filename in namespace is deprecated in favor of `#{parts.join('_')}`."
context.render("components/#{component}/#{parts.last}", &capture_block)
end
Komponent::Renderer.new(
controller,
).render(
component,
locals,
&block
)
end
alias :c :component

# :nocov:
def render_partial(partial_name, locals = {}, &block)
warn "[DEPRECATION WARNING] `render_partial` is deprecated. Please use default `render` instead."

Expand All @@ -79,4 +36,5 @@ def render_partial(partial_name, locals = {}, &block)

rendered_partial
end
# :nocov:
end
7 changes: 4 additions & 3 deletions lib/komponent/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# frozen_string_literal: true

require 'webpacker'
require 'komponent/core/component_helper'
require 'komponent/core/translation'
require 'komponent/core/component_path_resolver'
require 'komponent/component_helper'
require 'komponent/component_path_resolver'
require 'komponent/component_renderer'
require 'komponent/translation'

module Komponent
class Railtie < Rails::Railtie
Expand Down
File renamed without changes.
29 changes: 29 additions & 0 deletions test/komponent/component_renderer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

require 'test_helper'

class FakeController < ApplicationController
def initialize(method_name = nil, &method_body)
if method_name and block_given?
self.class.send(:define_method, method_name, method_body)
Rails.application.routes.draw do
get method_name, to: "fake##{method_name}"
end
end
end
end

class ComponentRendererTest < ActionController::TestCase
def test_
@controller = FakeController.new

renderer = Komponent::Renderer.new(@controller)
renderer.render('all', text: 'hello world')
@context = renderer.context

assert_respond_to @context, :block_given_to_component?
assert_respond_to @context, :properties
assert_respond_to @context, :translate
assert_equal @context.instance_variable_get(:'@text'), 'hello world'
end
end
28 changes: 27 additions & 1 deletion test/komponent/komponent_helper_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,39 @@
require 'test_helper'

class KomponentHelperTest < ActionView::TestCase
def test_helper_raises_component_missing_error
assert_raise Komponent::ComponentPathResolver::MissingComponentError do
component('missing')
end
end

def test_helper_renders_makes_locals_available_as_instance_variables
assert_equal %(<div class="world">🌎</div>), component('world', world: "🌎").chomp
assert_equal \
%(<div class="world">🌎</div>),
component('world', world: "🌎").chomp
end

def test_helper_makes_all_properties_accessible
assert_equal \
%(<div class="all">🌎 😎</div>),
component('all', world: "🌎", sunglasses: "😎").chomp
end

def test_helper_renders_localized_keys
I18n.locale = :en
assert_equal \
%(<div class="hello">Hello</div>),
component('hello').chomp

I18n.locale = :fr
assert_equal \
%(<div class="hello">Bonjour</div>),
component('hello').chomp
end

def test_helper_renders_default_property
assert_equal \
%(<div class="foo">Foobar</div>),
component('foo').chomp
end
end
2 changes: 2 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'simplecov'

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../fixtures/my_app/config/environment', __FILE__)
require 'rails/test_help'

0 comments on commit aa8b823

Please sign in to comment.