lotus-view 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +6 -15
- data/.travis.yml +6 -0
- data/.yardopts +3 -0
- data/Gemfile +13 -2
- data/README.md +514 -3
- data/Rakefile +17 -1
- data/lib/lotus/layout.rb +132 -0
- data/lib/lotus/presenter.rb +70 -0
- data/lib/lotus/view/dsl.rb +247 -0
- data/lib/lotus/view/inheritable.rb +50 -0
- data/lib/lotus/view/rendering/layout_finder.rb +104 -0
- data/lib/lotus/view/rendering/layout_registry.rb +63 -0
- data/lib/lotus/view/rendering/layout_scope.rb +138 -0
- data/lib/lotus/view/rendering/null_layout.rb +52 -0
- data/lib/lotus/view/rendering/null_template.rb +79 -0
- data/lib/lotus/view/rendering/partial.rb +29 -0
- data/lib/lotus/view/rendering/partial_finder.rb +41 -0
- data/lib/lotus/view/rendering/registry.rb +129 -0
- data/lib/lotus/view/rendering/scope.rb +48 -0
- data/lib/lotus/view/rendering/template.rb +56 -0
- data/lib/lotus/view/rendering/template_finder.rb +53 -0
- data/lib/lotus/view/rendering/templates_finder.rb +85 -0
- data/lib/lotus/view/rendering/view_finder.rb +37 -0
- data/lib/lotus/view/rendering.rb +265 -0
- data/lib/lotus/view/template.rb +45 -0
- data/lib/lotus/view/version.rb +4 -1
- data/lib/lotus/view.rb +180 -2
- data/lib/lotus-view.rb +1 -0
- data/lotus-view.gemspec +15 -11
- data/test/fixtures/templates/app/app_view.html.erb +0 -0
- data/test/fixtures/templates/app/view.html.erb +0 -0
- data/test/fixtures/templates/application.html.erb +10 -0
- data/test/fixtures/templates/articles/_form.html.erb +4 -0
- data/test/fixtures/templates/articles/alternative_new.html.erb +1 -0
- data/test/fixtures/templates/articles/index.atom.erb +5 -0
- data/test/fixtures/templates/articles/index.html.erb +3 -0
- data/test/fixtures/templates/articles/index.json.erb +9 -0
- data/test/fixtures/templates/articles/index.rss.erb +0 -0
- data/test/fixtures/templates/articles/new.html.erb +7 -0
- data/test/fixtures/templates/articles/show.html.erb +1 -0
- data/test/fixtures/templates/articles/show.json.erb +5 -0
- data/test/fixtures/templates/contacts/show.html.haml +1 -0
- data/test/fixtures/templates/dashboard/index.html.erb +2 -0
- data/test/fixtures/templates/hello_world_view.html.erb +1 -0
- data/test/fixtures/templates/index_view.html.erb +1 -0
- data/test/fixtures/templates/json_render_view.json.erb +3 -0
- data/test/fixtures/templates/render_view.html.erb +1 -0
- data/test/fixtures/templates/shared/_sidebar.html.erb +1 -0
- data/test/fixtures.rb +187 -0
- data/test/layout_test.rb +10 -0
- data/test/load_test.rb +79 -0
- data/test/presenter_test.rb +31 -0
- data/test/rendering_test.rb +125 -0
- data/test/root_test.rb +38 -0
- data/test/test_helper.rb +24 -0
- data/test/version_test.rb +7 -0
- data/test/view_test.rb +27 -0
- metadata +137 -10
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1><%= title %></h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
%h1= person.name
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Hello, World!</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Welcome</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Hello, <%= planet %>!</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
<div id="sidebar"></div>
|
data/test/fixtures.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
class HelloWorldView
|
2
|
+
include Lotus::View
|
3
|
+
end
|
4
|
+
|
5
|
+
class RenderView
|
6
|
+
include Lotus::View
|
7
|
+
end
|
8
|
+
|
9
|
+
class JsonRenderView
|
10
|
+
include Lotus::View
|
11
|
+
format :json
|
12
|
+
end
|
13
|
+
|
14
|
+
class AppView
|
15
|
+
include Lotus::View
|
16
|
+
root __dir__ + '/fixtures/templates/app'
|
17
|
+
layout :application
|
18
|
+
end
|
19
|
+
|
20
|
+
class AppViewLayout < AppView
|
21
|
+
layout nil
|
22
|
+
end
|
23
|
+
|
24
|
+
class MissingTemplateView
|
25
|
+
include Lotus::View
|
26
|
+
end
|
27
|
+
|
28
|
+
module App
|
29
|
+
class View
|
30
|
+
include Lotus::View
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ApplicationLayout
|
35
|
+
include Lotus::Layout
|
36
|
+
|
37
|
+
def title
|
38
|
+
'Title:'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class GlobalLayout
|
43
|
+
end
|
44
|
+
|
45
|
+
module Articles
|
46
|
+
class Index
|
47
|
+
include Lotus::View
|
48
|
+
layout :application
|
49
|
+
|
50
|
+
def title
|
51
|
+
"#{ layout.title } articles"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class RssIndex < Index
|
56
|
+
format :rss
|
57
|
+
layout nil
|
58
|
+
end
|
59
|
+
|
60
|
+
class AtomIndex < RssIndex
|
61
|
+
format :atom
|
62
|
+
layout nil
|
63
|
+
end
|
64
|
+
|
65
|
+
class New
|
66
|
+
include Lotus::View
|
67
|
+
|
68
|
+
def errors
|
69
|
+
{}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class AlternativeNew
|
74
|
+
include Lotus::View
|
75
|
+
end
|
76
|
+
|
77
|
+
class Create
|
78
|
+
include Lotus::View
|
79
|
+
template 'articles/new'
|
80
|
+
|
81
|
+
def errors
|
82
|
+
{title: 'Title is required'}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Show
|
87
|
+
include Lotus::View
|
88
|
+
|
89
|
+
def title
|
90
|
+
@title ||= article.title.upcase
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class JsonShow < Show
|
95
|
+
format :json
|
96
|
+
|
97
|
+
def article
|
98
|
+
OpenStruct.new(title: locals[:article].title.reverse)
|
99
|
+
end
|
100
|
+
|
101
|
+
def title
|
102
|
+
super.downcase
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Map
|
108
|
+
attr_reader :locations
|
109
|
+
|
110
|
+
def initialize(locations)
|
111
|
+
@locations = locations
|
112
|
+
end
|
113
|
+
|
114
|
+
def location_names
|
115
|
+
@locations.join(', ')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class MapPresenter
|
120
|
+
include Lotus::Presenter
|
121
|
+
|
122
|
+
def count
|
123
|
+
locations.count
|
124
|
+
end
|
125
|
+
|
126
|
+
def location_names
|
127
|
+
super.upcase
|
128
|
+
end
|
129
|
+
|
130
|
+
def inspect_object
|
131
|
+
@object.inspect
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
module Dashboard
|
136
|
+
class Index
|
137
|
+
include Lotus::View
|
138
|
+
|
139
|
+
def map
|
140
|
+
MapPresenter.new(locals[:map])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class IndexView
|
146
|
+
include Lotus::View
|
147
|
+
layout :application
|
148
|
+
end
|
149
|
+
|
150
|
+
class SongWidget
|
151
|
+
attr_reader :song
|
152
|
+
|
153
|
+
def initialize(song)
|
154
|
+
@song = song
|
155
|
+
end
|
156
|
+
|
157
|
+
def render
|
158
|
+
%(<audio src="#{ song.url }">#{ song.title }</audio>)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
module Songs
|
163
|
+
class Show
|
164
|
+
include Lotus::View
|
165
|
+
format :html
|
166
|
+
|
167
|
+
def render
|
168
|
+
SongWidget.new(song).render
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
module Metrics
|
174
|
+
class Index
|
175
|
+
include Lotus::View
|
176
|
+
|
177
|
+
def render
|
178
|
+
%(metrics)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
module Contacts
|
184
|
+
class Show
|
185
|
+
include Lotus::View
|
186
|
+
end
|
187
|
+
end
|
data/test/layout_test.rb
ADDED
data/test/load_test.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Lotus::View do
|
4
|
+
describe '.load!' do
|
5
|
+
before do
|
6
|
+
# Lotus::View.load! is invoked as last statement of `test/test_helper.rb`.
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'freezes .root' do
|
10
|
+
Lotus::View.root.frozen?.must_equal true
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'freezes .root for all the views' do
|
14
|
+
AppView.root.frozen?.must_equal true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'freezes .layout for all the views' do
|
18
|
+
AppView.layout.frozen?.must_equal true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'freezes .layout for subclasses' do
|
22
|
+
AppViewLayout.layout.frozen?.must_equal true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'freezes .views' do
|
26
|
+
Lotus::View.views.frozen?.must_equal true
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'freezes .format for all the views with that declaration' do
|
30
|
+
JsonRenderView.format.frozen?.must_equal true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'freezes .format for subclasses' do
|
34
|
+
Articles::RssIndex.format.frozen?.must_equal true
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'freezes .template' do
|
38
|
+
Articles::Show.template.frozen?.must_equal true
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'freezes .template for subclasses' do
|
42
|
+
Articles::JsonShow.template.frozen?.must_equal true
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'freezes .subclasses' do
|
46
|
+
Articles::Index.subclasses.frozen?.must_equal true
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'freezes .subclasses for subclasses' do
|
50
|
+
Articles::AtomIndex.subclasses.frozen?.must_equal true
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'freezes view .views' do
|
54
|
+
Articles::Index.send(:views).frozen?.must_equal true
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'freezes .views for subclasses' do
|
58
|
+
Articles::RssIndex.send(:views).frozen?.must_equal true
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'freezes .registry' do
|
62
|
+
Articles::Index.send(:registry).frozen?.must_equal true
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'freezes .registry for subclasses' do
|
66
|
+
Articles::AtomIndex.send(:registry).frozen?.must_equal true
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'layouts' do
|
70
|
+
it 'freezes .root' do
|
71
|
+
ApplicationLayout.root.frozen?.must_equal true
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'freezes .registry' do
|
75
|
+
ApplicationLayout.send(:registry).frozen?.must_equal true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Lotus::Presenter do
|
4
|
+
subject do
|
5
|
+
MapPresenter.new(map)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:map) { Map.new(['Rome']) }
|
9
|
+
|
10
|
+
it 'forwards methods to the wrapped object' do
|
11
|
+
subject.locations.must_equal map.locations
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'uses concrete methods' do
|
15
|
+
subject.count.must_equal map.locations.count
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'uses super to access object implementation' do
|
19
|
+
subject.location_names.must_equal map.locations.map {|l| l.upcase }.join(', ')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'has a direct access to the object' do
|
23
|
+
subject.inspect_object.must_match '#<Map'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises error when the requested method can't be satisfied" do
|
27
|
+
-> {
|
28
|
+
subject.unknown_method
|
29
|
+
}.must_raise NoMethodError
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Lotus::View do
|
5
|
+
describe 'rendering' do
|
6
|
+
it 'renders a template' do
|
7
|
+
HelloWorldView.render(format: :html).must_include %(<h1>Hello, World!</h1>)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'renders a template with context binding' do
|
11
|
+
RenderView.render(format: :html, planet: 'Mars').must_include %(<h1>Hello, Mars!</h1>)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'renders a template according to the declared format' do
|
15
|
+
JsonRenderView.render(format: :json, planet: 'Moon').must_include %("greet":"Hello, Moon!")
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'renders a template according to the requested format' do
|
19
|
+
articles = [ OpenStruct.new(title: 'Man on the Moon!') ]
|
20
|
+
|
21
|
+
rendered = Articles::Index.render(format: :json, articles: articles)
|
22
|
+
rendered.must_match %("title":"Man on the Moon!")
|
23
|
+
|
24
|
+
rendered = Articles::Index.render(format: :html, articles: articles)
|
25
|
+
rendered.must_match %(<h1>Man on the Moon!</h1>)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'binds given locals to the rendering context' do
|
29
|
+
article = OpenStruct.new(title: 'Hello')
|
30
|
+
|
31
|
+
rendered = Articles::Show.render(format: :html, article: article)
|
32
|
+
rendered.must_match %(<h1>HELLO</h1>)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'renders a template from a subclass, if it is able to handle the requested format' do
|
36
|
+
article = OpenStruct.new(title: 'Hello')
|
37
|
+
|
38
|
+
rendered = Articles::Show.render(format: :json, article: article)
|
39
|
+
rendered.must_match %("title":"olleh")
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises an error when the template is missing' do
|
43
|
+
article = OpenStruct.new(title: 'Ciao')
|
44
|
+
|
45
|
+
-> {
|
46
|
+
Articles::Show.render(format: :png, article: article)
|
47
|
+
}.must_raise(Lotus::View::MissingTemplateError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'raises an error when the format is missing' do
|
51
|
+
-> {
|
52
|
+
HelloWorldView.render({})
|
53
|
+
}.must_raise(Lotus::View::MissingFormatError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'renders different template, as specified by DSL' do
|
57
|
+
article = OpenStruct.new(title: 'Bonjour')
|
58
|
+
|
59
|
+
rendered = Articles::Create.render(format: :html, article: article)
|
60
|
+
rendered.must_match %(<h1>New Article</h1>)
|
61
|
+
rendered.must_match %(<h2>Errors</h2>)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'decorates locals' do
|
65
|
+
map = Map.new(['Rome', 'Cambridge'])
|
66
|
+
|
67
|
+
rendered = Dashboard::Index.render(format: :html, map: map)
|
68
|
+
rendered.must_match %(<h1>Map</h1>)
|
69
|
+
rendered.must_match %(<h2>2 locations</h2>)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'renders a partial' do
|
73
|
+
article = OpenStruct.new(title: nil)
|
74
|
+
|
75
|
+
rendered = Articles::New.render(format: :html, article: article)
|
76
|
+
|
77
|
+
rendered.must_match %(<h1>New Article</h1>)
|
78
|
+
rendered.must_match %(<input type="hidden" name="secret" value="23" />)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'renders a template within another template' do
|
82
|
+
article = OpenStruct.new(title: nil)
|
83
|
+
|
84
|
+
rendered = Articles::AlternativeNew.render(format: :html, article: article)
|
85
|
+
|
86
|
+
rendered.must_match %(<h1>New Article</h1>)
|
87
|
+
rendered.must_match %(<input type="hidden" name="secret" value="23" />)
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'uses HAML engine' do
|
91
|
+
person = OpenStruct.new(name: 'Luca')
|
92
|
+
|
93
|
+
rendered = Contacts::Show.render(format: :html, person: person)
|
94
|
+
rendered.must_match %(<h1>Luca</h1>)
|
95
|
+
end
|
96
|
+
|
97
|
+
describe 'when without a template' do
|
98
|
+
it 'renders from the custom rendering method' do
|
99
|
+
song = OpenStruct.new(title: 'Song Two', url: '/song2.mp3')
|
100
|
+
|
101
|
+
rendered = Songs::Show.render(format: :html, song: song)
|
102
|
+
rendered.must_equal %(<audio src="/song2.mp3">Song Two</audio>)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'respond to all the formats' do
|
106
|
+
rendered = Metrics::Index.render(format: :html)
|
107
|
+
rendered.must_equal %(metrics)
|
108
|
+
|
109
|
+
rendered = Metrics::Index.render(format: :json)
|
110
|
+
rendered.must_equal %(metrics)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe 'layout' do
|
115
|
+
it 'renders contents from layout' do
|
116
|
+
articles = [ OpenStruct.new(title: 'A Wonderful Day!') ]
|
117
|
+
|
118
|
+
rendered = Articles::Index.render(format: :html, articles: articles)
|
119
|
+
rendered.must_match %(<h1>A Wonderful Day!</h1>)
|
120
|
+
rendered.must_match %(<html>)
|
121
|
+
rendered.must_match %(<title>Title: articles</title>)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/test/root_test.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Lotus::View do
|
4
|
+
describe 'root' do
|
5
|
+
before do
|
6
|
+
Lotus::View.root = Pathname.new __dir__ + '/fixtures/templates'
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'exposes the path where to lookup for templates' do
|
10
|
+
expected = Pathname.new __dir__ + '/fixtures/templates'
|
11
|
+
Lotus::View.root.must_equal expected
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'is inherited' do
|
15
|
+
HelloWorldView.root.must_equal Lotus::View.root
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can be customized for each view' do
|
19
|
+
expected = Pathname.new __dir__ + '/fixtures/templates/app'
|
20
|
+
AppView.root.must_equal expected
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'when not set' do
|
24
|
+
before do
|
25
|
+
@root = Lotus::View.root
|
26
|
+
Lotus::View.root = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
Lotus::View.root = @root
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'sets the current directory as root' do
|
34
|
+
Lotus::View.root.must_equal Pathname.new('.')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
if ENV['COVERAGE'] == 'true'
|
5
|
+
require 'simplecov'
|
6
|
+
require 'coveralls'
|
7
|
+
|
8
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
9
|
+
SimpleCov::Formatter::HTMLFormatter,
|
10
|
+
Coveralls::SimpleCov::Formatter
|
11
|
+
]
|
12
|
+
|
13
|
+
SimpleCov.start do
|
14
|
+
command_name 'test'
|
15
|
+
add_filter 'test'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'minitest/autorun'
|
20
|
+
$:.unshift 'lib'
|
21
|
+
require 'lotus/view'
|
22
|
+
Lotus::View.root = Pathname.new __dir__ + '/fixtures/templates'
|
23
|
+
require 'fixtures'
|
24
|
+
Lotus::View.load!
|
data/test/view_test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Lotus::View do
|
4
|
+
describe '.layout=' do
|
5
|
+
before do
|
6
|
+
Lotus::View.unload!
|
7
|
+
|
8
|
+
class ViewWithInheritedLayout
|
9
|
+
include Lotus::View
|
10
|
+
end
|
11
|
+
|
12
|
+
Lotus::View.layout = :application
|
13
|
+
Lotus::View.load!
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
Lotus::View.unload!
|
18
|
+
Object.send(:remove_const, :ViewWithInheritedLayout)
|
19
|
+
Lotus::View.layout = nil
|
20
|
+
Lotus::View.load!
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'sets global layout' do
|
24
|
+
ViewWithInheritedLayout.layout.must_equal ApplicationLayout
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|