-
Notifications
You must be signed in to change notification settings - Fork 88
/
inheritable_attr_spec.rb
202 lines (170 loc) · 5.63 KB
/
inheritable_attr_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
require 'spec_helper'
require 'fpm/cookery/inheritable_attr'
require 'fpm/cookery/path'
def dsl_klass(name = nil)
(klass = Class.new).send(:extend, FPM::Cookery::InheritableAttr)
klass.class_eval(&Proc.new) if block_given?
klass
end
shared_examples 'attribute registration' do |attr_method, registry_method|
context "given an attribute created with .#{attr_method}" do
it "registers the attribute in the .#{registry_method} list" do
klass = Class.new do
extend FPM::Cookery::InheritableAttr
instance_eval %Q{
#{attr_method} :example_attr
}
end
expect(klass).to respond_to(registry_method)
expect(klass.send(registry_method)).to include(:example_attr)
end
end
end
shared_context 'class inheritance' do
let(:superklass) { dsl_klass }
let(:subklass) { Class.new(superklass) }
end
shared_examples 'attribute inheritance' do |attr_method, default_value, attr_name = :example_attr|
# A default implementation. Useful for +.attr_rw+, but should probably be
# overridden for the other DSL methods.
let(:attr_setter) {
Class.new do
def self.call(k, m, v)
k.send(m, v)
end
end
}
describe 'created attribute' do
it 'is defined on class' do
expect(superklass).to respond_to(attr_name)
end
it "is #{default_value.inspect} by default" do
expect(superklass.send(attr_name)).to eq(default_value)
end
it 'sets the attribute' do
attr_setter.(superklass, attr_name, value)
expect(superklass.send(attr_name)).to eq value
end
end
describe 'child class' do
describe 'inherited attribute' do
it 'inherits its value from the superclass' do
attr_setter.(superklass, attr_name, value)
expect(subklass.send(attr_name)).to eq value
end
context 'when altered' do
it 'does not propagate to the superclass' do
attr_setter.(superklass, attr_name, value)
attr_setter.(subklass, attr_name, child_value)
expect(superklass.send(attr_name)).to eq value
end
end
end
end
end
shared_context 'inheritable attributes' do |attr_method, default_value, attr_name = :example_attr|
include_context 'class inheritance'
include_examples 'attribute inheritance', attr_method, default_value
before(:example) do
missing = [:value, :child_value].reject { |m| respond_to?(m) }
raise "Missing required methods: #{missing.join(', ')}" unless missing.empty?
superklass.instance_eval do
send(attr_method, attr_name)
end
end
end
describe FPM::Cookery::InheritableAttr do
describe '.register_attrs' do
describe '.attr_rw' do
include_examples 'attribute registration', 'attr_rw', 'scalar_attrs'
end
describe '.attr_rw_list' do
include_examples 'attribute registration', 'attr_rw_list', 'list_attrs'
end
describe '.attr_rw_hash' do
include_examples 'attribute registration', 'attr_rw_hash', 'hash_attrs'
end
describe '.attr_rw_path' do
include_examples 'attribute registration', 'attr_rw_path', 'path_attrs'
end
end
describe '.attr_rw' do
include_context 'inheritable attributes', 'attr_rw', nil do
let(:value) { 'that' }
let(:child_value) { 'the other' }
end
end
describe '.attr_rw_list' do
include_context 'inheritable attributes', 'attr_rw_list', [] do
let(:attr_setter) {
Class.new do
def self.call(k, m, v)
k.send(m, *v)
end
end
}
let(:value) { %w{so la ti do} }
let(:child_value) { %w{fee fi fo fum} }
end
end
describe '.attr_rw_hash' do
include_context 'inheritable attributes', 'attr_rw_hash', {} do
let(:value) { {:name => 'mike', :rank => 'colonel' } }
let(:child_value) { {:name => 'mike', :favorite_color => 'puce' } }
end
describe 'created attribute' do
before do
superklass.instance_eval do
attr_rw_hash :metadata
end
superklass.metadata({ :radius => 4.172, :weight => 4 })
end
it 'merges its argument into the existing attribute value' do
superklass.metadata({ :weight => 7, :height => 12.3 })
expect(superklass.metadata).to eq({
:height => 12.3,
:weight => 7,
:radius => 4.172
})
end
it 'supports []= assignment' do
expect { superklass.metadata[:age] = 10000 }.not_to raise_error
expect(superklass.metadata).to include(:age => 10000)
end
context 'from child class' do
it 'does not propagate changes to the parent class' do
superklass.metadata[:tags] = %w{a b c}
subklass.metadata[:tags] << 'd'
expect(superklass.metadata[:tags]).not_to include('d')
end
end
end
end
describe '.attr_rw_path' do
include_context 'inheritable attributes', 'attr_rw_path', nil do
let(:attr_setter) {
Class.new do
def self.call(k, m, v)
k.send(:"#{m}=", v)
end
end
}
let(:value) { FPM::Cookery::Path.new('/var/spool') }
let(:child_value) { FPM::Cookery::Path.new('/proc/self') }
end
it 'returns an FPM::Cookery::Path object' do
superklass.attr_rw_path :home
superklass.home = "/where/the/heart/is"
expect(superklass.home).to be_an(FPM::Cookery::Path)
end
end
describe '.inherit_for' do
include_context 'class inheritance'
context 'given a method name that the superclass does not respond to' do
it 'returns nil' do
expect(superklass).not_to respond_to(:blech)
expect(FPM::Cookery::InheritableAttr.inherit_for(subklass, :blech)).to be nil
end
end
end
end