smartdown 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/lib/smartdown/engine/conditional_resolver.rb +1 -9
  2. data/lib/smartdown/engine/interpolator.rb +12 -1
  3. data/lib/smartdown/engine/state.rb +1 -10
  4. data/lib/smartdown/engine/transition.rb +2 -6
  5. data/lib/smartdown/engine.rb +2 -3
  6. data/lib/smartdown/model/predicate/combined.rb +9 -1
  7. data/lib/smartdown/model/predicate/comparison/base.rb +4 -1
  8. data/lib/smartdown/model/predicate/comparison/greater.rb +6 -1
  9. data/lib/smartdown/model/predicate/comparison/greater_or_equal.rb +6 -1
  10. data/lib/smartdown/model/predicate/comparison/less.rb +6 -1
  11. data/lib/smartdown/model/predicate/comparison/less_or_equal.rb +6 -1
  12. data/lib/smartdown/model/predicate/equality.rb +9 -1
  13. data/lib/smartdown/model/predicate/function.rb +28 -0
  14. data/lib/smartdown/model/predicate/named.rb +9 -1
  15. data/lib/smartdown/model/predicate/otherwise.rb +15 -0
  16. data/lib/smartdown/model/predicate/set_membership.rb +9 -1
  17. data/lib/smartdown/parser/node_transform.rb +14 -0
  18. data/lib/smartdown/parser/predicates.rb +18 -1
  19. data/lib/smartdown/version.rb +1 -1
  20. data/spec/acceptance/parsing_spec.rb +8 -0
  21. data/spec/engine/interpolator_spec.rb +14 -0
  22. data/spec/engine/state_spec.rb +0 -37
  23. data/spec/engine/transition_spec.rb +23 -19
  24. data/spec/engine_spec.rb +8 -11
  25. data/spec/fixtures/acceptance/animal-example-simple/animal-example-simple.txt +25 -0
  26. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_safe_pet.txt +69 -0
  27. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_tigers_are_fine.txt +9 -0
  28. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_trained_with_lions.txt +3 -0
  29. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_untrained_with_lions.txt +3 -0
  30. data/spec/fixtures/acceptance/animal-example-simple/questions/question_1.txt +13 -0
  31. data/spec/fixtures/acceptance/animal-example-simple/questions/question_2.txt +10 -0
  32. data/spec/model/predicates/combined_spec.rb +47 -0
  33. data/spec/model/predicates/comparison_spec.rb +162 -0
  34. data/spec/model/predicates/equality_spec.rb +36 -0
  35. data/spec/model/predicates/function_spec.rb +85 -0
  36. data/spec/model/predicates/named_spec.rb +41 -0
  37. data/spec/model/predicates/set_membership_spec.rb +39 -0
  38. data/spec/parser/predicates_spec.rb +98 -16
  39. data/spec/support/model_builder.rb +4 -0
  40. metadata +39 -14
  41. data/lib/smartdown/engine/predicate_evaluator.rb +0 -27
  42. data/spec/engine/predicate_evaluator_spec.rb +0 -284
