smartdown 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|