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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -15
  3. data/.travis.yml +6 -0
  4. data/.yardopts +3 -0
  5. data/Gemfile +13 -2
  6. data/README.md +514 -3
  7. data/Rakefile +17 -1
  8. data/lib/lotus/layout.rb +132 -0
  9. data/lib/lotus/presenter.rb +70 -0
  10. data/lib/lotus/view/dsl.rb +247 -0
  11. data/lib/lotus/view/inheritable.rb +50 -0
  12. data/lib/lotus/view/rendering/layout_finder.rb +104 -0
  13. data/lib/lotus/view/rendering/layout_registry.rb +63 -0
  14. data/lib/lotus/view/rendering/layout_scope.rb +138 -0
  15. data/lib/lotus/view/rendering/null_layout.rb +52 -0
  16. data/lib/lotus/view/rendering/null_template.rb +79 -0
  17. data/lib/lotus/view/rendering/partial.rb +29 -0
  18. data/lib/lotus/view/rendering/partial_finder.rb +41 -0
  19. data/lib/lotus/view/rendering/registry.rb +129 -0
  20. data/lib/lotus/view/rendering/scope.rb +48 -0
  21. data/lib/lotus/view/rendering/template.rb +56 -0
  22. data/lib/lotus/view/rendering/template_finder.rb +53 -0
  23. data/lib/lotus/view/rendering/templates_finder.rb +85 -0
  24. data/lib/lotus/view/rendering/view_finder.rb +37 -0
  25. data/lib/lotus/view/rendering.rb +265 -0
  26. data/lib/lotus/view/template.rb +45 -0
  27. data/lib/lotus/view/version.rb +4 -1
  28. data/lib/lotus/view.rb +180 -2
  29. data/lib/lotus-view.rb +1 -0
  30. data/lotus-view.gemspec +15 -11
  31. data/test/fixtures/templates/app/app_view.html.erb +0 -0
  32. data/test/fixtures/templates/app/view.html.erb +0 -0
  33. data/test/fixtures/templates/application.html.erb +10 -0
  34. data/test/fixtures/templates/articles/_form.html.erb +4 -0
  35. data/test/fixtures/templates/articles/alternative_new.html.erb +1 -0
  36. data/test/fixtures/templates/articles/index.atom.erb +5 -0
  37. data/test/fixtures/templates/articles/index.html.erb +3 -0
  38. data/test/fixtures/templates/articles/index.json.erb +9 -0
  39. data/test/fixtures/templates/articles/index.rss.erb +0 -0
  40. data/test/fixtures/templates/articles/new.html.erb +7 -0
  41. data/test/fixtures/templates/articles/show.html.erb +1 -0
  42. data/test/fixtures/templates/articles/show.json.erb +5 -0
  43. data/test/fixtures/templates/contacts/show.html.haml +1 -0
  44. data/test/fixtures/templates/dashboard/index.html.erb +2 -0
  45. data/test/fixtures/templates/hello_world_view.html.erb +1 -0
  46. data/test/fixtures/templates/index_view.html.erb +1 -0
  47. data/test/fixtures/templates/json_render_view.json.erb +3 -0
  48. data/test/fixtures/templates/render_view.html.erb +1 -0
  49. data/test/fixtures/templates/shared/_sidebar.html.erb +1 -0
  50. data/test/fixtures.rb +187 -0
  51. data/test/layout_test.rb +10 -0
  52. data/test/load_test.rb +79 -0
  53. data/test/presenter_test.rb +31 -0
  54. data/test/rendering_test.rb +125 -0
  55. data/test/root_test.rb +38 -0
  56. data/test/test_helper.rb +24 -0
  57. data/test/version_test.rb +7 -0
  58. data/test/view_test.rb +27 -0
  59. metadata +137 -10
@@ -0,0 +1,3 @@
1
+ <% articles.each do |article| %>
2
+ <h1><%= article.title %></h1>
3
+ <% end %>
@@ -0,0 +1,9 @@
1
+ {
2
+ "articles": [
3
+ <% articles.each do |article| %>
4
+ "article":{
5
+ "title":"<%= article.title %>"
6
+ }
7
+ <% end %>
8
+ ]
9
+ }
File without changes
@@ -0,0 +1,7 @@
1
+ <h1>New Article</h1>
2
+
3
+ <% if errors.any? %>
4
+ <h2>Errors</h2>
5
+ <% end %>
6
+
7
+ <%= render partial: 'articles/form', locals: { secret: 23 } %>
@@ -0,0 +1 @@
1
+ <h1><%= title %></h1>
@@ -0,0 +1,5 @@
1
+ {
2
+ article: {
3
+ "title":"<%= title %>"
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ %h1= person.name
@@ -0,0 +1,2 @@
1
+ <h1>Map</h1>
2
+ <h2><%= map.count %> locations</h2>
@@ -0,0 +1 @@
1
+ <h1>Hello, World!</h1>
@@ -0,0 +1 @@
1
+ <h1>Welcome</h1>
@@ -0,0 +1,3 @@
1
+ {
2
+ "greet":"Hello, <%= planet %>!"
3
+ }
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Layout do
4
+ describe 'rendering from layout' do
5
+ it 'renders partial' do
6
+ rendered = IndexView.render(format: :html)
7
+ rendered.must_match %(<div id="sidebar"></div>)
8
+ end
9
+ end
10
+ end
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
@@ -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!
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::View::VERSION do
4
+ it 'returns current version' do
5
+ Lotus::View::VERSION.must_equal '0.1.0'
6
+ end
7
+ end
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