@@ -0,0 +1,25 @@
1
+ meta_description: Animals eh?
2
+ satisfies_need: 100982
3
+ status: draft
4
+
5
+ # A simple example of smartdown
6
+
7
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
8
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
9
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
10
+ consequat.
11
+
12
+ Duis aute irure dolor in reprehenderit in voluptate velit esse
13
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
14
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
15
+
16
+ ## Before you start, you will need
17
+
18
+ * To know the kind of feline you have
19
+ * Any animal training certification documents
20
+
21
+ [start: question_1]
22
+
23
+ ## Devolved body
24
+
25
+ Which is really just body content after the start button
@@ -0,0 +1,69 @@
1
+ # You picked a safe pet
2
+
3
+ Good call.
4
+
5
+ # Markdown/Govspeak examples
6
+
7
+ ## H2 example
8
+
9
+ With answers, that are smart. Down with that?
10
+
11
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
12
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
13
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
14
+
15
+ ### H3 example
16
+
17
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
18
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
19
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
20
+
21
+ ## List
22
+
23
+ * one
24
+ * two
25
+ * three
26
+
27
+ ## List 2
28
+
29
+ - A
30
+ - B
31
+ - C
32
+
33
+ ## List 3
34
+
35
+ + Up
36
+ + Down
37
+ + Left
38
+ + Right
39
+
40
+ ^This is an information callout.^
41
+
42
+ %This is a warning callout - use it when there’s a risk of getting a fine or going to prison.%
43
+
44
+ $E
45
+ **Example**
46
+ Your income for the last tax year was £30,000. You think your income will drop to £20,000 this tax year. Enter £22,500 into the calculator.
47
+ $E
48
+
49
+ {::highlight-answer}
50
+ The VAT rate is *20%*
51
+ {:/highlight-answer}
52
+
53
+
54
+ $C
55
+ **Student Finance England**
56
+ Telephone: 0845 300 5090
57
+ Minicom: 0845 604 4434
58
+ [Find out about call charges](http://www.gov.uk/call-charges)
59
+ $C
60
+
61
+ $A
62
+ Hercules House
63
+ Hercules Road
64
+ London SE1 7DU
65
+ $A
66
+
67
+ $D
68
+ [Download the 'Example application form' (PDF, 35KB)](http://example.com/ "Example form")
69
+ $D
@@ -0,0 +1,9 @@
1
+ # Tigers are fine
2
+
3
+ You haven't trained with tigers, but you'll be alright, probably.
4
+
5
+ [next_steps]
6
+ ###You bought a tiger? Seriously?
7
+
8
+ [Your nearest hospital](https://gov.uk/hospital)
9
+ [end_next_steps]
@@ -0,0 +1,3 @@
1
+ # You've trained with lions
2
+
3
+ You'll be alright, I think.
@@ -0,0 +1,3 @@
1
+ # You're not trained with lions
2
+
3
+ Nice to know you.
@@ -0,0 +1,13 @@
1
+ # Amazing page title
2
+
3
+ # What type of feline do you have?
4
+
5
+ Check the serial number by the tail.
6
+
7
+ [choice: question_1]
8
+ * lion: Lion
9
+ * tiger: Tiger
10
+ * cat: Cat
11
+
12
+ * question_1 in {lion tiger} => question_2
13
+ * otherwise => outcome_safe_pet
@@ -0,0 +1,10 @@
1
+ # Are you trained for lions?
2
+
3
+ [choice: trained_for_lions]
4
+ * yes: Yes
5
+ * no: No
6
+
7
+ * question_1 is 'lion'
8
+ * trained_for_lions is 'yes' => outcome_trained_with_lions
9
+ * otherwise => outcome_untrained_with_lions
10
+ * otherwise => outcome_tigers_are_fine
@@ -0,0 +1,47 @@
1
+ require 'smartdown/model/predicate/combined'
2
+ require 'smartdown/model/predicate/combined'
3
+ require 'smartdown/model/predicate/named'
4
+ require 'smartdown/engine/state'
5
+
6
+ describe Smartdown::Model::Predicate::Combined do
7
+ let(:predicate_1) { Smartdown::Model::Predicate::Named.new("my_pred?") }
8
+ let(:predicate_2) { Smartdown::Model::Predicate::Named.new("my_other_pred?") }
9
+ subject(:predicate) { described_class.new([predicate_1, predicate_2])}
10
+
11
+ describe "#evaluate" do
12
+
13
+ context "both states are true" do
14
+ let(:state) {
15
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => true )
16
+ }
17
+
18
+ it "evaluates as true" do
19
+ expect(predicate.evaluate(state)).to eq(true)
20
+ end
21
+ end
22
+
23
+ context "both states are false" do
24
+ let(:state) {
25
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => false, "my_other_pred?" => false )
26
+ }
27
+
28
+ it "evaluates as false" do
29
+ expect(predicate.evaluate(state)).to eq(false)
30
+ end
31
+ end
32
+
33
+ context "one of the states is false" do
34
+ let(:state) {
35
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => false )
36
+ }
37
+
38
+ it "evaluates as false" do
39
+ expect(predicate.evaluate(state)).to eq(false)
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#humanize" do
45
+ it { expect(predicate.humanize).to eq("(my_pred? AND my_other_pred?)") }
46
+ end
47
+ end
@@ -0,0 +1,162 @@
1
+ require 'smartdown/model/predicate/comparison/greater_or_equal'
2
+ require 'smartdown/model/predicate/comparison/greater'
3
+ require 'smartdown/model/predicate/comparison/less_or_equal'
4
+ require 'smartdown/model/predicate/comparison/less'
5
+ require 'smartdown/engine/state'
6
+
7
+ describe "comparison predicates" do
8
+ context "comparison predicates with integers" do
9
+ let(:greater_predicate) { Smartdown::Model::Predicate::Comparison::Greater.new("my_var", "5") }
10
+ let(:greater_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("my_var", "5") }
11
+ let(:less_predicate) { Smartdown::Model::Predicate::Comparison::Less.new("my_var", "5") }
12
+ let(:less_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::LessOrEqual.new("my_var", "5") }
13
+ let(:predicates) { {
14
+ :greater => greater_predicate,
15
+ :greater_or_equal => greater_or_equal_predicate,
16
+ :less => less_predicate,
17
+ :less_or_equal => less_or_equal_predicate
18
+ } }
19
+
20
+ describe "#evaluate" do
21
+ context "state missing expected variable" do
22
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
23
+
24
+ it "raises an UndefinedValue error" do
25
+ predicates.values.each do |predicate|
26
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
27
+ end
28
+ end
29
+ end
30
+
31
+ context "state has lower value" do
32
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "4") }
33
+ let(:results) { {
34
+ :greater => false,
35
+ :greater_or_equal => false,
36
+ :less => true,
37
+ :less_or_equal => true
38
+ } }
39
+ it "evaluates correctly" do
40
+ predicates.each do |predicate_key, predicate|
41
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
42
+ end
43
+ end
44
+ end
45
+
46
+ context "state has identical value" do
47
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "5") }
48
+ let(:results) { {
49
+ :greater => false,
50
+ :greater_or_equal => true,
51
+ :less => false,
52
+ :less_or_equal => true
53
+ } }
54
+ it "evaluates correctly" do
55
+ predicates.each do |predicate_key, predicate|
56
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
57
+ end
58
+ end
59
+ end
60
+
61
+ context "state has higher value" do
62
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "6") }
63
+ let(:results) { {
64
+ :greater => true,
65
+ :greater_or_equal => true,
66
+ :less => false,
67
+ :less_or_equal => false
68
+ } }
69
+ it "evaluates correctly" do
70
+ predicates.each do |predicate_key, predicate|
71
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "#humanize" do
78
+ let(:results) { {
79
+ :greater => "my_var > 5",
80
+ :greater_or_equal => "my_var >= 5",
81
+ :less => "my_var < 5",
82
+ :less_or_equal => "my_var <= 5"
83
+ } }
84
+
85
+ it "has the right human representation" do
86
+ predicates.each do |predicate_key, predicate|
87
+ expect(predicate.humanize).to eq(results[predicate_key])
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ context "comparison predicates with dates" do
94
+ let(:greater_predicate) { Smartdown::Model::Predicate::Comparison::Greater.new("my_var", "2014-1-31") }
95
+ let(:greater_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("my_var", "2014-1-31") }
96
+ let(:less_predicate) { Smartdown::Model::Predicate::Comparison::Less.new("my_var", "2014-1-31") }
97
+ let(:less_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::LessOrEqual.new("my_var", "2014-1-31") }
98
+ let(:predicates) { {
99
+ :greater => greater_predicate,
100
+ :greater_or_equal => greater_or_equal_predicate,
101
+ :less => less_predicate,
102
+ :less_or_equal => less_or_equal_predicate
103
+ } }
104
+
105
+ describe "#evaluate" do
106
+ context "state missing expected variable" do
107
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
108
+
109
+ it "raises an UndefinedValue error" do
110
+ predicates.values.each do |predicate|
111
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
112
+ end
113
+ end
114
+ end
115
+
116
+ context "state has lower value" do
117
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-1-30") }
118
+ let(:results) { {
119
+ :greater => false,
120
+ :greater_or_equal => false,
121
+ :less => true,
122
+ :less_or_equal => true
123
+ } }
124
+ it "evaluates correctly" do
125
+ predicates.each do |predicate_key, predicate|
126
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
127
+ end
128
+ end
129
+ end
130
+
131
+ context "state has identical value" do
132
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-1-31") }
133
+ let(:results) { {
134
+ :greater => false,
135
+ :greater_or_equal => true,
136
+ :less => false,
137
+ :less_or_equal => true
138
+ } }
139
+ it "evaluates correctly" do
140
+ predicates.each do |predicate_key, predicate|
141
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
142
+ end
143
+ end
144
+ end
145
+
146
+ context "state has higher value" do
147
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-2-1") }
148
+ let(:results) { {
149
+ :greater => true,
150
+ :greater_or_equal => true,
151
+ :less => false,
152
+ :less_or_equal => false
153
+ } }
154
+ it "evaluates correctly" do
155
+ predicates.each do |predicate_key, predicate|
156
+ expect(predicate.evaluate(state)).to eq(results[predicate_key])
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,36 @@
1
+ require 'smartdown/model/predicate/equality'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::Equality do
5
+ subject(:predicate) { described_class.new("my_var", "some value") }
6
+
7
+ describe "#evaluate" do
8
+ context "state missing expected variable" do
9
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
10
+
11
+ it "raises an UndefinedValue error" do
12
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
13
+ end
14
+ end
15
+
16
+ context "state has expected variable with same value" do
17
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some value") }
18
+
19
+ it "evaluates to true" do
20
+ expect(predicate.evaluate(state)).to eq(true)
21
+ end
22
+ end
23
+
24
+ context "state has expected variable with a different value" do
25
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some other value") }
26
+
27
+ it "evaluates to false" do
28
+ expect(predicate.evaluate(state)).to eq(false)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "#humanize" do
34
+ it { expect(predicate.humanize).to eq("my_var == 'some value'") }
35
+ end
36
+ end
@@ -0,0 +1,85 @@
1
+ require 'smartdown/model/predicate/function'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::Function do
5
+ let(:function_name) { "my_function" }
6
+ subject(:predicate) { described_class.new(function_name, []) }
7
+
8
+ describe "#evaluate" do
9
+ context "state missing expected function definition" do
10
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
11
+
12
+ it "raises an UndefinedValue error" do
13
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
14
+ end
15
+ end
16
+
17
+ context "with no arguments" do
18
+ let(:function_return) { "hello" }
19
+ let(:my_function) { ->() { "hello" } }
20
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", function_name => my_function) }
21
+
22
+ it "gets the return value of the function" do
23
+ expect(predicate.evaluate(state)).to eq(function_return)
24
+ end
25
+ end
26
+
27
+ context "with arguments" do
28
+ let(:my_function) { ->(x) { x * 100 } }
29
+ subject(:predicate) { described_class.new(function_name, ["number"]) }
30
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", "number" => 3, function_name => my_function) }
31
+
32
+ it "gets the return value of the function" do
33
+ expect(predicate.evaluate(state)).to eq(300)
34
+ end
35
+ end
36
+
37
+ context "with many arguments" do
38
+ let(:function_return) { "hello" }
39
+ let(:my_function) { ->(name, age) { "Hi #{name}, you were #{age-20}, 20 years ago" } }
40
+ subject(:predicate) { described_class.new(function_name, ["name", "age"]) }
41
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", "name" => "David", "age" => 30, function_name => my_function) }
42
+
43
+ it "gets the return value of the function" do
44
+ expect(predicate.evaluate(state)).to eq("Hi David, you were 10, 20 years ago")
45
+ end
46
+ end
47
+
48
+ context "with nested functions" do
49
+ # nesting looks like: function_1(function_2(5))
50
+ let(:function_1) { ->(x) { x - 1 } }
51
+ let(:function_2) { ->(x) { x * 100 } }
52
+ subject(:predicate) {
53
+ described_class.new("function_1", [
54
+ described_class.new("function_2", ['number'])
55
+ ])
56
+ }
57
+
58
+ let(:state) { Smartdown::Engine::State.new(
59
+ current_node: "n",
60
+ "function_1" => function_1,
61
+ "function_2" => function_2,
62
+ "number" => 5
63
+ ) }
64
+
65
+ it "gets the return value of the function" do
66
+ expect(predicate.evaluate(state)).to eq(499)
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#humanize" do
72
+ context "with no arguments" do
73
+ subject(:predicate) { described_class.new(function_name, []) }
74
+
75
+ it { expect(predicate.humanize).to eq("my_function()") }
76
+ end
77
+
78
+ context "with some arguments" do
79
+ let(:function_name) { "my_function" }
80
+ subject(:predicate) { described_class.new(function_name, ['foo', 'bar']) }
81
+
82
+ it { expect(predicate.humanize).to eq("my_function(foo bar)") }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,41 @@
1
+ require 'smartdown/model/predicate/named'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::Named do
5
+ let(:predicate_name) { "my_pred?" }
6
+ subject(:predicate) { described_class.new(predicate_name) }
7
+
8
+ describe "#evaluate" do
9
+ context "state missing predicate definition" do
10
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
11
+
12
+ it "raises an UndefinedValue error" do
13
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
14
+ end
15
+ end
16
+
17
+ context "state has predicate definition" do
18
+ let(:state) {
19
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true )
20
+ }
21
+
22
+ it "fetches the predicate value from the state" do
23
+ expect(predicate.evaluate(state)).to eq(true)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#humanize" do
29
+ context "predicate name without question mark" do
30
+ subject(:predicate) { described_class.new("my_pred") }
31
+
32
+ it { expect(predicate.humanize).to eq("my_pred?") }
33
+ end
34
+
35
+ context "predicate name with question mark" do
36
+ subject(:predicate) { described_class.new("my_pred?") }
37
+
38
+ it { expect(predicate.humanize).to eq("my_pred?") }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ require 'smartdown/model/predicate/set_membership'
2
+ require 'smartdown/engine/state'
3
+
4
+ describe Smartdown::Model::Predicate::SetMembership do
5
+ let(:expected_values) { ["v1", "v2", "v3"] }
6
+ let(:varname) { "my_var" }
7
+ subject(:predicate) { described_class.new(varname, expected_values) }
8
+
9
+ describe "#evaluate" do
10
+ context "state missing expected variable" do
11
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
12
+
13
+ it "raises an UndefinedValue error" do
14
+ expect { predicate.evaluate(state) }.to raise_error(Smartdown::Engine::UndefinedValue)
15
+ end
16
+ end
17
+
18
+ context "state has expected variable with one of the expected values" do
19
+ it "evaluates to true" do
20
+ expected_values.each do |value|
21
+ state = Smartdown::Engine::State.new(current_node: "n", varname => value)
22
+ expect(predicate.evaluate(state)).to eq(true)
23
+ end
24
+ end
25
+ end
26
+
27
+ context "state has expected variable with a value not in the list of expected values" do
28
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", varname => "some other value") }
29
+
30
+ it "evaluates to false" do
31
+ expect(predicate.evaluate(state)).to eq(false)
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "#humanize" do
37
+ it { expect(predicate.humanize).to eq("my_var in [v1, v2, v3]") }
38
+ end
39
+ end