tree_branch 1.0.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 +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +8 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +89 -0
- data/Guardfile +16 -0
- data/LICENSE +7 -0
- data/README.md +431 -0
- data/bin/console +15 -0
- data/lib/tree_branch/comparator.rb +25 -0
- data/lib/tree_branch/node.rb +42 -0
- data/lib/tree_branch/processor.rb +47 -0
- data/lib/tree_branch/simple_node.rb +21 -0
- data/lib/tree_branch/tree_branch.rb +38 -0
- data/lib/tree_branch/version.rb +12 -0
- data/lib/tree_branch.rb +10 -0
- data/spec/fixtures/born_after1915.yml +13 -0
- data/spec/fixtures/born_after1915_with_m_or_s.yml +9 -0
- data/spec/fixtures/node.yml +18 -0
- data/spec/fixtures/node_with_injected.yml +22 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/tree_branch/comparator_spec.rb +47 -0
- data/spec/tree_branch/node_spec.rb +24 -0
- data/spec/tree_branch/tree_branch_spec.rb +394 -0
- data/tree_branch.gemspec +29 -0
- metadata +137 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module TreeBranch
|
11
|
+
# This class understands how to take a tree, digest it given a context, set of comparators, and a
|
12
|
+
# block, then returns a new tree structure.
|
13
|
+
class Processor
|
14
|
+
def process(node, context: nil, comparators: [], &block)
|
15
|
+
return nil if at_least_one_comparator_returns_false?(node.data, context, comparators)
|
16
|
+
|
17
|
+
valid_children = process_children(node.children, context, comparators, &block)
|
18
|
+
|
19
|
+
if block_given?
|
20
|
+
yield(node.data, valid_children, context)
|
21
|
+
else
|
22
|
+
::TreeBranch::Node.new(node.data)
|
23
|
+
.add(valid_children)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def at_least_one_comparator_returns_false?(data, context, comparators)
|
30
|
+
Array(comparators).any? { |c| execute_comparator(c, data, context) == false }
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_children(children, context, comparators, &block)
|
34
|
+
children.map do |node|
|
35
|
+
process(node, context: context, comparators: comparators, &block)
|
36
|
+
end.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute_comparator(comparator, data, context)
|
40
|
+
if comparator.is_a?(Proc)
|
41
|
+
comparator.call(OpenStruct.new(data), OpenStruct.new(context))
|
42
|
+
else
|
43
|
+
comparator.new(data: data, context: context).valid?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module TreeBranch
|
11
|
+
# A basic subclass of Node that makes the data element a deterministic and comparable OpenStruct
|
12
|
+
# object.
|
13
|
+
class SimpleNode < Node
|
14
|
+
acts_as_hashable
|
15
|
+
|
16
|
+
def initialize(data: {}, children: [])
|
17
|
+
@data = OpenStruct.new(data)
|
18
|
+
@children = self.class.array(children)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'acts_as_hashable'
|
11
|
+
require 'ostruct'
|
12
|
+
|
13
|
+
require_relative 'comparator'
|
14
|
+
require_relative 'node'
|
15
|
+
require_relative 'simple_node'
|
16
|
+
require_relative 'processor'
|
17
|
+
|
18
|
+
# Top-level namespace of the library. The methods contained here should be considered the
|
19
|
+
# main public API.
|
20
|
+
module TreeBranch
|
21
|
+
class << self
|
22
|
+
def process(node: {}, context: {}, comparators: [], &block)
|
23
|
+
::TreeBranch::Processor.new
|
24
|
+
.process(
|
25
|
+
normalize_node(node),
|
26
|
+
context: context,
|
27
|
+
comparators: comparators,
|
28
|
+
&block
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def normalize_node(node)
|
35
|
+
node.is_a?(::TreeBranch::Node) ? node : ::TreeBranch::SimpleNode.make(node, nullable: false)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module TreeBranch
|
11
|
+
VERSION = '1.0.0'
|
12
|
+
end
|
data/lib/tree_branch.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'tree_branch/tree_branch'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
:data:
|
2
|
+
name: Matt
|
3
|
+
:dob: '1920-01-04'
|
4
|
+
state: IL
|
5
|
+
:children:
|
6
|
+
- :data:
|
7
|
+
:name: Nick
|
8
|
+
dob: '1930-04-04'
|
9
|
+
:state: WI
|
10
|
+
:children:
|
11
|
+
- :data:
|
12
|
+
name: Katie
|
13
|
+
:dob: '1820-02-05'
|
14
|
+
:state: 'CA'
|
15
|
+
- :data:
|
16
|
+
:name: Sam
|
17
|
+
dob: '1940-09-12'
|
18
|
+
:state: AK
|
@@ -0,0 +1,22 @@
|
|
1
|
+
:data:
|
2
|
+
name: Matt
|
3
|
+
:dob: '1920-01-04'
|
4
|
+
state: IL
|
5
|
+
injected: Mattcakes!!
|
6
|
+
:children:
|
7
|
+
- :data:
|
8
|
+
:name: Nick
|
9
|
+
dob: '1930-04-04'
|
10
|
+
:state: WI
|
11
|
+
injected: Nickcakes!!
|
12
|
+
:children:
|
13
|
+
- :data:
|
14
|
+
name: Katie
|
15
|
+
:dob: '1820-02-05'
|
16
|
+
:state: CA
|
17
|
+
injected: Katiecakes!!
|
18
|
+
- :data:
|
19
|
+
:name: Sam
|
20
|
+
dob: '1940-09-12'
|
21
|
+
:state: AK
|
22
|
+
injected: Samcakes!!
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'date'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
require './lib/tree_branch'
|
14
|
+
|
15
|
+
def fixture_path(filename)
|
16
|
+
File.join('spec', 'fixtures', filename)
|
17
|
+
end
|
18
|
+
|
19
|
+
def fixture(filename)
|
20
|
+
YAML.load_file(fixture_path(filename))
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './spec/spec_helper'
|
11
|
+
|
12
|
+
describe ::TreeBranch::Comparator do
|
13
|
+
let(:data_hash) do
|
14
|
+
{
|
15
|
+
'name': 'Matt',
|
16
|
+
dob: '1920-01-04',
|
17
|
+
'state' => 'IL'
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:context_hash) do
|
22
|
+
{
|
23
|
+
letters: %w[M S]
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should initialize from hashes correctly' do
|
28
|
+
comparator = ::TreeBranch::Comparator.new(data: data_hash, context: context_hash)
|
29
|
+
|
30
|
+
expect(comparator.data['name']).to eq(data_hash['name'])
|
31
|
+
expect(comparator.data[:dob]).to eq(data_hash[:dob])
|
32
|
+
expect(comparator.data['state']).to eq(data_hash['state'])
|
33
|
+
expect(comparator.context[:letters]).to eq(context_hash[:letters])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should initialize from OpenStruct objects correctly' do
|
37
|
+
data = OpenStruct.new(data_hash)
|
38
|
+
context = OpenStruct.new(context_hash)
|
39
|
+
|
40
|
+
comparator = ::TreeBranch::Comparator.new(data: data, context: context)
|
41
|
+
|
42
|
+
expect(comparator.data.name).to eq(data.name)
|
43
|
+
expect(comparator.data.dob).to eq(data.dob)
|
44
|
+
expect(comparator.data.state).to eq(data.state)
|
45
|
+
expect(comparator.context.letters).to eq(context.letters)
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './spec/spec_helper'
|
11
|
+
|
12
|
+
describe ::TreeBranch::Node do
|
13
|
+
let(:node_hash) { fixture('node.yml') }
|
14
|
+
|
15
|
+
let(:node) { ::TreeBranch::SimpleNode.make(node_hash) }
|
16
|
+
|
17
|
+
it 'should initialize and equality compare correctly' do
|
18
|
+
expected_data = OpenStruct.new(node_hash[:data])
|
19
|
+
expected_children = ::TreeBranch::SimpleNode.array(node_hash[:children])
|
20
|
+
|
21
|
+
expect(node.data).to eq(expected_data)
|
22
|
+
expect(node.children).to eq(expected_children)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,394 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './spec/spec_helper'
|
11
|
+
|
12
|
+
describe ::TreeBranch do
|
13
|
+
# We will use this spec to also test ::TreeBranch::Node#process since ::TreeBranch#process
|
14
|
+
# fully delegates to that method.
|
15
|
+
describe '#process' do
|
16
|
+
let(:node_hash) { fixture('node.yml') }
|
17
|
+
|
18
|
+
let(:node_hash_with_injected) { fixture('node_with_injected.yml') }
|
19
|
+
|
20
|
+
let(:node) { ::TreeBranch::SimpleNode.make(node_hash) }
|
21
|
+
|
22
|
+
let(:node_with_injected) { ::TreeBranch::SimpleNode.make(node_hash_with_injected) }
|
23
|
+
|
24
|
+
let(:born_after1915_hash) { fixture('born_after1915.yml') }
|
25
|
+
|
26
|
+
let(:born_after1915) { ::TreeBranch::SimpleNode.make(born_after1915_hash) }
|
27
|
+
|
28
|
+
let(:born_after1915_starts_with_m_or_s_hash) do
|
29
|
+
fixture('born_after1915_with_m_or_s.yml')
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:born_after1915_starts_with_m_or_s) do
|
33
|
+
::TreeBranch::SimpleNode.make(born_after1915_starts_with_m_or_s_hash)
|
34
|
+
end
|
35
|
+
|
36
|
+
class BornAfter1915 < ::TreeBranch::Comparator
|
37
|
+
def valid?
|
38
|
+
Date.parse(data.dob).year > 1915
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class NameStartsWith < ::TreeBranch::Comparator
|
43
|
+
def valid?
|
44
|
+
context[:letters].include?(data.name.to_s[0])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
let(:name_starts_with_lambda) do
|
49
|
+
lambda do |data, context|
|
50
|
+
context.letters.include?(data.name.to_s[0])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should return everything when no comparators are given' do
|
55
|
+
expect(::TreeBranch.process(node: node_hash)).to eq(node)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should return nil when no comparators pass for top level node' do
|
59
|
+
# The base comparator class returns false by default
|
60
|
+
expect(::TreeBranch.process(node: node_hash, comparators: ::TreeBranch::Comparator)).to be nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should return valid nodes with one comparator' do
|
64
|
+
actual = ::TreeBranch.process(node: node_hash, comparators: BornAfter1915)
|
65
|
+
|
66
|
+
expect(actual).to eq(born_after1915)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should return valid nodes with two class comparators' do
|
70
|
+
input = {
|
71
|
+
node: node_hash,
|
72
|
+
context: { letters: %w[M S] },
|
73
|
+
comparators: [BornAfter1915, NameStartsWith]
|
74
|
+
}
|
75
|
+
|
76
|
+
expect(::TreeBranch.process(input)).to eq(born_after1915_starts_with_m_or_s)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should return valid nodes with one class comparator and one lambda comparator' do
|
80
|
+
input = {
|
81
|
+
node: node_hash,
|
82
|
+
context: { letters: %w[M S] },
|
83
|
+
comparators: [BornAfter1915, name_starts_with_lambda]
|
84
|
+
}
|
85
|
+
|
86
|
+
expect(::TreeBranch.process(input)).to eq(born_after1915_starts_with_m_or_s)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should execute the block after node processing' do
|
90
|
+
outside_variable = '!!'
|
91
|
+
|
92
|
+
input = {
|
93
|
+
node: node,
|
94
|
+
context: OpenStruct.new(suffix: 'cakes')
|
95
|
+
}
|
96
|
+
|
97
|
+
processed = ::TreeBranch.process(input) do |data, children, context|
|
98
|
+
local_node = ::TreeBranch::SimpleNode.new(data: data, children: children)
|
99
|
+
|
100
|
+
local_node.data.injected = "#{local_node.data.name}#{context.suffix}#{outside_variable}"
|
101
|
+
|
102
|
+
local_node
|
103
|
+
end
|
104
|
+
|
105
|
+
expect(processed).to eq(node_with_injected)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'README Examples' do
|
110
|
+
class StateComparator < ::TreeBranch::Comparator
|
111
|
+
STATE_OPS = {
|
112
|
+
none: %i[open],
|
113
|
+
passive: %i[open save close print],
|
114
|
+
active: %i[open save close print cut copy paste]
|
115
|
+
}.freeze
|
116
|
+
|
117
|
+
def valid?
|
118
|
+
data.command.nil? || Array(STATE_OPS[context[:state]]).include?(data.command)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
let(:menu) do
|
123
|
+
{
|
124
|
+
data: { name: 'Menu' },
|
125
|
+
children: [
|
126
|
+
{
|
127
|
+
data: { name: 'File' },
|
128
|
+
children: [
|
129
|
+
{ data: { name: 'Open', command: :open } },
|
130
|
+
{ data: { name: 'Save', command: :save, right: :write } },
|
131
|
+
{ data: { name: 'Close', command: :close } },
|
132
|
+
{
|
133
|
+
data: { name: 'Print', command: :print },
|
134
|
+
children: [
|
135
|
+
{ data: { name: 'Print' } },
|
136
|
+
{ data: { name: 'Print Preview' } }
|
137
|
+
]
|
138
|
+
}
|
139
|
+
]
|
140
|
+
},
|
141
|
+
{
|
142
|
+
data: { name: 'Edit' },
|
143
|
+
children: [
|
144
|
+
{ data: { name: 'Cut', command: :cut } },
|
145
|
+
{ data: { name: 'Copy', command: :copy } },
|
146
|
+
{ data: { name: 'Paste', command: :paste } }
|
147
|
+
]
|
148
|
+
}
|
149
|
+
]
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should compute state: none' do
|
154
|
+
no_file_menu = ::TreeBranch.process(
|
155
|
+
node: menu,
|
156
|
+
comparators: StateComparator,
|
157
|
+
context: { state: :none }
|
158
|
+
) do |data, children, _context|
|
159
|
+
::TreeBranch::SimpleNode.new(data: data, children: children)
|
160
|
+
end
|
161
|
+
|
162
|
+
expected = ::TreeBranch::SimpleNode.new(
|
163
|
+
data: { name: 'Menu' },
|
164
|
+
children: [
|
165
|
+
{
|
166
|
+
data: { name: 'File' },
|
167
|
+
children: [
|
168
|
+
{ data: { name: 'Open', command: :open } }
|
169
|
+
]
|
170
|
+
},
|
171
|
+
{
|
172
|
+
data: { name: 'Edit' }
|
173
|
+
}
|
174
|
+
]
|
175
|
+
)
|
176
|
+
|
177
|
+
expect(no_file_menu).to eq(expected)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should compute state: passive' do
|
181
|
+
no_file_menu = ::TreeBranch.process(
|
182
|
+
node: menu,
|
183
|
+
comparators: StateComparator,
|
184
|
+
context: { state: :passive }
|
185
|
+
) do |data, children, _context|
|
186
|
+
::TreeBranch::SimpleNode.new(data: data, children: children)
|
187
|
+
end
|
188
|
+
|
189
|
+
expected = ::TreeBranch::SimpleNode.new(
|
190
|
+
data: { name: 'Menu' },
|
191
|
+
children: [
|
192
|
+
{
|
193
|
+
data: { name: 'File' },
|
194
|
+
children: [
|
195
|
+
{ data: { name: 'Open', command: :open } },
|
196
|
+
{ data: { name: 'Save', command: :save, right: :write } },
|
197
|
+
{ data: { name: 'Close', command: :close } },
|
198
|
+
{
|
199
|
+
data: { name: 'Print', command: :print },
|
200
|
+
children: [
|
201
|
+
{ data: { name: 'Print' } },
|
202
|
+
{ data: { name: 'Print Preview' } }
|
203
|
+
]
|
204
|
+
}
|
205
|
+
]
|
206
|
+
},
|
207
|
+
{
|
208
|
+
data: { name: 'Edit' }
|
209
|
+
}
|
210
|
+
]
|
211
|
+
)
|
212
|
+
|
213
|
+
expect(no_file_menu).to eq(expected)
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should compute state: active' do
|
217
|
+
no_file_menu = ::TreeBranch.process(
|
218
|
+
node: menu,
|
219
|
+
comparators: StateComparator,
|
220
|
+
context: { state: :active }
|
221
|
+
) do |data, children, _context|
|
222
|
+
::TreeBranch::SimpleNode.new(data: data, children: children)
|
223
|
+
end
|
224
|
+
|
225
|
+
expected = ::TreeBranch::SimpleNode.new(
|
226
|
+
data: { name: 'Menu' },
|
227
|
+
children: [
|
228
|
+
{
|
229
|
+
data: { name: 'File' },
|
230
|
+
children: [
|
231
|
+
{ data: { name: 'Open', command: :open } },
|
232
|
+
{ data: { name: 'Save', command: :save, right: :write } },
|
233
|
+
{ data: { name: 'Close', command: :close } },
|
234
|
+
{
|
235
|
+
data: { name: 'Print', command: :print },
|
236
|
+
children: [
|
237
|
+
{ data: { name: 'Print' } },
|
238
|
+
{ data: { name: 'Print Preview' } }
|
239
|
+
]
|
240
|
+
}
|
241
|
+
]
|
242
|
+
},
|
243
|
+
{
|
244
|
+
data: { name: 'Edit' },
|
245
|
+
children: [
|
246
|
+
{ data: { name: 'Cut', command: :cut } },
|
247
|
+
{ data: { name: 'Copy', command: :copy } },
|
248
|
+
{ data: { name: 'Paste', command: :paste } }
|
249
|
+
]
|
250
|
+
}
|
251
|
+
]
|
252
|
+
)
|
253
|
+
|
254
|
+
expect(no_file_menu).to eq(expected)
|
255
|
+
end
|
256
|
+
|
257
|
+
class AuthorizationComparator < ::TreeBranch::Comparator
|
258
|
+
def valid?
|
259
|
+
data.right.nil? || Array(context[:rights]).include?(data.right)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'should compute state: passive for read-only authorization' do
|
264
|
+
no_file_menu = ::TreeBranch.process(
|
265
|
+
node: menu,
|
266
|
+
comparators: [StateComparator, AuthorizationComparator],
|
267
|
+
context: { state: :passive }
|
268
|
+
) do |data, children, _context|
|
269
|
+
::TreeBranch::SimpleNode.new(data: data, children: children)
|
270
|
+
end
|
271
|
+
|
272
|
+
expected = ::TreeBranch::SimpleNode.new(
|
273
|
+
data: { name: 'Menu' },
|
274
|
+
children: [
|
275
|
+
{
|
276
|
+
data: { name: 'File' },
|
277
|
+
children: [
|
278
|
+
{ data: { name: 'Open', command: :open } },
|
279
|
+
{ data: { name: 'Close', command: :close } },
|
280
|
+
{
|
281
|
+
data: { name: 'Print', command: :print },
|
282
|
+
children: [
|
283
|
+
{ data: { name: 'Print' } },
|
284
|
+
{ data: { name: 'Print Preview' } }
|
285
|
+
]
|
286
|
+
}
|
287
|
+
]
|
288
|
+
},
|
289
|
+
{
|
290
|
+
data: { name: 'Edit' }
|
291
|
+
}
|
292
|
+
]
|
293
|
+
)
|
294
|
+
|
295
|
+
expect(no_file_menu).to eq(expected)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should compute state: passive for read/write authorization' do
|
299
|
+
no_file_menu = ::TreeBranch.process(
|
300
|
+
node: menu,
|
301
|
+
comparators: [StateComparator, AuthorizationComparator],
|
302
|
+
context: { state: :passive, rights: :write }
|
303
|
+
) do |data, children, _context|
|
304
|
+
::TreeBranch::SimpleNode.new(data: data, children: children)
|
305
|
+
end
|
306
|
+
|
307
|
+
expected = ::TreeBranch::SimpleNode.new(
|
308
|
+
data: { name: 'Menu' },
|
309
|
+
children: [
|
310
|
+
{
|
311
|
+
data: { name: 'File' },
|
312
|
+
children: [
|
313
|
+
{ data: { name: 'Open', command: :open } },
|
314
|
+
{ data: { name: 'Save', command: :save, right: :write } },
|
315
|
+
{ data: { name: 'Close', command: :close } },
|
316
|
+
{
|
317
|
+
data: { name: 'Print', command: :print },
|
318
|
+
children: [
|
319
|
+
{ data: { name: 'Print' } },
|
320
|
+
{ data: { name: 'Print Preview' } }
|
321
|
+
]
|
322
|
+
}
|
323
|
+
]
|
324
|
+
},
|
325
|
+
{
|
326
|
+
data: { name: 'Edit' }
|
327
|
+
}
|
328
|
+
]
|
329
|
+
)
|
330
|
+
|
331
|
+
expect(no_file_menu).to eq(expected)
|
332
|
+
end
|
333
|
+
|
334
|
+
let(:auth_comparator) do
|
335
|
+
lambda do |data, context|
|
336
|
+
data.right.nil? || Array(context.rights).include?(data.right)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
class MenuItem
|
341
|
+
acts_as_hashable
|
342
|
+
|
343
|
+
attr_reader :menu_items, :name
|
344
|
+
|
345
|
+
def initialize(name: '', menu_items: [])
|
346
|
+
@name = name
|
347
|
+
@menu_items = self.class.array(menu_items)
|
348
|
+
end
|
349
|
+
|
350
|
+
def eql?(other)
|
351
|
+
name == other.name && menu_items == other.menu_items
|
352
|
+
end
|
353
|
+
|
354
|
+
def ==(other)
|
355
|
+
eql?(other)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'should compute state: passive for read/write authorization and return MenuItem(s)' do
|
360
|
+
passive_read_write_menu =
|
361
|
+
::TreeBranch.process(
|
362
|
+
node: menu,
|
363
|
+
comparators: [StateComparator, auth_comparator],
|
364
|
+
context: { state: :passive, rights: :write }
|
365
|
+
) { |data, children, _context| MenuItem.new(name: data.name, menu_items: children) }
|
366
|
+
|
367
|
+
expected = MenuItem.new(
|
368
|
+
name: 'Menu',
|
369
|
+
menu_items: [
|
370
|
+
{
|
371
|
+
name: 'File',
|
372
|
+
menu_items: [
|
373
|
+
{ name: 'Open' },
|
374
|
+
{ name: 'Save' },
|
375
|
+
{ name: 'Close' },
|
376
|
+
{
|
377
|
+
name: 'Print',
|
378
|
+
menu_items: [
|
379
|
+
{ name: 'Print' },
|
380
|
+
{ name: 'Print Preview' }
|
381
|
+
]
|
382
|
+
}
|
383
|
+
]
|
384
|
+
},
|
385
|
+
{
|
386
|
+
name: 'Edit'
|
387
|
+
}
|
388
|
+
]
|
389
|
+
)
|
390
|
+
|
391
|
+
expect(passive_read_write_menu).to eq(expected)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|