smartdown 0.3.0 → 0.4.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/lib/smartdown/engine/conditional_resolver.rb +1 -9
- data/lib/smartdown/engine/interpolator.rb +12 -1
- data/lib/smartdown/engine/state.rb +1 -10
- data/lib/smartdown/engine/transition.rb +2 -6
- data/lib/smartdown/engine.rb +2 -3
- data/lib/smartdown/model/predicate/combined.rb +9 -1
- data/lib/smartdown/model/predicate/comparison/base.rb +4 -1
- data/lib/smartdown/model/predicate/comparison/greater.rb +6 -1
- data/lib/smartdown/model/predicate/comparison/greater_or_equal.rb +6 -1
- data/lib/smartdown/model/predicate/comparison/less.rb +6 -1
- data/lib/smartdown/model/predicate/comparison/less_or_equal.rb +6 -1
- data/lib/smartdown/model/predicate/equality.rb +9 -1
- data/lib/smartdown/model/predicate/function.rb +28 -0
- data/lib/smartdown/model/predicate/named.rb +9 -1
- data/lib/smartdown/model/predicate/otherwise.rb +15 -0
- data/lib/smartdown/model/predicate/set_membership.rb +9 -1
- data/lib/smartdown/parser/node_transform.rb +14 -0
- data/lib/smartdown/parser/predicates.rb +18 -1
- data/lib/smartdown/version.rb +1 -1
- data/spec/acceptance/parsing_spec.rb +8 -0
- data/spec/engine/interpolator_spec.rb +14 -0
- data/spec/engine/state_spec.rb +0 -37
- data/spec/engine/transition_spec.rb +23 -19
- data/spec/engine_spec.rb +8 -11
- data/spec/fixtures/acceptance/animal-example-simple/animal-example-simple.txt +25 -0
- data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_safe_pet.txt +69 -0
- data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_tigers_are_fine.txt +9 -0
- data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_trained_with_lions.txt +3 -0
- data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_untrained_with_lions.txt +3 -0
- data/spec/fixtures/acceptance/animal-example-simple/questions/question_1.txt +13 -0
- data/spec/fixtures/acceptance/animal-example-simple/questions/question_2.txt +10 -0
- data/spec/model/predicates/combined_spec.rb +47 -0
- data/spec/model/predicates/comparison_spec.rb +162 -0
- data/spec/model/predicates/equality_spec.rb +36 -0
- data/spec/model/predicates/function_spec.rb +85 -0
- data/spec/model/predicates/named_spec.rb +41 -0
- data/spec/model/predicates/set_membership_spec.rb +39 -0
- data/spec/parser/predicates_spec.rb +98 -16
- data/spec/support/model_builder.rb +4 -0
- metadata +39 -14
- data/lib/smartdown/engine/predicate_evaluator.rb +0 -27
- data/spec/engine/predicate_evaluator_spec.rb +0 -284
@@ -1,12 +1,6 @@
|
|
1
|
-
require 'smartdown/engine/predicate_evaluator'
|
2
|
-
|
3
1
|
module Smartdown
|
4
2
|
class Engine
|
5
3
|
class ConditionalResolver
|
6
|
-
def initialize(predicate_evaluator = nil)
|
7
|
-
@predicate_evaluator = predicate_evaluator || PredicateEvaluator.new
|
8
|
-
end
|
9
|
-
|
10
4
|
def call(node, state)
|
11
5
|
node.dup.tap do |new_node|
|
12
6
|
new_node.elements = resolve_conditionals(node.elements, state)
|
@@ -14,10 +8,8 @@ module Smartdown
|
|
14
8
|
end
|
15
9
|
|
16
10
|
private
|
17
|
-
attr_accessor :predicate_evaluator
|
18
|
-
|
19
11
|
def evaluate(conditional, state)
|
20
|
-
if
|
12
|
+
if conditional.predicate.evaluate(state)
|
21
13
|
conditional.true_case
|
22
14
|
else
|
23
15
|
conditional.false_case
|
@@ -39,7 +39,18 @@ module Smartdown
|
|
39
39
|
|
40
40
|
private
|
41
41
|
def interpolate(text, state)
|
42
|
-
text.to_s.gsub(/%{([^}]+)}/)
|
42
|
+
text.to_s.gsub(/%{([^}]+)}/) do |_|
|
43
|
+
resolve_term($1, state)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def resolve_term(interpolation, state)
|
48
|
+
begin
|
49
|
+
parsed = Smartdown::Parser::Predicates.new.parse(interpolation)
|
50
|
+
Smartdown::Parser::NodeTransform.new.apply(parsed, {}).evaluate(state)
|
51
|
+
rescue Parslet::ParseFailed
|
52
|
+
state.get(interpolation)
|
53
|
+
end
|
43
54
|
end
|
44
55
|
end
|
45
56
|
|
@@ -22,12 +22,7 @@ module Smartdown
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def get(key)
|
25
|
-
|
26
|
-
if value.respond_to?(:call)
|
27
|
-
evaluate(value)
|
28
|
-
else
|
29
|
-
value
|
30
|
-
end
|
25
|
+
fetch(key)
|
31
26
|
end
|
32
27
|
|
33
28
|
def put(name, value)
|
@@ -54,10 +49,6 @@ module Smartdown
|
|
54
49
|
raise UndefinedValue, "variable '#{key}' not defined", caller
|
55
50
|
end
|
56
51
|
end
|
57
|
-
|
58
|
-
def evaluate(callable)
|
59
|
-
@cached[callable] ||= callable.call(self)
|
60
|
-
end
|
61
52
|
end
|
62
53
|
end
|
63
54
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'smartdown/engine/predicate_evaluator'
|
2
1
|
require 'smartdown/engine/errors'
|
3
2
|
|
4
3
|
module Smartdown
|
@@ -10,7 +9,6 @@ module Smartdown
|
|
10
9
|
@state = state
|
11
10
|
@node = node
|
12
11
|
@inputs = input_array
|
13
|
-
@predicate_evaluator = options[:predicate_evaluator] || PredicateEvaluator.new
|
14
12
|
end
|
15
13
|
|
16
14
|
def next_node
|
@@ -27,8 +25,6 @@ module Smartdown
|
|
27
25
|
end
|
28
26
|
|
29
27
|
private
|
30
|
-
attr_reader :predicate_evaluator
|
31
|
-
|
32
28
|
def next_node_from_next_node_rules
|
33
29
|
next_node_rules && first_matching_rule(next_node_rules.rules).outcome
|
34
30
|
end
|
@@ -60,11 +56,11 @@ module Smartdown
|
|
60
56
|
rules.each do |rule|
|
61
57
|
case rule
|
62
58
|
when Smartdown::Model::Rule
|
63
|
-
if
|
59
|
+
if rule.predicate.evaluate(state_with_inputs)
|
64
60
|
throw(:match, rule)
|
65
61
|
end
|
66
62
|
when Smartdown::Model::NestedRule
|
67
|
-
if
|
63
|
+
if rule.predicate.evaluate(state_with_inputs)
|
68
64
|
throw_first_matching_rule_in(rule.children)
|
69
65
|
end
|
70
66
|
else
|
data/lib/smartdown/engine.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'smartdown/engine/transition'
|
2
2
|
require 'smartdown/engine/state'
|
3
3
|
require 'smartdown/engine/node_presenter'
|
4
|
+
require 'smartdown/model/predicate/otherwise'
|
4
5
|
|
5
6
|
module Smartdown
|
6
7
|
class Engine
|
@@ -20,9 +21,7 @@ module Smartdown
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def default_predicates
|
23
|
-
{
|
24
|
-
otherwise: ->(_) { true }
|
25
|
-
}.merge(@initial_state)
|
24
|
+
{}.merge(@initial_state)
|
26
25
|
end
|
27
26
|
|
28
27
|
def process(responses, test_start_state = nil)
|
@@ -1,7 +1,15 @@
|
|
1
1
|
module Smartdown
|
2
2
|
module Model
|
3
3
|
module Predicate
|
4
|
-
Combined = Struct.new(:predicates)
|
4
|
+
Combined = Struct.new(:predicates) do
|
5
|
+
def evaluate(state)
|
6
|
+
predicates.map { |predicate| predicate.evaluate(state) }.all?
|
7
|
+
end
|
8
|
+
|
9
|
+
def humanize
|
10
|
+
"(#{predicates.map { |p| p.humanize }.join(' AND ')})"
|
11
|
+
end
|
12
|
+
end
|
5
13
|
end
|
6
14
|
end
|
7
15
|
end
|
@@ -6,13 +6,18 @@ module Smartdown
|
|
6
6
|
module Predicate
|
7
7
|
module Comparison
|
8
8
|
class Greater < Base
|
9
|
-
def evaluate(
|
9
|
+
def evaluate(state)
|
10
|
+
variable = state.get(varname)
|
10
11
|
if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
|
11
12
|
Date.parse(variable) > Date.parse(value)
|
12
13
|
else
|
13
14
|
variable > value
|
14
15
|
end
|
15
16
|
end
|
17
|
+
|
18
|
+
def humanize
|
19
|
+
"#{varname} > #{value}"
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -6,13 +6,18 @@ module Smartdown
|
|
6
6
|
module Predicate
|
7
7
|
module Comparison
|
8
8
|
class GreaterOrEqual < Base
|
9
|
-
def evaluate(
|
9
|
+
def evaluate(state)
|
10
|
+
variable = state.get(varname)
|
10
11
|
if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
|
11
12
|
Date.parse(variable) >= Date.parse(value)
|
12
13
|
else
|
13
14
|
variable >= value
|
14
15
|
end
|
15
16
|
end
|
17
|
+
|
18
|
+
def humanize
|
19
|
+
"#{varname} >= #{value}"
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -6,13 +6,18 @@ module Smartdown
|
|
6
6
|
module Predicate
|
7
7
|
module Comparison
|
8
8
|
class Less < Base
|
9
|
-
def evaluate(
|
9
|
+
def evaluate(state)
|
10
|
+
variable = state.get(varname)
|
10
11
|
if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
|
11
12
|
Date.parse(variable) < Date.parse(value)
|
12
13
|
else
|
13
14
|
variable < value
|
14
15
|
end
|
15
16
|
end
|
17
|
+
|
18
|
+
def humanize
|
19
|
+
"#{varname} < #{value}"
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -6,13 +6,18 @@ module Smartdown
|
|
6
6
|
module Predicate
|
7
7
|
module Comparison
|
8
8
|
class LessOrEqual < Base
|
9
|
-
def evaluate(
|
9
|
+
def evaluate(state)
|
10
|
+
variable = state.get(varname)
|
10
11
|
if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
|
11
12
|
Date.parse(variable) <= Date.parse(value)
|
12
13
|
else
|
13
14
|
variable <= value
|
14
15
|
end
|
15
16
|
end
|
17
|
+
|
18
|
+
def humanize
|
19
|
+
"#{varname} <= #{value}"
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -1,7 +1,15 @@
|
|
1
1
|
module Smartdown
|
2
2
|
module Model
|
3
3
|
module Predicate
|
4
|
-
Equality = Struct.new(:varname, :expected_value)
|
4
|
+
Equality = Struct.new(:varname, :expected_value) do
|
5
|
+
def evaluate(state)
|
6
|
+
state.get(varname) == expected_value
|
7
|
+
end
|
8
|
+
|
9
|
+
def humanize
|
10
|
+
"#{varname} == '#{expected_value}'"
|
11
|
+
end
|
12
|
+
end
|
5
13
|
end
|
6
14
|
end
|
7
15
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Smartdown
|
2
|
+
module Model
|
3
|
+
module Predicate
|
4
|
+
Function = Struct.new(:name, :arguments) do
|
5
|
+
def evaluate(state)
|
6
|
+
state.get(name).call(*evaluate_arguments(state))
|
7
|
+
end
|
8
|
+
|
9
|
+
def humanize
|
10
|
+
"#{name}(#{arguments.join(' ')})"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def evaluate_arguments(state)
|
16
|
+
# Should have an evaluatable "variable" object that matches identifier
|
17
|
+
arguments.map do |argument|
|
18
|
+
if argument.respond_to?(:evaluate)
|
19
|
+
argument.evaluate(state)
|
20
|
+
else
|
21
|
+
state.get(argument)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,7 +1,15 @@
|
|
1
1
|
module Smartdown
|
2
2
|
module Model
|
3
3
|
module Predicate
|
4
|
-
SetMembership = Struct.new(:varname, :values)
|
4
|
+
SetMembership = Struct.new(:varname, :values) do
|
5
|
+
def evaluate(state)
|
6
|
+
values.include?(state.get(varname))
|
7
|
+
end
|
8
|
+
|
9
|
+
def humanize
|
10
|
+
"#{varname} in [#{values.join(", ")}]"
|
11
|
+
end
|
12
|
+
end
|
5
13
|
end
|
6
14
|
end
|
7
15
|
end
|
@@ -16,6 +16,7 @@ require 'smartdown/model/predicate/equality'
|
|
16
16
|
require 'smartdown/model/predicate/set_membership'
|
17
17
|
require 'smartdown/model/predicate/named'
|
18
18
|
require 'smartdown/model/predicate/combined'
|
19
|
+
require 'smartdown/model/predicate/function'
|
19
20
|
require 'smartdown/model/predicate/comparison/greater_or_equal'
|
20
21
|
require 'smartdown/model/predicate/comparison/greater'
|
21
22
|
require 'smartdown/model/predicate/comparison/less_or_equal'
|
@@ -120,11 +121,24 @@ module Smartdown
|
|
120
121
|
Smartdown::Model::Predicate::Named.new(name)
|
121
122
|
}
|
122
123
|
|
124
|
+
rule(:otherwise_predicate => simple(:name) ) {
|
125
|
+
Smartdown::Model::Predicate::Otherwise.new
|
126
|
+
}
|
123
127
|
|
124
128
|
rule(:combined_predicate => {first_predicate: subtree(:first_predicate), and_predicates: subtree(:and_predicates) }) {
|
125
129
|
Smartdown::Model::Predicate::Combined.new([first_predicate]+and_predicates)
|
126
130
|
}
|
127
131
|
|
132
|
+
rule(:function_argument => simple(:argument)) { argument }
|
133
|
+
|
134
|
+
rule(:function_predicate => { name: simple(:name), arguments: subtree(:arguments) }) {
|
135
|
+
Smartdown::Model::Predicate::Function.new(name, Array(arguments))
|
136
|
+
}
|
137
|
+
|
138
|
+
rule(:function_predicate => { name: simple(:name) }) {
|
139
|
+
Smartdown::Model::Predicate::Function.new(name, [])
|
140
|
+
}
|
141
|
+
|
128
142
|
rule(:comparison_predicate => { varname: simple(:varname),
|
129
143
|
value: simple(:value),
|
130
144
|
operator: simple(:operator)
|
@@ -34,13 +34,18 @@ module Smartdown
|
|
34
34
|
}
|
35
35
|
|
36
36
|
rule(:named_predicate) {
|
37
|
-
|
37
|
+
(identifier >> str('?')).as(:named_predicate)
|
38
|
+
}
|
39
|
+
|
40
|
+
rule(:otherwise_predicate) {
|
41
|
+
str('otherwise').as(:otherwise_predicate)
|
38
42
|
}
|
39
43
|
|
40
44
|
rule(:predicate) {
|
41
45
|
equality_predicate.as(:equality_predicate) |
|
42
46
|
set_membership_predicate.as(:set_membership_predicate) |
|
43
47
|
comparison_predicate.as(:comparison_predicate) |
|
48
|
+
otherwise_predicate |
|
44
49
|
named_predicate
|
45
50
|
}
|
46
51
|
|
@@ -50,8 +55,20 @@ module Smartdown
|
|
50
55
|
predicate).repeat(1).as(:and_predicates)
|
51
56
|
}
|
52
57
|
|
58
|
+
rule(:function_arguments) {
|
59
|
+
identifier.as(:function_argument) >> (some_space >> identifier.as(:function_argument)).repeat
|
60
|
+
}
|
61
|
+
|
62
|
+
rule (:function_predicate) {
|
63
|
+
identifier.as(:name) >>
|
64
|
+
str('(') >>
|
65
|
+
function_arguments.as(:arguments).maybe >>
|
66
|
+
str(')')
|
67
|
+
}
|
68
|
+
|
53
69
|
rule (:predicates) {
|
54
70
|
combined_predicate.as(:combined_predicate) |
|
71
|
+
function_predicate.as(:function_predicate) |
|
55
72
|
predicate
|
56
73
|
}
|
57
74
|
|
data/lib/smartdown/version.rb
CHANGED
@@ -104,6 +104,14 @@ EXPECTED
|
|
104
104
|
expect(flow.nodes.map(&:name)).to eq(["question-and-outcome", "q1", "o1"])
|
105
105
|
end
|
106
106
|
end
|
107
|
+
|
108
|
+
context "full flow example" do
|
109
|
+
subject(:flow) { Smartdown.parse(fixture("animal-example-simple")) }
|
110
|
+
|
111
|
+
it "parses" do
|
112
|
+
expect(flow.instance_of? Smartdown::Model::Flow).to eq(true)
|
113
|
+
end
|
114
|
+
end
|
107
115
|
end
|
108
116
|
|
109
117
|
|
@@ -78,4 +78,18 @@ describe Smartdown::Engine::Interpolator do
|
|
78
78
|
expect(interpolated_node.elements.first.content).to eq("Hello #{example_name}")
|
79
79
|
end
|
80
80
|
end
|
81
|
+
|
82
|
+
context "a paragraph containing function call" do
|
83
|
+
let(:elements) { [Smartdown::Model::Element::MarkdownParagraph.new('%{double(number)}')] }
|
84
|
+
let(:state) {
|
85
|
+
Smartdown::Engine::State.new(
|
86
|
+
current_node: node.name,
|
87
|
+
number: 10,
|
88
|
+
double: ->(number) { number * 2 }
|
89
|
+
)
|
90
|
+
}
|
91
|
+
it "interpolates the result of the function call" do
|
92
|
+
expect(interpolated_node.elements.first.content).to eq("20")
|
93
|
+
end
|
94
|
+
end
|
81
95
|
end
|
data/spec/engine/state_spec.rb
CHANGED
@@ -48,43 +48,6 @@ describe Smartdown::Engine::State do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
context "lambda values" do
|
52
|
-
let(:predicate) { double("predicate", call: true) }
|
53
|
-
subject(:state) {
|
54
|
-
described_class.new(current_node: :start_state, predicate?: predicate)
|
55
|
-
}
|
56
|
-
|
57
|
-
describe "#get" do
|
58
|
-
it "evaluates lambda with state" do
|
59
|
-
expect(predicate).to receive(:call).with(state).and_return(true)
|
60
|
-
expect(state.get(:predicate?)).to eq(true)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "caches the result of evaluating the lambda" do
|
64
|
-
expect(predicate).to receive(:call).once
|
65
|
-
state.get(:predicate?)
|
66
|
-
state.get(:predicate?)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
describe "#==" do
|
71
|
-
let(:l1) { ->(state) { true } }
|
72
|
-
let(:l2) { ->(state) { true } }
|
73
|
-
|
74
|
-
let(:state_with_l1a) { described_class.new(current_node: "red", pred: l1)}
|
75
|
-
let(:state_with_l1b) { described_class.new(current_node: "red", pred: l1)}
|
76
|
-
let(:state_with_l2) { described_class.new(current_node: "red", pred: l2)}
|
77
|
-
|
78
|
-
it "is equal if identical lambdas" do
|
79
|
-
expect(state_with_l1a).to eq(state_with_l1b)
|
80
|
-
end
|
81
|
-
|
82
|
-
it "is not equal if different lambdas" do
|
83
|
-
expect(state_with_l1a).not_to eq(state_with_l2)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
51
|
describe "#==" do
|
89
52
|
let(:s1) { described_class.new(current_node: "red") }
|
90
53
|
let(:s2) { described_class.new(current_node: "red") }
|
@@ -8,8 +8,7 @@ describe Smartdown::Engine::Transition do
|
|
8
8
|
let(:start_state) { Smartdown::Engine::State.new(current_node: current_node_name) }
|
9
9
|
let(:input) { "yes" }
|
10
10
|
let(:input_array) { [input] }
|
11
|
-
subject(:transition) { described_class.new(start_state, current_node, input_array
|
12
|
-
let(:predicate_evaluator) { instance_double("Smartdown::Engine::PredicateEvaluator", evaluate: true) }
|
11
|
+
subject(:transition) { described_class.new(start_state, current_node, input_array) }
|
13
12
|
let(:state_including_input) {
|
14
13
|
start_state.put(current_node.name, input_array)
|
15
14
|
}
|
@@ -65,22 +64,22 @@ describe Smartdown::Engine::Transition do
|
|
65
64
|
}
|
66
65
|
|
67
66
|
describe "#next_node" do
|
68
|
-
it "
|
69
|
-
expect(
|
67
|
+
it "evaluates the predicates with state including the input value" do
|
68
|
+
expect(predicate1).to receive(:evaluate).with(state_including_input).and_return(true)
|
70
69
|
|
71
70
|
transition.next_node
|
72
71
|
end
|
73
72
|
|
74
|
-
it "
|
75
|
-
allow(
|
76
|
-
expect(
|
73
|
+
it "evaluates the predicates for each rule in turn" do
|
74
|
+
allow(predicate1).to receive(:evaluate).with(state_including_input).and_return(false)
|
75
|
+
expect(predicate2).to receive(:evaluate).with(state_including_input).and_return(true)
|
77
76
|
|
78
77
|
transition.next_node
|
79
78
|
end
|
80
79
|
|
81
80
|
it "returns the name of the next node" do
|
82
|
-
allow(
|
83
|
-
allow(
|
81
|
+
allow(predicate1).to receive(:evaluate).with(state_including_input).and_return(false)
|
82
|
+
allow(predicate2).to receive(:evaluate).with(state_including_input).and_return(true)
|
84
83
|
|
85
84
|
expect(transition.next_node).to eq(outcome_name2)
|
86
85
|
end
|
@@ -94,6 +93,8 @@ describe Smartdown::Engine::Transition do
|
|
94
93
|
.put(:current_node, outcome_name1)
|
95
94
|
.put(current_node.name, input_array)
|
96
95
|
|
96
|
+
allow(predicate1).to receive(:evaluate).with(state_including_input).and_return(true)
|
97
|
+
|
97
98
|
expect(transition.next_state).to eq(expected_state)
|
98
99
|
end
|
99
100
|
end
|
@@ -130,27 +131,27 @@ describe Smartdown::Engine::Transition do
|
|
130
131
|
|
131
132
|
describe "#next_node" do
|
132
133
|
context "p1 false" do
|
133
|
-
it "
|
134
|
-
expect(
|
134
|
+
it "evaluates each predicate in turn" do
|
135
|
+
expect(predicate1).to receive(:evaluate).with(state_including_input).and_return(false)
|
135
136
|
|
136
137
|
expect { transition.next_node }.to raise_error(Smartdown::Engine::IndeterminateNextNode)
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
140
141
|
context "p1 and p2 true" do
|
141
|
-
it "
|
142
|
-
allow(
|
143
|
-
expect(
|
142
|
+
it "evaluates each predicate in turn" do
|
143
|
+
allow(predicate1).to receive(:evaluate).with(state_including_input).and_return(true)
|
144
|
+
expect(predicate2).to receive(:evaluate).with(state_including_input).and_return(true)
|
144
145
|
|
145
146
|
expect(transition.next_node).to eq(outcome_name1)
|
146
147
|
end
|
147
148
|
end
|
148
149
|
|
149
150
|
context "p1 true, p2 false, p3 true" do
|
150
|
-
it "
|
151
|
-
allow(
|
152
|
-
allow(
|
153
|
-
expect(
|
151
|
+
it "evaluates each predicate in turn" do
|
152
|
+
allow(predicate1).to receive(:evaluate).with(state_including_input).and_return(true)
|
153
|
+
allow(predicate2).to receive(:evaluate).with(state_including_input).and_return(false)
|
154
|
+
expect(predicate3).to receive(:evaluate).with(state_including_input).and_return(true)
|
154
155
|
|
155
156
|
expect(transition.next_node).to eq(outcome_name2)
|
156
157
|
end
|
@@ -160,6 +161,7 @@ describe Smartdown::Engine::Transition do
|
|
160
161
|
|
161
162
|
context "next node rules and a named question" do
|
162
163
|
let(:question_name) { "my_question" }
|
164
|
+
let(:predicate1) { double("predicate1") }
|
163
165
|
|
164
166
|
let(:current_node) {
|
165
167
|
Smartdown::Model::Node.new(
|
@@ -167,7 +169,7 @@ describe Smartdown::Engine::Transition do
|
|
167
169
|
[
|
168
170
|
Smartdown::Model::Element::Question::MultipleChoice.new(question_name, {"a" => "Apple"}),
|
169
171
|
Smartdown::Model::NextNodeRules.new(
|
170
|
-
[Smartdown::Model::Rule.new(
|
172
|
+
[Smartdown::Model::Rule.new(predicate1, "o1")]
|
171
173
|
)
|
172
174
|
]
|
173
175
|
)
|
@@ -175,6 +177,8 @@ describe Smartdown::Engine::Transition do
|
|
175
177
|
|
176
178
|
describe "#next_state" do
|
177
179
|
it "assigns the input value to a variable matching the question name" do
|
180
|
+
allow(predicate1).to receive(:evaluate).and_return(true)
|
181
|
+
|
178
182
|
expect(transition.next_state.get(question_name)).to eq(input)
|
179
183
|
end
|
180
184
|
end
|
data/spec/engine_spec.rb
CHANGED
@@ -5,12 +5,6 @@ describe Smartdown::Engine do
|
|
5
5
|
subject(:engine) { Smartdown::Engine.new(flow) }
|
6
6
|
let(:start_state) {
|
7
7
|
engine.build_start_state
|
8
|
-
.put(:eea_passport?, ->(state) {
|
9
|
-
%w{greek british}.include?(state.get(:what_passport_do_you_have?))
|
10
|
-
})
|
11
|
-
.put(:imaginary?, ->(state) {
|
12
|
-
%w{narnia}.include?(state.get(:what_country_are_you_going_to?))
|
13
|
-
})
|
14
8
|
.put(:otherwise, true)
|
15
9
|
}
|
16
10
|
|
@@ -34,7 +28,7 @@ describe Smartdown::Engine do
|
|
34
28
|
)
|
35
29
|
next_node_rules do
|
36
30
|
rule do
|
37
|
-
|
31
|
+
set_membership_predicate("what_passport_do_you_have?", ["greek", "british"])
|
38
32
|
outcome("outcome_no_visa_needed")
|
39
33
|
end
|
40
34
|
end
|
@@ -85,11 +79,11 @@ describe Smartdown::Engine do
|
|
85
79
|
)
|
86
80
|
next_node_rules do
|
87
81
|
rule do
|
88
|
-
|
82
|
+
set_membership_predicate("what_country_are_you_going_to?", ["narnia"])
|
89
83
|
outcome("outcome_imaginary_country")
|
90
84
|
end
|
91
85
|
rule do
|
92
|
-
|
86
|
+
set_membership_predicate("what_passport_do_you_have?", ["greek", "british"])
|
93
87
|
outcome("outcome_no_visa_needed")
|
94
88
|
end
|
95
89
|
end
|
@@ -118,16 +112,19 @@ describe Smartdown::Engine do
|
|
118
112
|
}
|
119
113
|
|
120
114
|
describe "initial_state" do
|
115
|
+
let(:lambda) {
|
116
|
+
->(state) { 'a_dynamic_state_object' }
|
117
|
+
}
|
121
118
|
let(:initial_state) { {
|
122
119
|
key_1: 'a_state_object',
|
123
|
-
key_2:
|
120
|
+
key_2: :lambda
|
124
121
|
} }
|
125
122
|
let(:engine) { Smartdown::Engine.new(flow, initial_state) }
|
126
123
|
subject(:state) { engine.process([]) }
|
127
124
|
|
128
125
|
it "should have added initial_state to state" do
|
129
126
|
expect(subject.get(:key_1)).to eql 'a_state_object'
|
130
|
-
expect(subject.get(:key_2)).to eql
|
127
|
+
expect(subject.get(:key_2)).to eql :lambda
|
131
128
|
end
|
132
129
|
end
|
133
130
|
|