dry-view 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 03a55b8f70f9a2c483db8b54a34cc46470b67824
4
+ data.tar.gz: 8bb2990860563bfd3efc8fcc42f2d08a2642b743
5
+ SHA512:
6
+ metadata.gz: 407e38a1b4c40c3cb95146218771f0099a61345abededc7cde29ecbfbcf1448288208786ef35cd463dcfe8c2d532c44fb57dc66790f62b53b42ba97b5726825e
7
+ data.tar.gz: 2c4c61cb1325de92fab40a23ede167b93be155f0e0981ff5ef5f51faee9126748756f3a03000962b136132936e78a29746a21371e08095b008a130d716968a63
@@ -0,0 +1,26 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Documentation cache and generated files:
13
+ /.yardoc/
14
+ /_yardoc/
15
+ /doc/
16
+ /rdoc/
17
+
18
+ ## Environment normalisation:
19
+ /.bundle/
20
+ /lib/bundler/man/
21
+
22
+ # For a library or gem, you might want to ignore these files since the code is
23
+ # intended to run in multiple environments; otherwise, check them in:
24
+ Gemfile.lock
25
+ .ruby-version
26
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,33 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ bundler_args: --without tools benchmarks
5
+ script:
6
+ - bundle exec rake spec
7
+ rvm:
8
+ - 2.0
9
+ - 2.1
10
+ - 2.2
11
+ - 2.3.0
12
+ - rbx-2
13
+ - jruby-9000
14
+ - ruby-head
15
+ - jruby-head
16
+ env:
17
+ global:
18
+ - JRUBY_OPTS='--dev -J-Xmx1024M'
19
+ matrix:
20
+ allow_failures:
21
+ - rvm: ruby-head
22
+ - rvm: jruby-head
23
+ notifications:
24
+ email: false
25
+ webhooks:
26
+ urls:
27
+ - https://webhooks.gitter.im/e/19098b4253a72c9796db
28
+ on_success: change # options: [always|never|change] default: always
29
+ on_failure: always # options: [always|never|change] default: always
30
+ on_start: false # default: false
31
+ addons:
32
+ code_climate:
33
+ repo_token: c1c8f334714254acf5da901ca4da6afe24b15408074faae27159e3d14a450b62
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'byebug', platform: :mri
7
+ gem 'slim'
8
+
9
+ gem 'codeclimate-test-reporter', platform: :rbx
10
+ end
11
+
12
+ group :tools do
13
+ gem 'pry'
14
+ end
15
+
16
+ group :benchmarks do
17
+ gem 'benchmark-ips'
18
+ gem 'actionview'
19
+ end
@@ -0,0 +1,10 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Piotr Solnica
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,15 @@
1
+ [gitter]: https://gitter.im/dry-rb/chat
2
+ [gem]: https://rubygems.org/gems/dry-view
3
+ [travis]: https://travis-ci.org/dry-rb/dry-view
4
+ [code_climate]: https://codeclimate.com/github/dry-rb/dry-view
5
+ [inch]: http://inch-ci.org/github/dry-rb/dry-view
6
+
7
+ # dry-view [![Join the Gitter chat](https://badges.gitter.im/Join%20Chat.svg)][gitter]
8
+
9
+ [![Gem Version](https://img.shields.io/gem/v/dry-view.svg)][gem]
10
+ [![Build Status](https://img.shields.io/travis/dry-rb/dry-view.svg)][travis]
11
+ [![Code Climate](https://img.shields.io/codeclimate/github/dry-rb/dry-view.svg)][code_climate]
12
+ [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/dry-rb/dry-view.svg)][code_climate]
13
+ [![API Documentation Coverage](http://inch-ci.org/github/dry-rb/dry-view.svg)][inch]
14
+
15
+ Data-oriented view rendering system.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: [:spec]
@@ -0,0 +1 @@
1
+ <a href="/users/1">User</a>
@@ -0,0 +1,25 @@
1
+ require 'pathname'
2
+ require 'benchmark/ips'
3
+ require 'dry/view/renderer'
4
+ require 'action_view'
5
+
6
+ class ActionRender
7
+ include ActionView::Helpers
8
+
9
+ def button
10
+ link_to('User', '/users/1')
11
+ end
12
+ end
13
+
14
+ action_renderer = ActionRender.new
15
+ rodakase_renderer = Dry::View::Renderer.new(Pathname(__FILE__).dirname.join('templates'), engine: :erb)
16
+
17
+ template = rodakase_renderer.dir.join('button.erb')
18
+
19
+ SCOPE = {}
20
+
21
+ Benchmark.ips do |x|
22
+ x.report('actionview') { action_renderer.button }
23
+ x.report('rodakase') { rodakase_renderer.render(template, SCOPE) }
24
+ x.compare!
25
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dry/view/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dry-view"
8
+ spec.version = Dry::View::VERSION
9
+ spec.authors = ["Piotr Solnica"]
10
+ spec.email = ["piotr.solnica@gmail.com"]
11
+ spec.summary = "Lightweight web application stack on top of Roda"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/dry-rb/dry-view"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "inflecto", "~> 0"
22
+ spec.add_runtime_dependency "tilt", "~> 2.0"
23
+ spec.add_runtime_dependency "dry-configurable", "~> 0.1"
24
+ spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.7"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.1"
29
+ spec.add_development_dependency "capybara", "~> 2.5"
30
+ end
@@ -0,0 +1 @@
1
+ require 'dry/view'
@@ -0,0 +1,2 @@
1
+ require 'dry/view/renderer'
2
+ require 'dry/view/layout'
@@ -0,0 +1,122 @@
1
+ require 'dry-configurable'
2
+ require 'dry-equalizer'
3
+ require 'inflecto'
4
+
5
+ require 'dry/view/part'
6
+ require 'dry/view/value_part'
7
+ require 'dry/view/null_part'
8
+ require 'dry/view/renderer'
9
+
10
+ module Dry
11
+ module View
12
+ class Layout
13
+ include Dry::Equalizer(:config)
14
+
15
+ Scope = Struct.new(:page)
16
+
17
+ DEFAULT_DIR = 'layouts'.freeze
18
+
19
+ extend Dry::Configurable
20
+
21
+ setting :root
22
+ setting :name
23
+ setting :template
24
+ setting :formats, { html: :erb }
25
+ setting :scope
26
+
27
+ attr_reader :config, :scope, :layout_dir, :layout_path, :template_path,
28
+ :default_format
29
+
30
+ def self.renderer(format = default_format)
31
+ unless config.formats.key?(format.to_sym)
32
+ raise ArgumentError, "format +#{format}+ is not configured"
33
+ end
34
+
35
+ renderers[format]
36
+ end
37
+
38
+ def self.renderers
39
+ @renderers ||= Hash.new do |h, key|
40
+ h[key.to_sym] = Renderer.new(
41
+ config.root, format: key, engine: config.formats[key.to_sym]
42
+ )
43
+ end
44
+ end
45
+
46
+ def self.default_format
47
+ config.formats.keys.first
48
+ end
49
+
50
+ def initialize
51
+ @config = self.class.config
52
+ @default_format = self.class.default_format
53
+ @layout_dir = DEFAULT_DIR
54
+ @layout_path = "#{layout_dir}/#{config.name}"
55
+ @template_path = config.template
56
+ @scope = config.scope
57
+ end
58
+
59
+ def call(options = {})
60
+ renderer = self.class.renderer(options.fetch(:format, default_format))
61
+
62
+ renderer.(layout_path, layout_scope(options, renderer)) do
63
+ renderer.(template_path, template_scope(options, renderer))
64
+ end
65
+ end
66
+
67
+ def locals(options)
68
+ options.fetch(:locals, {})
69
+ end
70
+
71
+ def parts(locals, renderer)
72
+ return empty_part(template_path, renderer) unless locals.any?
73
+
74
+ part_hash = locals.each_with_object({}) do |(key, value), result|
75
+ part =
76
+ case value
77
+ when Array
78
+ el_key = Inflecto.singularize(key).to_sym
79
+
80
+ template_part(
81
+ key, renderer,
82
+ value.map { |element| template_part(el_key, renderer, element) }
83
+ )
84
+ else
85
+ template_part(key, renderer, value)
86
+ end
87
+
88
+ result[key] = part
89
+ end
90
+
91
+ part(template_path, renderer, part_hash)
92
+ end
93
+
94
+ private
95
+
96
+ def layout_scope(options, renderer)
97
+ Scope.new(layout_part(:page, renderer, options.fetch(:scope, scope)))
98
+ end
99
+
100
+ def template_scope(options, renderer)
101
+ parts(locals(options), renderer)
102
+ end
103
+
104
+ def layout_part(name, renderer, value)
105
+ part(layout_dir, renderer, { name => value })
106
+ end
107
+
108
+ def template_part(name, renderer, value)
109
+ part(template_path, renderer, { name => value })
110
+ end
111
+
112
+ def part(dir, renderer, value = {})
113
+ part_class = value.values[0] ? ValuePart : NullPart
114
+ part_class.new(renderer.chdir(dir), value)
115
+ end
116
+
117
+ def empty_part(dir, renderer)
118
+ Part.new(renderer.chdir(dir))
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,30 @@
1
+ require 'dry-equalizer'
2
+ require 'dry/view/value_part'
3
+
4
+ module Dry
5
+ module View
6
+ class NullPart < ValuePart
7
+ def [](key)
8
+ end
9
+
10
+ def each(&block)
11
+ end
12
+
13
+ def respond_to_missing?(*)
14
+ true
15
+ end
16
+
17
+ private
18
+
19
+ def method_missing(meth, *args, &block)
20
+ template_path = template?("#{meth}_missing")
21
+
22
+ if template_path
23
+ render(template_path)
24
+ else
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ require 'dry-equalizer'
2
+
3
+ module Dry
4
+ module View
5
+ class Part
6
+ include Dry::Equalizer(:renderer)
7
+
8
+ attr_reader :renderer
9
+
10
+ def initialize(renderer)
11
+ @renderer = renderer
12
+ end
13
+
14
+ def render(path, &block)
15
+ renderer.render(path, self, &block)
16
+ end
17
+
18
+ def template?(name)
19
+ renderer.lookup("_#{name}")
20
+ end
21
+
22
+ def respond_to_missing?(meth, include_private = false)
23
+ super || template?(meth)
24
+ end
25
+
26
+ private
27
+
28
+ def method_missing(meth, *args, &block)
29
+ template_path = template?(meth)
30
+
31
+ if template_path
32
+ render(template_path, &block)
33
+ else
34
+ super
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,68 @@
1
+ require 'tilt'
2
+ require 'dry-equalizer'
3
+
4
+ module Dry
5
+ module View
6
+ class Renderer
7
+ include Dry::Equalizer(:dir, :root, :engine)
8
+
9
+ TemplateNotFoundError = Class.new(StandardError)
10
+
11
+ attr_reader :dir, :root, :format, :engine, :tilts
12
+
13
+ def self.tilts
14
+ @__engines__ ||= {}
15
+ end
16
+
17
+ def initialize(dir, options = {})
18
+ @dir = dir
19
+ @root = options.fetch(:root, dir)
20
+ @format = options[:format]
21
+ @engine = options[:engine]
22
+ @tilts = self.class.tilts
23
+ end
24
+
25
+ def call(template, scope, &block)
26
+ path = lookup(template)
27
+
28
+ if path
29
+ render(path, scope, &block)
30
+ else
31
+ raise TemplateNotFoundError, "Template #{template} could not be looked up within #{root}"
32
+ end
33
+ end
34
+
35
+ def render(path, scope, &block)
36
+ tilt(path).render(scope, &block)
37
+ end
38
+
39
+ def tilt(path)
40
+ tilts.fetch(path) { tilts[path] = Tilt[engine].new(path) }
41
+ end
42
+
43
+ def lookup(name)
44
+ template?(name) || template?("shared/#{name}") || !root? && chdir('..').lookup(name)
45
+ end
46
+
47
+ def root?
48
+ dir == root
49
+ end
50
+
51
+ def template?(name)
52
+ template_path = path(name)
53
+
54
+ if File.exist?(template_path)
55
+ template_path
56
+ end
57
+ end
58
+
59
+ def path(name)
60
+ dir.join("#{name}.#{format}.#{engine}")
61
+ end
62
+
63
+ def chdir(dirname)
64
+ self.class.new(dir.join(dirname), engine: engine, format: format, root: root)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,50 @@
1
+ require 'dry-equalizer'
2
+ require 'dry/view/part'
3
+
4
+ module Dry
5
+ module View
6
+ class ValuePart < Part
7
+ include Dry::Equalizer(:renderer, :_data, :_value)
8
+
9
+ attr_reader :_data, :_value
10
+
11
+ def initialize(renderer, data)
12
+ super(renderer)
13
+ @_data = data
14
+ @_value = data.values[0]
15
+ end
16
+
17
+ def to_s
18
+ _value.to_s
19
+ end
20
+
21
+ def [](key)
22
+ _value[key]
23
+ end
24
+
25
+ def each(&block)
26
+ _value.each(&block)
27
+ end
28
+
29
+ def respond_to_missing?(meth, include_private = false)
30
+ _data.key?(meth) || super
31
+ end
32
+
33
+ private
34
+
35
+ def method_missing(meth, *args, &block)
36
+ template_path = template?(meth)
37
+
38
+ if template_path
39
+ render(template_path, &block)
40
+ elsif _data.key?(meth)
41
+ _data[meth]
42
+ elsif _value.respond_to?(meth)
43
+ _value.public_send(meth, *args, &block)
44
+ else
45
+ super
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ module Dry
2
+ module View
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ h1 Hello
@@ -0,0 +1,6 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title == page.title
5
+ body
6
+ == yield
@@ -0,0 +1,3 @@
1
+ # <%= page.title %>
2
+
3
+ <%= yield %>
@@ -0,0 +1,3 @@
1
+ ol
2
+ - tasks.each do |task|
3
+ li == task[:title]
@@ -0,0 +1,2 @@
1
+ h1 = header[:title]
2
+ p = user[:name]
@@ -0,0 +1,5 @@
1
+ h2 = subtitle
2
+
3
+ .users
4
+ == users.index_table do
5
+ == users.tbody
@@ -0,0 +1,5 @@
1
+ ## <%= subtitle %>
2
+
3
+ <% users.each do |user| %>
4
+ * <%= user[:name] %> (<%= user[:email] %>)
5
+ <% end %>
@@ -0,0 +1,2 @@
1
+ tr
2
+ == yield
@@ -0,0 +1,5 @@
1
+ tbody
2
+ - users.each do |user|
3
+ == user.row do
4
+ td = user[:name]
5
+ td = user[:email]
@@ -0,0 +1,72 @@
1
+ RSpec.describe 'dry-view' do
2
+ let(:view_class) do
3
+ klass = Class.new(Dry::View::Layout)
4
+
5
+ klass.configure do |config|
6
+ config.root = SPEC_ROOT.join('fixtures/templates')
7
+ config.name = 'app'
8
+ config.template = 'users'
9
+ config.formats = {html: :slim, txt: :erb}
10
+ end
11
+
12
+ klass
13
+ end
14
+
15
+ let(:scope) do
16
+ Struct.new(:title).new('dry-view rocks!')
17
+ end
18
+
19
+ it 'renders within a layout using provided scope' do
20
+ view = view_class.new
21
+
22
+ users = [
23
+ { name: 'Jane', email: 'jane@doe.org' },
24
+ { name: 'Joe', email: 'joe@doe.org' }
25
+ ]
26
+
27
+ expect(view.(scope: scope, locals: { subtitle: "Users List", users: users })).to eql(
28
+ '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><h2>Users List</h2><div class="users"><table><tbody><tr><td>Jane</td><td>jane@doe.org</td></tr><tr><td>Joe</td><td>joe@doe.org</td></tr></tbody></table></div></body></html>'
29
+ )
30
+ end
31
+
32
+ it 'renders a view with an alternative format and engine' do
33
+ view = view_class.new
34
+
35
+ users = [
36
+ { name: 'Jane', email: 'jane@doe.org' },
37
+ { name: 'Joe', email: 'joe@doe.org' }
38
+ ]
39
+
40
+ expect(view.(scope: scope, locals: { subtitle: 'Users List', users: users }, format: 'txt').strip).to eql(
41
+ "# dry-view rocks!\n\n## Users List\n\n* Jane (jane@doe.org)\n* Joe (joe@doe.org)"
42
+ )
43
+ end
44
+
45
+ describe 'inheritance' do
46
+ let(:parent_view) do
47
+ klass = Class.new(Dry::View::Layout)
48
+
49
+ klass.setting :root, SPEC_ROOT.join('fixtures/templates')
50
+ klass.setting :name, 'app'
51
+ klass.setting :formats, {html: :slim}
52
+
53
+ klass
54
+ end
55
+
56
+ let(:child_view) do
57
+ Class.new(parent_view) do
58
+ configure do |config|
59
+ config.template = 'tasks'
60
+ end
61
+ end
62
+ end
63
+
64
+ it 'renders within a parent class layout using provided scope' do
65
+ view = child_view.new
66
+
67
+ expect(view.(scope: scope, locals: { tasks: [{ title: 'one' }, { title: 'two' }] })).to eql(
68
+ '<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><ol><li>one</li><li>two</li></ol></body></html>'
69
+ )
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,20 @@
1
+ if RUBY_ENGINE == "rbx"
2
+ require "codeclimate-test-reporter"
3
+ CodeClimate::TestReporter.start
4
+ end
5
+
6
+ begin
7
+ require 'byebug'
8
+ rescue LoadError; end
9
+
10
+ SPEC_ROOT = Pathname(__FILE__).dirname
11
+
12
+ require 'dry-view'
13
+ require 'slim'
14
+
15
+ RSpec.configure do |config|
16
+ config.disable_monkey_patching!
17
+
18
+ config.order = :random
19
+ Kernel.srand config.seed
20
+ end
@@ -0,0 +1,55 @@
1
+ RSpec.describe Dry::View::Layout do
2
+ subject(:layout) { layout_class.new }
3
+
4
+ let(:layout_class) do
5
+ klass = Class.new(Dry::View::Layout)
6
+
7
+ klass.configure do |config|
8
+ config.root = SPEC_ROOT.join('fixtures/templates')
9
+ config.name = 'app'
10
+ config.template = 'user'
11
+ config.formats = {html: :slim}
12
+ end
13
+
14
+ klass
15
+ end
16
+
17
+ let(:page) do
18
+ double(:page, title: 'Test')
19
+ end
20
+
21
+ let(:options) do
22
+ { scope: page, locals: { user: { name: 'Jane' }, header: { title: 'User' } } }
23
+ end
24
+
25
+ let(:renderer) do
26
+ layout.class.renderers[:html]
27
+ end
28
+
29
+ describe '#call' do
30
+ it 'renders template within the layout' do
31
+ expect(layout.(options)).to eql(
32
+ '<!DOCTYPE html><html><head><title>Test</title></head><body><h1>User</h1><p>Jane</p></body></html>'
33
+ )
34
+ end
35
+ end
36
+
37
+ describe '#parts' do
38
+ it 'returns view parts' do
39
+ part = layout.parts({ user: { id: 1, name: 'Jane' } }, renderer)
40
+
41
+ expect(part[:id]).to be(1)
42
+ expect(part[:name]).to eql('Jane')
43
+ end
44
+
45
+ it 'builds null parts for nil values' do
46
+ part = layout.parts({ user: nil }, renderer)
47
+
48
+ expect(part[:id]).to be_nil
49
+ end
50
+
51
+ it 'returns empty part when no locals are passed' do
52
+ expect(layout.parts({}, renderer)).to be_instance_of(Dry::View::Part)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
1
+ require 'dry/view/null_part'
2
+
3
+ RSpec.describe Dry::View::NullPart do
4
+ subject(:part) do
5
+ Dry::View::NullPart.new(renderer, data)
6
+ end
7
+
8
+ let(:name) { :user }
9
+ let(:data) { { user: nil } }
10
+
11
+ let(:renderer) { double(:renderer) }
12
+
13
+ describe '#[]' do
14
+ it 'returns nil for any data value names' do
15
+ expect(part[:email]).to eql(nil)
16
+ end
17
+ end
18
+
19
+ describe '#method_missing' do
20
+ it 'renders a template with the _missing suffix' do
21
+ expect(renderer).to receive(:lookup).with('_row_missing').and_return('_row_missing.slim')
22
+ expect(renderer).to receive(:render).with('_row_missing.slim', part)
23
+
24
+ part.row
25
+ end
26
+
27
+ it 'renders a _missing template within another when block is passed' do
28
+ block = proc { part.fields }
29
+
30
+ expect(renderer).to receive(:lookup).with('_form_missing').and_return('form_missing.slim')
31
+ expect(renderer).to receive(:lookup).with('_fields_missing').and_return('fields_missing.slim')
32
+
33
+ expect(renderer).to receive(:render).with('form_missing.slim', part, &block)
34
+ expect(renderer).to receive(:render).with('fields_missing.slim', part)
35
+
36
+ part.form(block)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ require 'dry/view/renderer'
2
+
3
+ RSpec.describe Dry::View::Renderer do
4
+ subject(:renderer) do
5
+ Dry::View::Renderer.new(SPEC_ROOT.join('fixtures/templates'), format: 'html', engine: :slim)
6
+ end
7
+
8
+ let(:scope) { double(:scope) }
9
+
10
+ describe '#call' do
11
+ it 'renders template' do
12
+ expect(renderer.('hello', scope)).to eql('<h1>Hello</h1>')
13
+ end
14
+
15
+ it 'looks up shared template in current dir' do
16
+ expect(renderer.('_shared_hello', scope)).to eql('<h1>Hello</h1>')
17
+ end
18
+
19
+ it 'looks up shared template in upper dir' do
20
+ expect(renderer.chdir('greetings').('_shared_hello', scope)).to eql('<h1>Hello</h1>')
21
+ end
22
+
23
+ it 'raises error when template was not found' do
24
+ expect {
25
+ renderer.('not_found', scope)
26
+ }.to raise_error(Dry::View::Renderer::TemplateNotFoundError, /not_found/)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ require 'dry/view/part'
2
+
3
+ RSpec.describe Dry::View::ValuePart do
4
+ subject(:part) do
5
+ Dry::View::ValuePart.new(renderer, data)
6
+ end
7
+
8
+ let(:name) { :user }
9
+ let(:data) { { user: { email: 'jane@doe.org' } } }
10
+
11
+ let(:renderer) { double(:renderer) }
12
+
13
+ describe '#[]' do
14
+ it 'gives access to data values' do
15
+ expect(part[:email]).to eql('jane@doe.org')
16
+ end
17
+ end
18
+
19
+ describe '#render' do
20
+ it 'renders given template' do
21
+ expect(renderer).to receive(:render).with('row.slim', part)
22
+
23
+ part.render('row.slim')
24
+ end
25
+ end
26
+
27
+ describe '#template?' do
28
+ it 'asks renderer if there is a valid template for a given identifier' do
29
+ expect(renderer).to receive(:lookup).with('_row').and_return('row.slim')
30
+
31
+ expect(part.template?('row')).to eql('row.slim')
32
+ end
33
+ end
34
+
35
+ describe '#method_missing' do
36
+ it 'renders template' do
37
+ expect(renderer).to receive(:lookup).with('_row').and_return('_row.slim')
38
+ expect(renderer).to receive(:render).with('_row.slim', part)
39
+
40
+ part.row
41
+ end
42
+
43
+ it 'renders template within another when block is passed' do
44
+ block = proc { part.fields }
45
+
46
+ expect(renderer).to receive(:lookup).with('_form').and_return('form.slim')
47
+ expect(renderer).to receive(:lookup).with('_fields').and_return('fields.slim')
48
+
49
+ expect(renderer).to receive(:render).with('form.slim', part, &block)
50
+ expect(renderer).to receive(:render).with('fields.slim', part)
51
+
52
+ part.form(block)
53
+ end
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-view
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: inflecto
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tilt
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-configurable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-equalizer
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '10.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '10.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: capybara
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.5'
125
+ description: Lightweight web application stack on top of Roda
126
+ email:
127
+ - piotr.solnica@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".travis.yml"
135
+ - Gemfile
136
+ - LICENSE.md
137
+ - README.md
138
+ - Rakefile
139
+ - benchmarks/templates/button.erb
140
+ - benchmarks/view.rb
141
+ - dry-view.gemspec
142
+ - lib/dry-view.rb
143
+ - lib/dry/view.rb
144
+ - lib/dry/view/layout.rb
145
+ - lib/dry/view/null_part.rb
146
+ - lib/dry/view/part.rb
147
+ - lib/dry/view/renderer.rb
148
+ - lib/dry/view/value_part.rb
149
+ - lib/dry/view/version.rb
150
+ - spec/fixtures/templates/hello.html.slim
151
+ - spec/fixtures/templates/layouts/app.html.slim
152
+ - spec/fixtures/templates/layouts/app.txt.erb
153
+ - spec/fixtures/templates/shared/_index_table.html.slim
154
+ - spec/fixtures/templates/shared/_shared_hello.html.slim
155
+ - spec/fixtures/templates/tasks.html.slim
156
+ - spec/fixtures/templates/user.html.slim
157
+ - spec/fixtures/templates/users.html.slim
158
+ - spec/fixtures/templates/users.txt.erb
159
+ - spec/fixtures/templates/users/_row.html.slim
160
+ - spec/fixtures/templates/users/_tbody.html.slim
161
+ - spec/integration/view_spec.rb
162
+ - spec/spec_helper.rb
163
+ - spec/unit/layout_spec.rb
164
+ - spec/unit/null_part_spec.rb
165
+ - spec/unit/renderer_spec.rb
166
+ - spec/unit/value_part_spec.rb
167
+ homepage: https://github.com/dry-rb/dry-view
168
+ licenses:
169
+ - MIT
170
+ metadata: {}
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ required_rubygems_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ requirements: []
186
+ rubyforge_project:
187
+ rubygems_version: 2.5.1
188
+ signing_key:
189
+ specification_version: 4
190
+ summary: Lightweight web application stack on top of Roda
191
+ test_files:
192
+ - spec/fixtures/templates/hello.html.slim
193
+ - spec/fixtures/templates/layouts/app.html.slim
194
+ - spec/fixtures/templates/layouts/app.txt.erb
195
+ - spec/fixtures/templates/shared/_index_table.html.slim
196
+ - spec/fixtures/templates/shared/_shared_hello.html.slim
197
+ - spec/fixtures/templates/tasks.html.slim
198
+ - spec/fixtures/templates/user.html.slim
199
+ - spec/fixtures/templates/users.html.slim
200
+ - spec/fixtures/templates/users.txt.erb
201
+ - spec/fixtures/templates/users/_row.html.slim
202
+ - spec/fixtures/templates/users/_tbody.html.slim
203
+ - spec/integration/view_spec.rb
204
+ - spec/spec_helper.rb
205
+ - spec/unit/layout_spec.rb
206
+ - spec/unit/null_part_spec.rb
207
+ - spec/unit/renderer_spec.rb
208
+ - spec/unit/value_part_spec.rb