accord 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.
- data/.gitignore +7 -0
- data/.rvmrc +1 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +43 -0
- data/accord.gemspec +20 -0
- data/lib/accord.rb +4 -0
- data/lib/accord/adapter_registry.rb +106 -0
- data/lib/accord/base_registry.rb +73 -0
- data/lib/accord/declarations.rb +131 -0
- data/lib/accord/exceptions.rb +22 -0
- data/lib/accord/extendor.rb +36 -0
- data/lib/accord/extendor_container.rb +43 -0
- data/lib/accord/interface.rb +189 -0
- data/lib/accord/interface_body.rb +73 -0
- data/lib/accord/interface_members.rb +31 -0
- data/lib/accord/interface_method.rb +27 -0
- data/lib/accord/interfaces.rb +471 -0
- data/lib/accord/nested_key_hash.rb +66 -0
- data/lib/accord/ro.rb +65 -0
- data/lib/accord/signature_info.rb +76 -0
- data/lib/accord/specification.rb +83 -0
- data/lib/accord/subscription_registry.rb +76 -0
- data/lib/accord/tags.rb +25 -0
- data/lib/accord/version.rb +3 -0
- data/spec/adapter_registry_spec.rb +296 -0
- data/spec/declarations_spec.rb +144 -0
- data/spec/extendor_container_spec.rb +101 -0
- data/spec/extendor_spec.rb +203 -0
- data/spec/integration/adaptation_spec.rb +86 -0
- data/spec/integration/adapter_for_class_declaration_spec.rb +22 -0
- data/spec/integration/adapter_hook_spec.rb +41 -0
- data/spec/integration/default_adapters_spec.rb +81 -0
- data/spec/integration/hash_adapters_spec.rb +20 -0
- data/spec/integration/interface_declaration_spec.rb +258 -0
- data/spec/integration/multi_adapters_spec.rb +83 -0
- data/spec/integration/named_adapters_spec.rb +54 -0
- data/spec/integration/single_adapters_spec.rb +93 -0
- data/spec/integration/subscriptions_spec.rb +245 -0
- data/spec/integration/verification_spec.rb +215 -0
- data/spec/interface_body_spec.rb +157 -0
- data/spec/interface_members_spec.rb +57 -0
- data/spec/interface_spec.rb +147 -0
- data/spec/nested_key_hash_spec.rb +140 -0
- data/spec/signature_info_spec.rb +65 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/specification_spec.rb +246 -0
- data/spec/subscription_registry_spec.rb +206 -0
- data/spec/tags_spec.rb +38 -0
- metadata +134 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'accord/interface_body'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
describe InterfaceBody do
|
6
|
+
let(:interface) { stub }
|
7
|
+
let(:bases) { [] }
|
8
|
+
let(:members) { stub }
|
9
|
+
let(:invariants) { stub }
|
10
|
+
let(:sig_info) { stub(:param => nil, :block => nil, :splat => nil) }
|
11
|
+
|
12
|
+
subject { InterfaceBody.new(interface, bases, members, invariants) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub_const("Accord::SignatureInfo", stub)
|
16
|
+
stub_const("Accord::InterfaceMethod", stub)
|
17
|
+
Accord::SignatureInfo.stub(:new).and_return(sig_info)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#extends" do
|
21
|
+
let(:base1) { stub }
|
22
|
+
let(:base2) { stub }
|
23
|
+
|
24
|
+
it "adds up interfaces" do
|
25
|
+
subject.extends(base1)
|
26
|
+
expect(bases).to include(base1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "doesn't add a already added base" do
|
30
|
+
subject.extends(base1)
|
31
|
+
subject.extends(base1)
|
32
|
+
expect(bases.size).to eq 1
|
33
|
+
end
|
34
|
+
|
35
|
+
it "prepends the added bases" do
|
36
|
+
subject.extends(base1)
|
37
|
+
subject.extends(base2)
|
38
|
+
|
39
|
+
expect(bases.shift).to be base2
|
40
|
+
expect(bases.shift).to be base1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#responds_to" do
|
45
|
+
let(:method) { stub }
|
46
|
+
|
47
|
+
before do
|
48
|
+
members.stub(:add)
|
49
|
+
InterfaceMethod.stub(:new).with(interface, :method, sig_info).
|
50
|
+
and_return(method)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "adds up methods" do
|
54
|
+
members.should_receive(:add).with(:method, method)
|
55
|
+
subject.responds_to(:method)
|
56
|
+
end
|
57
|
+
|
58
|
+
context "using :params option" do
|
59
|
+
context "to add a regular argument" do
|
60
|
+
it "creates a param in the signature info" do
|
61
|
+
sig_info.should_receive(:param).with(:foo)
|
62
|
+
subject.responds_to(:method, params: :foo)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "to add a splat argument" do
|
67
|
+
it "creates a splat in the signature info" do
|
68
|
+
sig_info.should_receive(:splat).with(:foo)
|
69
|
+
subject.responds_to(:method, params: :"*foo")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "to add a block argument" do
|
74
|
+
it "creates a block argument in the signature info" do
|
75
|
+
sig_info.should_receive(:block).with(:foo)
|
76
|
+
subject.responds_to(:method, params: :"&foo")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "using block" do
|
82
|
+
context "to add a regular argument" do
|
83
|
+
it "creates a param in the signature info" do
|
84
|
+
sig_info.should_receive(:param).with(:foo)
|
85
|
+
subject.responds_to(:method) { param :foo }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "to add a splat argument" do
|
90
|
+
it "creates a splat in the signature info" do
|
91
|
+
sig_info.should_receive(:splat).with(:foo)
|
92
|
+
subject.responds_to(:method) { splat :foo }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "to add a block argument" do
|
97
|
+
it "creates a block argument in the signature info" do
|
98
|
+
sig_info.should_receive(:block).with(:foo)
|
99
|
+
subject.responds_to(:method) { block :foo }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#invariant" do
|
106
|
+
it "adds up invariants" do
|
107
|
+
block = Proc.new { }
|
108
|
+
invariants.should_receive(:add).with(:invariant, &block)
|
109
|
+
subject.invariant(:invariant, &block)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#interface" do
|
114
|
+
it "exposes the interface object" do
|
115
|
+
expect(subject.interface).to be interface
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "#tags" do
|
120
|
+
let(:tags) { stub }
|
121
|
+
|
122
|
+
it "expose interface tags" do
|
123
|
+
interface.stub(:tags).and_return(tags)
|
124
|
+
expect(subject.tags).to be tags
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe ".run" do
|
129
|
+
subject { InterfaceBody }
|
130
|
+
let(:root_interface) { stub }
|
131
|
+
|
132
|
+
before do
|
133
|
+
stub_const('Accord::Interface', root_interface)
|
134
|
+
interface.stub(:bases=)
|
135
|
+
interface.stub(:members)
|
136
|
+
interface.stub(:invariants)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "adds the root interface if bases is left empty" do
|
140
|
+
interface.should_receive(:bases=).with([root_interface])
|
141
|
+
subject.run(interface) { }
|
142
|
+
end
|
143
|
+
|
144
|
+
it "doesn't add root interface if bases has at least one interface" do
|
145
|
+
stub_const('Accord::TestInterface', stub)
|
146
|
+
interface.should_receive(:bases=).with([Accord::TestInterface])
|
147
|
+
subject.run(interface) { extends Accord::TestInterface }
|
148
|
+
end
|
149
|
+
|
150
|
+
it "runs the block in an instance" do
|
151
|
+
instance = nil
|
152
|
+
subject.run(interface) { instance = self }
|
153
|
+
expect(instance).to be_a InterfaceBody
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'accord/interface_members'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
describe InterfaceMembers do
|
6
|
+
let(:interface) { stub }
|
7
|
+
let(:member) { stub }
|
8
|
+
|
9
|
+
subject { InterfaceMembers.new(interface) }
|
10
|
+
|
11
|
+
it "doesn't have any member added by default" do
|
12
|
+
expect(subject[:m]).to be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#add" do
|
16
|
+
it "adds the member" do
|
17
|
+
subject.add(:m, member)
|
18
|
+
expect(subject[:m]).to be member
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#names" do
|
23
|
+
it "return added names as symbols" do
|
24
|
+
subject.add(:m, member)
|
25
|
+
expect(subject.names).to eq [:m]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#added?" do
|
30
|
+
it "returns true if the method was added" do
|
31
|
+
subject.add(:m, member)
|
32
|
+
expect(subject).to be_added(:m)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns false if the method was not added" do
|
36
|
+
expect(subject).to_not be_added(:m)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#each" do
|
41
|
+
it "iterates over all members" do
|
42
|
+
names = []
|
43
|
+
members = []
|
44
|
+
|
45
|
+
m, n = stub, stub
|
46
|
+
|
47
|
+
subject.add(:m, m)
|
48
|
+
subject.add(:n, n)
|
49
|
+
|
50
|
+
subject.each { |name, member| names << name; members << member }
|
51
|
+
|
52
|
+
expect(names).to eq [:m, :n]
|
53
|
+
expect(members).to eq [m, n]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'accord/interface'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
describe InterfaceClass do
|
6
|
+
before { Accord.clear_adapter_hooks }
|
7
|
+
|
8
|
+
specify "interfaces are just a kind of specification" do
|
9
|
+
expect(InterfaceClass.new(:I)).to be_a(Specification)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns self as its single interface" do
|
13
|
+
interface = InterfaceClass.new(:I)
|
14
|
+
expect(interface.interfaces).to eq [interface]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns ancestor specs in #ancestors" do
|
18
|
+
spec = Specification.new
|
19
|
+
interface = InterfaceClass.new(:I, [spec])
|
20
|
+
|
21
|
+
expect(interface.ancestors).to eq [interface, spec]
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns only ancestor interfaces in #iro" do
|
25
|
+
spec = Specification.new
|
26
|
+
interface = InterfaceClass.new(:I, [spec])
|
27
|
+
|
28
|
+
expect(interface.iro).to eq [interface]
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#provided_by?" do
|
32
|
+
let(:object) { stub }
|
33
|
+
let(:interface) { InterfaceClass.new(:I) }
|
34
|
+
|
35
|
+
let(:provided_by_declaration) { stub(:extends? => false) }
|
36
|
+
let(:declarations_module) { stub }
|
37
|
+
|
38
|
+
before do
|
39
|
+
stub_const('Accord::Declarations', declarations_module)
|
40
|
+
declarations_module.stub(:provided_by).with(object).
|
41
|
+
and_return(provided_by_declaration)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "returns false if the object is not told to provide the interface" do
|
45
|
+
expect(interface).to_not be_provided_by(object)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns true if the object is told to provide the interface" do
|
49
|
+
provided_by_declaration.stub(:extends?).with(interface).
|
50
|
+
and_return(true)
|
51
|
+
expect(interface).to be_provided_by(object)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#implemented_by?" do
|
56
|
+
let(:factory) { stub }
|
57
|
+
let(:interface) { InterfaceClass.new(:I) }
|
58
|
+
|
59
|
+
let(:implemented_by_declaration) { stub(:extends? => false) }
|
60
|
+
let(:declarations_module) { stub }
|
61
|
+
|
62
|
+
before do
|
63
|
+
stub_const('Accord::Declarations', declarations_module)
|
64
|
+
declarations_module.stub(:implemented_by).with(factory).
|
65
|
+
and_return(implemented_by_declaration)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns false if the factory is not told to implement the interface" do
|
69
|
+
expect(interface).to_not be_implemented_by(factory)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns true if the factory is told to implement the interface" do
|
73
|
+
implemented_by_declaration.stub(:extends?).with(interface).
|
74
|
+
and_return(true)
|
75
|
+
expect(interface).to be_implemented_by(factory)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#adapt" do
|
80
|
+
let(:interface) { InterfaceClass.new(:I) }
|
81
|
+
let(:object) { stub }
|
82
|
+
let(:adapter) { stub }
|
83
|
+
let(:hook_miss) { Proc.new { |iface, *obs| nil } }
|
84
|
+
let(:hook_hit) { Proc.new { |iface, *obs| adapter } }
|
85
|
+
|
86
|
+
it "returns nil if no hook is installed" do
|
87
|
+
expect(interface.adapt(object)).to be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns nil if all hooks fail" do
|
91
|
+
Accord.install_adapter_hook(hook_miss)
|
92
|
+
Accord.install_adapter_hook(hook_miss)
|
93
|
+
Accord.install_adapter_hook(hook_miss)
|
94
|
+
|
95
|
+
expect(interface.adapt(object)).to be_nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns the result of the first hook which succeeds" do
|
99
|
+
hook_hit2 = Proc.new { |iface, *obs| 'other adapter' }
|
100
|
+
|
101
|
+
Accord.install_adapter_hook(hook_miss)
|
102
|
+
Accord.install_adapter_hook(hook_hit)
|
103
|
+
Accord.install_adapter_hook(hook_miss)
|
104
|
+
Accord.install_adapter_hook(hook_hit2)
|
105
|
+
Accord.install_adapter_hook(hook_miss)
|
106
|
+
|
107
|
+
expect(interface.adapt(object)).to be adapter
|
108
|
+
end
|
109
|
+
|
110
|
+
context "when the hook actually cares about what is being adapted" do
|
111
|
+
let(:hook_hit) { stub }
|
112
|
+
|
113
|
+
before do
|
114
|
+
hook_hit.stub(:call).with(interface, object, object).
|
115
|
+
and_return(adapter)
|
116
|
+
Accord.install_adapter_hook(hook_hit)
|
117
|
+
end
|
118
|
+
|
119
|
+
subject { interface.adapt(object, object) }
|
120
|
+
it { should be adapter }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#adapt!" do
|
125
|
+
let(:interface) { InterfaceClass.new(:I) }
|
126
|
+
let(:object) { stub }
|
127
|
+
let(:adapter) { stub }
|
128
|
+
let(:hook_miss) { Proc.new { |iface, *obs| nil } }
|
129
|
+
let(:hook_hit) { Proc.new { |iface, *obs| adapter } }
|
130
|
+
|
131
|
+
it "raises TypeError if there's no hook installed" do
|
132
|
+
expect { interface.adapt!(object) }.to raise_error(TypeError)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "raises TypeError if no hook provided a suitable adapter" do
|
136
|
+
Accord.install_adapter_hook(Proc.new { |iface, *obs| nil })
|
137
|
+
|
138
|
+
expect { interface.adapt!(object) }.to raise_error(TypeError)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "returns the adapter if one hook succeeded" do
|
142
|
+
Accord.install_adapter_hook(Proc.new { |iface, *obs| adapter })
|
143
|
+
expect(interface.adapt!(object)).to be adapter
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'accord/nested_key_hash'
|
3
|
+
|
4
|
+
module Accord
|
5
|
+
describe NestedKeyHash do
|
6
|
+
subject { NestedKeyHash.new }
|
7
|
+
|
8
|
+
let(:k1) { 'key 1' }
|
9
|
+
let(:k2) { 'key 2' }
|
10
|
+
let(:k3) { 'key 3' }
|
11
|
+
|
12
|
+
let(:key) { [k1, k2, k3] }
|
13
|
+
|
14
|
+
specify "empty hash returns nil" do
|
15
|
+
expect(subject[key]).to be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "adds a value using a composed key" do
|
19
|
+
value = stub
|
20
|
+
subject[key] = value
|
21
|
+
expect(subject[key]).to be value
|
22
|
+
end
|
23
|
+
|
24
|
+
specify "different keys store different values" do
|
25
|
+
value1 = stub
|
26
|
+
value2 = stub
|
27
|
+
subject[[k1, k2, k3]] = value1
|
28
|
+
subject[[k3, k2, k1]] = value2
|
29
|
+
|
30
|
+
expect(subject[[k1, k2, k3]]).to be value1
|
31
|
+
expect(subject[[k3, k2, k1]]).to be value2
|
32
|
+
end
|
33
|
+
|
34
|
+
it "overwrites a value when using the same key" do
|
35
|
+
old_value = stub
|
36
|
+
new_value = stub
|
37
|
+
subject[key] = old_value
|
38
|
+
subject[key] = new_value
|
39
|
+
expect(subject[key]).to be new_value
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "keys not prefixing other already in use returns nil" do
|
43
|
+
subject[key] = 'a value'
|
44
|
+
expect(subject[[k2, k1, k3]]).to be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
specify "prefix keys returns hash containing the remaining keys nested" do
|
48
|
+
value = stub
|
49
|
+
subject[key] = value
|
50
|
+
expect(subject[[k1]]).to eq({ k2 => { k3 => value } })
|
51
|
+
end
|
52
|
+
|
53
|
+
it "deletes a key" do
|
54
|
+
subject[key] = 'a value'
|
55
|
+
subject.delete(key)
|
56
|
+
expect(subject[key]).to be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
specify "deleting returns the deleted value" do
|
60
|
+
value = stub
|
61
|
+
subject[key] = value
|
62
|
+
result = subject.delete(key)
|
63
|
+
expect(result).to be value
|
64
|
+
end
|
65
|
+
|
66
|
+
specify "deleting a key removes intermediate empty hashes" do
|
67
|
+
subject[key] = 'a value'
|
68
|
+
subject.delete(key)
|
69
|
+
expect(subject[[k1]]).to be_nil
|
70
|
+
expect(subject[[k1, k2]]).to be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
specify "deleting a key keeps intermediate non-empty hashes" do
|
74
|
+
value, another_value, k4, k5 = stub, stub, 'key 4', 'key 5'
|
75
|
+
subject[[k1, k2, k3, k4]] = value
|
76
|
+
subject[[k1, k2, k4, k5]] = another_value
|
77
|
+
subject.delete([k1, k2, k3, k4])
|
78
|
+
|
79
|
+
expect(subject[[k1, k2, k4, k5]]).to be another_value
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "key lookup with expansion" do
|
83
|
+
|
84
|
+
let(:k11) { stub }
|
85
|
+
let(:k12) { stub }
|
86
|
+
let(:k13) { stub }
|
87
|
+
let(:k21) { stub }
|
88
|
+
let(:k22) { stub }
|
89
|
+
let(:k23) { stub }
|
90
|
+
|
91
|
+
let(:expansions) do
|
92
|
+
{
|
93
|
+
k1 => [k11, k12, k13],
|
94
|
+
k2 => [k21, k22, k23]
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#detect_expansion" do
|
99
|
+
it "hits the value with valid expansion" do
|
100
|
+
subject[[k11, k22]] = 'value'
|
101
|
+
result = subject.detect_expansion([k1, k2]) do |k|
|
102
|
+
expansions[k]
|
103
|
+
end
|
104
|
+
expect(result).to eq 'value'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "misses the value with invalid expansion" do
|
108
|
+
subject[[k11, k12]] = 'value'
|
109
|
+
result = subject.detect_expansion([k1, k2]) do |k|
|
110
|
+
expansions[k]
|
111
|
+
end
|
112
|
+
expect(result).to be_nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#select_expansions" do
|
117
|
+
it "hits the values matching some valid expansion" do
|
118
|
+
subject[[k11, k22]] = 'value'
|
119
|
+
subject[[k12, k21]] = 'other value'
|
120
|
+
|
121
|
+
result = subject.select_expansions([k1, k2]) do |k|
|
122
|
+
expansions[k]
|
123
|
+
end
|
124
|
+
expect(result).to eq ['value', 'other value']
|
125
|
+
end
|
126
|
+
|
127
|
+
it "misses if no valid expansion is found" do
|
128
|
+
subject[[k11, k12]] = 'value'
|
129
|
+
subject[[k11, k13]] = 'other value'
|
130
|
+
|
131
|
+
result = subject.select_expansions([k1, k2]) do |k|
|
132
|
+
expansions[k]
|
133
|
+
end
|
134
|
+
|
135
|
+
expect(result).to be_empty
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|