dry-view 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +15 -11
- data/CHANGELOG.md +23 -0
- data/Gemfile +6 -5
- data/README.md +8 -1
- data/benchmarks/templates/{button.erb → button.html.erb} +0 -0
- data/benchmarks/view.rb +3 -4
- data/bin/console +7 -0
- data/dry-view.gemspec +7 -6
- data/lib/dry/view/controller.rb +107 -0
- data/lib/dry/view/exposure.rb +61 -0
- data/lib/dry/view/exposures.rb +50 -0
- data/lib/dry/view/path.rb +40 -0
- data/lib/dry/view/renderer.rb +20 -28
- data/lib/dry/view/scope.rb +55 -0
- data/lib/dry/view/version.rb +1 -1
- data/lib/dry/view.rb +1 -1
- data/spec/fixtures/templates/empty.html.slim +1 -0
- data/spec/fixtures/templates/layouts/app.html.slim +1 -1
- data/spec/fixtures/templates/layouts/app.txt.erb +1 -1
- data/spec/fixtures/templates/parts_with_args/_box.html.slim +3 -0
- data/spec/fixtures/templates/parts_with_args.html.slim +3 -0
- data/spec/fixtures/templates/users/_tbody.html.slim +1 -1
- data/spec/fixtures/templates/users.html.slim +4 -4
- data/spec/fixtures/templates/users.txt.erb +0 -2
- data/spec/fixtures/templates/users_with_count.html.slim +5 -0
- data/spec/fixtures/templates_override/users.html.slim +5 -0
- data/spec/integration/exposures_spec.rb +178 -0
- data/spec/integration/view_spec.rb +83 -20
- data/spec/spec_helper.rb +13 -3
- data/spec/unit/controller_spec.rb +36 -0
- data/spec/unit/exposure_spec.rb +146 -0
- data/spec/unit/exposures_spec.rb +63 -0
- data/spec/unit/renderer_spec.rb +2 -1
- data/spec/unit/scope_spec.rb +98 -0
- metadata +36 -46
- data/lib/dry/view/layout.rb +0 -126
- data/lib/dry/view/null_part.rb +0 -30
- data/lib/dry/view/part.rb +0 -39
- data/lib/dry/view/value_part.rb +0 -50
- data/spec/unit/layout_spec.rb +0 -55
- data/spec/unit/null_part_spec.rb +0 -39
- data/spec/unit/value_part_spec.rb +0 -55
@@ -0,0 +1,178 @@
|
|
1
|
+
RSpec.describe 'exposures' do
|
2
|
+
let(:context) { Struct.new(:title, :assets).new('dry-view rocks!', -> input { "#{input}.jpg" }) }
|
3
|
+
|
4
|
+
it 'uses exposures to build view locals' do
|
5
|
+
vc = Class.new(Dry::View::Controller) do
|
6
|
+
configure do |config|
|
7
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
8
|
+
config.layout = 'app'
|
9
|
+
config.template = 'users'
|
10
|
+
config.default_format = :html
|
11
|
+
end
|
12
|
+
|
13
|
+
expose :users do |input|
|
14
|
+
input.fetch(:users).map { |user|
|
15
|
+
user.merge(name: user[:name].upcase)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end.new
|
19
|
+
|
20
|
+
users = [
|
21
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
22
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
23
|
+
]
|
24
|
+
|
25
|
+
expect(vc.(users: users, context: context)).to eql(
|
26
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><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><img src="mindblown.jpg" /></body></html>'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'supports both blocks and instance methods as exposures' do
|
31
|
+
vc = Class.new(Dry::View::Controller) do
|
32
|
+
configure do |config|
|
33
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
34
|
+
config.layout = 'app'
|
35
|
+
config.template = 'users'
|
36
|
+
config.default_format = :html
|
37
|
+
end
|
38
|
+
|
39
|
+
expose :users
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def users(input)
|
44
|
+
input.fetch(:users).map { |user|
|
45
|
+
user.merge(name: user[:name].upcase)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end.new
|
49
|
+
|
50
|
+
users = [
|
51
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
52
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
53
|
+
]
|
54
|
+
|
55
|
+
expect(vc.(users: users, context: context)).to eql(
|
56
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><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><img src="mindblown.jpg" /></body></html>'
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'passes matching input data if no proc or instance method is available' do
|
61
|
+
vc = Class.new(Dry::View::Controller) do
|
62
|
+
configure do |config|
|
63
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
64
|
+
config.layout = 'app'
|
65
|
+
config.template = 'users'
|
66
|
+
config.default_format = :html
|
67
|
+
end
|
68
|
+
|
69
|
+
expose :users
|
70
|
+
end.new
|
71
|
+
|
72
|
+
users = [
|
73
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
74
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
75
|
+
]
|
76
|
+
|
77
|
+
expect(vc.(users: users, context: context)).to eql(
|
78
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><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><img src="mindblown.jpg" /></body></html>'
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'allows exposures to depend on each other' do
|
83
|
+
vc = Class.new(Dry::View::Controller) do
|
84
|
+
configure do |config|
|
85
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
86
|
+
config.layout = 'app'
|
87
|
+
config.template = 'users_with_count'
|
88
|
+
config.default_format = :html
|
89
|
+
end
|
90
|
+
|
91
|
+
expose :users do |input|
|
92
|
+
input.fetch(:users)
|
93
|
+
end
|
94
|
+
|
95
|
+
expose :users_count do |users|
|
96
|
+
"#{users.length} users"
|
97
|
+
end
|
98
|
+
end.new
|
99
|
+
|
100
|
+
users = [
|
101
|
+
{name: 'Jane', email: 'jane@doe.org'},
|
102
|
+
{name: 'Joe', email: 'joe@doe.org'}
|
103
|
+
]
|
104
|
+
|
105
|
+
expect(vc.(users: users, context: context)).to eql(
|
106
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><ul><li>Jane (jane@doe.org)</li><li>Joe (joe@doe.org)</li></ul><div class="count">2 users</div></body></html>'
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'supports defining multiple exposures at once' do
|
111
|
+
vc = Class.new(Dry::View::Controller) do
|
112
|
+
configure do |config|
|
113
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
114
|
+
config.layout = 'app'
|
115
|
+
config.template = 'users_with_count'
|
116
|
+
config.default_format = :html
|
117
|
+
end
|
118
|
+
|
119
|
+
expose :users, :users_count
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def users(input)
|
124
|
+
input.fetch(:users)
|
125
|
+
end
|
126
|
+
|
127
|
+
def users_count(users)
|
128
|
+
"#{users.length} users"
|
129
|
+
end
|
130
|
+
end.new
|
131
|
+
|
132
|
+
users = [
|
133
|
+
{name: 'Jane', email: 'jane@doe.org'},
|
134
|
+
{name: 'Joe', email: 'joe@doe.org'}
|
135
|
+
]
|
136
|
+
|
137
|
+
expect(vc.(users: users, context: context)).to eql(
|
138
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><ul><li>Jane (jane@doe.org)</li><li>Joe (joe@doe.org)</li></ul><div class="count">2 users</div></body></html>'
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'allows exposures to be hidden from the view' do
|
143
|
+
vc = Class.new(Dry::View::Controller) do
|
144
|
+
configure do |config|
|
145
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
146
|
+
config.layout = 'app'
|
147
|
+
config.template = 'users_with_count'
|
148
|
+
config.default_format = :html
|
149
|
+
end
|
150
|
+
|
151
|
+
private_expose :prefix do
|
152
|
+
"COUNT: "
|
153
|
+
end
|
154
|
+
|
155
|
+
expose :users do |input|
|
156
|
+
input.fetch(:users)
|
157
|
+
end
|
158
|
+
|
159
|
+
expose :users_count do |prefix, users|
|
160
|
+
"#{prefix}#{users.length} users"
|
161
|
+
end
|
162
|
+
end.new
|
163
|
+
|
164
|
+
users = [
|
165
|
+
{name: 'Jane', email: 'jane@doe.org'},
|
166
|
+
{name: 'Joe', email: 'joe@doe.org'}
|
167
|
+
]
|
168
|
+
|
169
|
+
input = {users: users, context: context}
|
170
|
+
|
171
|
+
expect(vc.(input)).to eql(
|
172
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><ul><li>Jane (jane@doe.org)</li><li>Joe (joe@doe.org)</li></ul><div class="count">COUNT: 2 users</div></body></html>'
|
173
|
+
)
|
174
|
+
|
175
|
+
expect(vc.locals(input)).to include(:users, :users_count)
|
176
|
+
expect(vc.locals(input)).not_to include(:prefix)
|
177
|
+
end
|
178
|
+
end
|
@@ -1,51 +1,114 @@
|
|
1
1
|
RSpec.describe 'dry-view' do
|
2
|
-
let(:
|
3
|
-
Class.new(Dry::View::
|
2
|
+
let(:vc_class) do
|
3
|
+
Class.new(Dry::View::Controller) do
|
4
4
|
configure do |config|
|
5
|
-
config.
|
6
|
-
config.
|
5
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
6
|
+
config.layout = 'app'
|
7
7
|
config.template = 'users'
|
8
|
-
config.
|
8
|
+
config.default_format = :html
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
let(:
|
14
|
-
Struct.new(:title).new('dry-view rocks!')
|
13
|
+
let(:context) do
|
14
|
+
Struct.new(:title, :assets).new('dry-view rocks!', -> input { "#{input}.jpg" })
|
15
15
|
end
|
16
16
|
|
17
|
-
it 'renders within a layout
|
18
|
-
|
17
|
+
it 'renders within a layout and makes the provided context available everywhere' do
|
18
|
+
vc = vc_class.new
|
19
19
|
|
20
20
|
users = [
|
21
21
|
{ name: 'Jane', email: 'jane@doe.org' },
|
22
22
|
{ name: 'Joe', email: 'joe@doe.org' }
|
23
23
|
]
|
24
24
|
|
25
|
-
expect(
|
26
|
-
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><
|
25
|
+
expect(vc.(context: context, locals: {users: users})).to eql(
|
26
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><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><img src="mindblown.jpg" /></body></html>'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'renders without a layout' do
|
31
|
+
vc = Class.new(vc_class) do
|
32
|
+
configure do |config|
|
33
|
+
config.layout = false
|
34
|
+
end
|
35
|
+
end.new
|
36
|
+
|
37
|
+
users = [
|
38
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
39
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
40
|
+
]
|
41
|
+
|
42
|
+
expect(vc.(context: context, locals: {users: users})).to eql(
|
43
|
+
'<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><img src="mindblown.jpg" />'
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'renders a view without locals' do
|
48
|
+
vc = Class.new(vc_class) do
|
49
|
+
configure do |config|
|
50
|
+
config.template = 'empty'
|
51
|
+
end
|
52
|
+
end.new
|
53
|
+
|
54
|
+
expect(vc.(context: context, locals: {})).to eq(
|
55
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><p>This is a view with no locals.</p></body></html>'
|
27
56
|
)
|
28
57
|
end
|
29
58
|
|
30
59
|
it 'renders a view with an alternative format and engine' do
|
31
|
-
|
60
|
+
vc = vc_class.new
|
61
|
+
|
62
|
+
users = [
|
63
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
64
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
65
|
+
]
|
66
|
+
|
67
|
+
expect(vc.(context: context, locals: {users: users}, format: 'txt').strip).to eql(
|
68
|
+
"# dry-view rocks!\n\n* Jane (jane@doe.org)\n* Joe (joe@doe.org)"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'renders a view with a template on another view path' do
|
73
|
+
vc = Class.new(vc_class) do
|
74
|
+
configure do |config|
|
75
|
+
config.paths = [SPEC_ROOT.join('fixtures/templates_override')] + Array(config.paths)
|
76
|
+
end
|
77
|
+
end.new
|
78
|
+
|
79
|
+
users = [
|
80
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
81
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
82
|
+
]
|
83
|
+
|
84
|
+
expect(vc.(context: context, locals: {users: users})).to eq(
|
85
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><h1>OVERRIDE</h1><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>'
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'renders a view that passes arguments to partials' do
|
90
|
+
vc = Class.new(vc_class) do
|
91
|
+
configure do |config|
|
92
|
+
config.template = 'parts_with_args'
|
93
|
+
end
|
94
|
+
end.new
|
32
95
|
|
33
96
|
users = [
|
34
97
|
{ name: 'Jane', email: 'jane@doe.org' },
|
35
98
|
{ name: 'Joe', email: 'joe@doe.org' }
|
36
99
|
]
|
37
100
|
|
38
|
-
expect(
|
39
|
-
|
101
|
+
expect(vc.(context: context, locals: {users: users})).to eq(
|
102
|
+
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><div class="users"><div class="box"><h2>Nombre</h2>Jane</div><div class="box"><h2>Nombre</h2>Joe</div></div></body></html>'
|
40
103
|
)
|
41
104
|
end
|
42
105
|
|
43
106
|
describe 'inheritance' do
|
44
107
|
let(:parent_view) do
|
45
|
-
klass = Class.new(Dry::View::
|
108
|
+
klass = Class.new(Dry::View::Controller)
|
46
109
|
|
47
|
-
klass.setting :
|
48
|
-
klass.setting :
|
110
|
+
klass.setting :paths, SPEC_ROOT.join('fixtures/templates')
|
111
|
+
klass.setting :layout, 'app'
|
49
112
|
klass.setting :formats, {html: :slim}
|
50
113
|
|
51
114
|
klass
|
@@ -59,10 +122,10 @@ RSpec.describe 'dry-view' do
|
|
59
122
|
end
|
60
123
|
end
|
61
124
|
|
62
|
-
it 'renders within a parent class layout using provided
|
63
|
-
|
125
|
+
it 'renders within a parent class layout using provided context' do
|
126
|
+
vc = child_view.new
|
64
127
|
|
65
|
-
expect(
|
128
|
+
expect(vc.(context: context, locals: { tasks: [{ title: 'one' }, { title: 'two' }] })).to eql(
|
66
129
|
'<!DOCTYPE html><html><head><title>dry-view rocks!</title></head><body><ol><li>one</li><li>two</li></ol></body></html>'
|
67
130
|
)
|
68
131
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
if RUBY_ENGINE ==
|
2
|
-
require
|
3
|
-
|
1
|
+
if RUBY_ENGINE == 'ruby'
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "/spec/"
|
5
|
+
end
|
4
6
|
end
|
5
7
|
|
6
8
|
begin
|
@@ -24,3 +26,11 @@ RSpec.configure do |config|
|
|
24
26
|
config.order = :random
|
25
27
|
Kernel.srand config.seed
|
26
28
|
end
|
29
|
+
|
30
|
+
RSpec::Matchers.define :part_including do |data|
|
31
|
+
match { |actual|
|
32
|
+
data.all? { |(key, val)|
|
33
|
+
actual._data[key] == val
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
RSpec.describe Dry::View::Controller do
|
2
|
+
subject(:layout) { layout_class.new }
|
3
|
+
|
4
|
+
let(:layout_class) do
|
5
|
+
klass = Class.new(Dry::View::Controller)
|
6
|
+
|
7
|
+
klass.configure do |config|
|
8
|
+
config.paths = SPEC_ROOT.join('fixtures/templates')
|
9
|
+
config.layout = 'app'
|
10
|
+
config.template = 'user'
|
11
|
+
config.default_format = :html
|
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
|
+
{ context: 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
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
RSpec.describe Dry::View::Exposure do
|
2
|
+
subject(:exposure) { described_class.new(:hello, proc) }
|
3
|
+
|
4
|
+
let(:proc) { -> input { "hi" } }
|
5
|
+
|
6
|
+
describe "initialization and attributes" do
|
7
|
+
describe "#name" do
|
8
|
+
it "accepts a name" do
|
9
|
+
expect(exposure.name).to eql :hello
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#proc" do
|
14
|
+
it "accepts a proc" do
|
15
|
+
expect(exposure.proc).to eql proc
|
16
|
+
end
|
17
|
+
|
18
|
+
it "allows a nil proc" do
|
19
|
+
expect(described_class.new(:hello).proc).to be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allows proc to take no arguments" do
|
23
|
+
proc = -> { "hi" }
|
24
|
+
expect { described_class.new(:hello, proc) }.not_to raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it "requires proc to take positional arguments only" do
|
28
|
+
proc = -> a: "a" { "hi" }
|
29
|
+
expect { described_class.new(:hello, proc) }.to raise_error ArgumentError
|
30
|
+
|
31
|
+
proc = -> input, a: "a" { "hi" }
|
32
|
+
expect { described_class.new(:hello, proc) }.to raise_error ArgumentError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#to_view" do
|
37
|
+
it "is true by default" do
|
38
|
+
expect(exposure.to_view).to be true
|
39
|
+
end
|
40
|
+
|
41
|
+
it "can be set to false on initialization" do
|
42
|
+
expect(described_class.new(:hello, to_view: false).to_view).to be false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#bind" do
|
48
|
+
context "proc provided" do
|
49
|
+
subject(:bound_exposure) { exposure.bind(Object.new) }
|
50
|
+
|
51
|
+
it "returns itself" do
|
52
|
+
expect(bound_exposure).to eql exposure
|
53
|
+
end
|
54
|
+
|
55
|
+
it "retains the same proc" do
|
56
|
+
expect(bound_exposure.proc).to eql proc
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "no proc provided" do
|
61
|
+
subject(:bound_exposure) { exposure.bind(object) }
|
62
|
+
|
63
|
+
let(:exposure) { described_class.new(:hello) }
|
64
|
+
|
65
|
+
context "matching instance method" do
|
66
|
+
let(:object) do
|
67
|
+
Class.new do
|
68
|
+
def hello(input)
|
69
|
+
"hi there, #{input.fetch(:name)}"
|
70
|
+
end
|
71
|
+
end.new
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns a new object" do
|
75
|
+
expect(bound_exposure).not_to eql exposure
|
76
|
+
end
|
77
|
+
|
78
|
+
it "sets the proc to the method on the object matching the exposure's name" do
|
79
|
+
expect(bound_exposure.proc).to eql object.method(:hello)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "no matching instance method" do
|
84
|
+
let(:object) { Object.new }
|
85
|
+
|
86
|
+
it "returns a new object" do
|
87
|
+
expect(bound_exposure).not_to eql exposure
|
88
|
+
end
|
89
|
+
|
90
|
+
it "builds a proc that passes through data from a matching key in the input" do
|
91
|
+
expect(bound_exposure.proc.(hello: "hello in input")).to eq "hello in input"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#dependencies" do
|
98
|
+
let(:proc) { -> input, foo, bar { "hi" } }
|
99
|
+
|
100
|
+
it "returns an array of exposure dependencies derived from the proc's argument names" do
|
101
|
+
expect(exposure.dependencies).to eql [:input, :foo, :bar]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#call" do
|
106
|
+
let(:input) { double("input") }
|
107
|
+
|
108
|
+
before do
|
109
|
+
allow(proc).to receive(:call)
|
110
|
+
end
|
111
|
+
|
112
|
+
context "proc expects input only" do
|
113
|
+
it "sends the input to the proc" do
|
114
|
+
exposure.(input)
|
115
|
+
|
116
|
+
expect(proc).to have_received(:call).with(input)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "proc expects input and dependencies" do
|
121
|
+
let(:proc) { -> input, greeting { "#{greeting}, #{input.fetch(:name)}" } }
|
122
|
+
let(:locals) { {greeting: "Hola"} }
|
123
|
+
|
124
|
+
before do
|
125
|
+
exposure.(input, locals)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "sends the input and dependency values to the proc" do
|
129
|
+
expect(proc).to have_received(:call).with(input, "Hola")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "proc expects dependencies only" do
|
134
|
+
let(:proc) { -> greeting, farewell { "#{greeting}, #{input.fetch(:name)}" } }
|
135
|
+
let(:locals) { {greeting: "Hola", farewell: "Adios"} }
|
136
|
+
|
137
|
+
before do
|
138
|
+
exposure.(input, locals)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "sends the dependency values to the proc" do
|
142
|
+
expect(proc).to have_received(:call).with "Hola", "Adios"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
RSpec.describe Dry::View::Exposures do
|
2
|
+
subject(:exposures) { described_class.new }
|
3
|
+
|
4
|
+
describe "#exposures" do
|
5
|
+
it "is empty by defalut" do
|
6
|
+
expect(exposures.exposures).to be_empty
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#add" do
|
11
|
+
it "creates and adds an exposure" do
|
12
|
+
proc = -> input { "hi" }
|
13
|
+
exposures.add :hello, proc
|
14
|
+
|
15
|
+
expect(exposures[:hello].name).to eq :hello
|
16
|
+
expect(exposures[:hello].proc).to eq proc
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#bind" do
|
21
|
+
subject(:bound_exposures) { exposures.bind(object) }
|
22
|
+
|
23
|
+
let(:object) do
|
24
|
+
Class.new do
|
25
|
+
def hello(input)
|
26
|
+
"hi"
|
27
|
+
end
|
28
|
+
end.new
|
29
|
+
end
|
30
|
+
|
31
|
+
before do
|
32
|
+
exposures.add(:hello)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "binds each of the exposures" do
|
36
|
+
expect(bound_exposures[:hello].proc).to eq object.method(:hello)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns a new copy of the exposures" do
|
40
|
+
expect(exposures.exposures).not_to eql(bound_exposures.exposures)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#locals" do
|
45
|
+
before do
|
46
|
+
exposures.add(:greeting, -> input { input.fetch(:greeting).upcase })
|
47
|
+
exposures.add(:farewell, -> greeting { "#{greeting} and goodbye" })
|
48
|
+
end
|
49
|
+
|
50
|
+
subject(:locals) { exposures.locals(greeting: "hello") }
|
51
|
+
|
52
|
+
it "returns the values from the exposures' procs" do
|
53
|
+
expect(locals).to eq(greeting: "HELLO", farewell: "HELLO and goodbye")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "does not return any values from private exposures" do
|
57
|
+
exposures.add(:hidden, -> input { "shh" }, to_view: false)
|
58
|
+
|
59
|
+
expect(locals).to include(:greeting, :farewell)
|
60
|
+
expect(locals).not_to include(:hidden)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/spec/unit/renderer_spec.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
require 'dry/view/path'
|
1
2
|
require 'dry/view/renderer'
|
2
3
|
|
3
4
|
RSpec.describe Dry::View::Renderer do
|
4
5
|
subject(:renderer) do
|
5
|
-
Dry::View::Renderer.new(SPEC_ROOT.join('fixtures/templates'), format: 'html'
|
6
|
+
Dry::View::Renderer.new([Dry::View::Path.new(SPEC_ROOT.join('fixtures/templates'))], format: 'html')
|
6
7
|
end
|
7
8
|
|
8
9
|
let(:scope) { double(:scope) }
|