smartdown 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -132,7 +132,7 @@ passport_country:
132
132
  [country: passport_country]
133
133
  ```
134
134
 
135
- ### Date (tbd)
135
+ ### Date
136
136
 
137
137
  ```markdown
138
138
  ## What is the baby’s due date?
@@ -140,8 +140,6 @@ passport_country:
140
140
  [date: baby_due_date]
141
141
  ```
142
142
 
143
- Asks for a specific date in the given range. Ranges can be expressed as a relative number of years or absolute dates in YYYY-MM-DD format.
144
-
145
143
  ### Value (tbd)
146
144
 
147
145
  ```markdown
@@ -158,14 +156,12 @@ Asks for an arbitrary text input.
158
156
 
159
157
  Asks for a numerical input which can have decimals and optional thousand-separating commas.
160
158
 
161
- ### Salary (tbd)
159
+ ### Salary
162
160
 
163
161
  ```markdown
164
162
  [salary: salary_value]
165
163
  ```
166
164
 
167
- Asks for salary which can be expressed as either a weekly or monthly money amount. The user chooses between weekly/monthly
168
-
169
165
  ### Checkbox (tbd)
170
166
 
171
167
  ```markdown
@@ -0,0 +1,8 @@
1
+ require 'smartdown/api/question'
2
+
3
+ module Smartdown
4
+ module Api
5
+ class DateQuestion < Question
6
+ end
7
+ end
8
+ end
@@ -69,6 +69,10 @@ module Smartdown
69
69
  nodes.select{ |node| node.is_a? Smartdown::Api::Outcome}
70
70
  end
71
71
 
72
+ def coversheet
73
+ @coversheet ||= Smartdown::Api::Coversheet.new(@smartdown_flow.coversheet)
74
+ end
75
+
72
76
  private
73
77
 
74
78
  def transform_node(node)
@@ -85,10 +89,6 @@ module Smartdown
85
89
  end
86
90
  end
87
91
 
88
- def coversheet
89
- @coversheet ||= Smartdown::Api::Coversheet.new(@smartdown_flow.coversheet)
90
- end
91
-
92
92
  def front_matter
93
93
  @front_matter ||= coversheet.front_matter
94
94
  end
@@ -9,12 +9,12 @@ module Smartdown
9
9
  headings = node_elements.select { |element|
10
10
  element.is_a? Smartdown::Model::Element::MarkdownHeading
11
11
  }
12
- @title = headings.first.content.to_s if headings.first
13
12
  nb_questions = node_elements.select{ |element|
14
- element.is_a? Smartdown::Model::Element::Question::MultipleChoice
13
+ element.class.to_s.include?("Smartdown::Model::Element::Question")
15
14
  }.count
16
15
  if headings.count > nb_questions
17
16
  node_elements.delete(headings.first) #Remove page title
17
+ @title = headings.first.content.to_s
18
18
  end
19
19
  @elements = node_elements
20
20
  @front_matter = node.front_matter
@@ -5,12 +5,16 @@ module Smartdown
5
5
 
6
6
  def_delegators :@question, :title, :options
7
7
 
8
- attr_reader :response
8
+ attr_reader :response, :question
9
9
 
10
10
  def initialize(elements, response)
11
11
  @response = response
12
12
  if elements.find{|element| element.is_a? Smartdown::Model::Element::Question::MultipleChoice}
13
13
  @question = MultipleChoice.new(elements)
14
+ elsif elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Date}
15
+ @question = DateQuestion.new(elements)
16
+ elsif elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Salary}
17
+ @question = SalaryQuestion.new(elements)
14
18
  end
15
19
  end
16
20
 
@@ -13,7 +13,7 @@ module Smartdown
13
13
  }
14
14
  @title = headings.first.content.to_s if headings.first
15
15
  nb_questions = node_elements.select{ |element|
16
- element.is_a? Smartdown::Model::Element::Question::MultipleChoice
16
+ element.class.to_s.include?("Smartdown::Model::Element::Question")
17
17
  }.count
18
18
  if headings.count > nb_questions
19
19
  node_elements.delete(headings.first) #Remove page title
@@ -1,4 +1,6 @@
1
1
  require 'smartdown/api/multiple_choice'
2
+ require 'smartdown/api/date_question'
3
+ require 'smartdown/api/salary_question'
2
4
 
3
5
  module Smartdown
4
6
  module Api
@@ -9,6 +11,10 @@ module Smartdown
9
11
  end.map do |question_element_group|
10
12
  if question_element_group.find{|element| element.is_a? Smartdown::Model::Element::Question::MultipleChoice}
11
13
  Smartdown::Api::MultipleChoice.new(question_element_group)
14
+ elsif question_element_group.find{|element| element.is_a? Smartdown::Model::Element::Question::Date}
15
+ Smartdown::Api::DateQuestion.new(question_element_group)
16
+ elsif question_element_group.find{|element| element.is_a? Smartdown::Model::Element::Question::Salary}
17
+ Smartdown::Api::SalaryQuestion.new(question_element_group)
12
18
  end
13
19
  end
14
20
  end
@@ -0,0 +1,8 @@
1
+ require 'smartdown/api/question'
2
+
3
+ module Smartdown
4
+ module Api
5
+ class SalaryQuestion < Question
6
+ end
7
+ end
8
+ end
@@ -14,6 +14,10 @@ module Smartdown
14
14
  ->(state) { predicate.values.include?(state.get(predicate.varname)) }
15
15
  when Smartdown::Model::Predicate::Named
16
16
  ->(state) { state.get(predicate.name) }
17
+ when Smartdown::Model::Predicate::Comparison::Base
18
+ ->(state) { predicate.evaluate(state.get(predicate.varname)) }
19
+ when Smartdown::Model::Predicate::Combined
20
+ ->(state) { predicate.predicates.map { |p| evaluate(p, state) }.all? }
17
21
  else
18
22
  raise "Unknown predicate type #{predicate.class}"
19
23
  end
@@ -0,0 +1,9 @@
1
+ module Smartdown
2
+ module Model
3
+ module Element
4
+ module Question
5
+ Date = Struct.new(:name)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Smartdown
2
+ module Model
3
+ module Element
4
+ module Question
5
+ Salary = Struct.new(:name)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Smartdown
2
+ module Model
3
+ module Predicate
4
+ Combined = Struct.new(:predicates)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Smartdown
2
+ module Model
3
+ module Predicate
4
+ module Comparison
5
+ Base = Struct.new(:varname, :value)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ require 'smartdown/model/predicate/comparison/base'
2
+ require 'date'
3
+
4
+ module Smartdown
5
+ module Model
6
+ module Predicate
7
+ module Comparison
8
+ class Greater < Base
9
+ def evaluate(variable)
10
+ if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
11
+ Date.parse(variable) > Date.parse(value)
12
+ else
13
+ variable > value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'smartdown/model/predicate/comparison/base'
2
+ require 'date'
3
+
4
+ module Smartdown
5
+ module Model
6
+ module Predicate
7
+ module Comparison
8
+ class GreaterOrEqual < Base
9
+ def evaluate(variable)
10
+ if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
11
+ Date.parse(variable) >= Date.parse(value)
12
+ else
13
+ variable >= value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'smartdown/model/predicate/comparison/base'
2
+ require 'date'
3
+
4
+ module Smartdown
5
+ module Model
6
+ module Predicate
7
+ module Comparison
8
+ class Less < Base
9
+ def evaluate(variable)
10
+ if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
11
+ Date.parse(variable) < Date.parse(value)
12
+ else
13
+ variable < value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ require 'smartdown/model/predicate/comparison/base'
2
+ require 'date'
3
+
4
+ module Smartdown
5
+ module Model
6
+ module Predicate
7
+ module Comparison
8
+ class LessOrEqual < Base
9
+ def evaluate(variable)
10
+ if /(\d{4})-(\d{1,2})-(\d{1,2})/.match(value)
11
+ Date.parse(variable) <= Date.parse(value)
12
+ else
13
+ variable <= value
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class DateQuestion < Base
7
+ rule(:date_question) {
8
+ (
9
+ str("[date:") >>
10
+ optional_space >>
11
+ question_identifier.as(:identifier) >>
12
+ optional_space >>
13
+ str("]") >>
14
+ optional_space >>
15
+ line_ending
16
+ ).as(:date)
17
+ }
18
+ root(:date_question)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ require 'smartdown/parser/base'
2
+
3
+ module Smartdown
4
+ module Parser
5
+ module Element
6
+ class SalaryQuestion < Base
7
+ rule(:salary_question) {
8
+ (
9
+ str("[salary:") >>
10
+ optional_space >>
11
+ question_identifier.as(:identifier) >>
12
+ optional_space >>
13
+ str("]") >>
14
+ optional_space >>
15
+ line_ending
16
+ ).as(:salary)
17
+ }
18
+ root(:salary_question)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -3,6 +3,8 @@ require 'smartdown/parser/rules'
3
3
  require 'smartdown/parser/element/front_matter'
4
4
  require 'smartdown/parser/element/start_button'
5
5
  require 'smartdown/parser/element/multiple_choice_question'
6
+ require 'smartdown/parser/element/date_question'
7
+ require 'smartdown/parser/element/salary_question'
6
8
  require 'smartdown/parser/element/markdown_heading'
7
9
  require 'smartdown/parser/element/markdown_paragraph'
8
10
  require 'smartdown/parser/element/conditional'
@@ -15,6 +17,8 @@ module Smartdown
15
17
  Element::Conditional.new |
16
18
  Element::MarkdownHeading.new |
17
19
  Element::MultipleChoiceQuestion.new |
20
+ Element::DateQuestion.new |
21
+ Element::SalaryQuestion.new |
18
22
  Rules.new |
19
23
  Element::StartButton.new |
20
24
  Element::NextSteps.new |
@@ -5,6 +5,8 @@ require 'smartdown/model/rule'
5
5
  require 'smartdown/model/nested_rule'
6
6
  require 'smartdown/model/next_node_rules'
7
7
  require 'smartdown/model/element/question/multiple_choice'
8
+ require 'smartdown/model/element/question/date'
9
+ require 'smartdown/model/element/question/salary'
8
10
  require 'smartdown/model/element/start_button'
9
11
  require 'smartdown/model/element/markdown_heading'
10
12
  require 'smartdown/model/element/markdown_paragraph'
@@ -13,6 +15,11 @@ require 'smartdown/model/element/next_steps'
13
15
  require 'smartdown/model/predicate/equality'
14
16
  require 'smartdown/model/predicate/set_membership'
15
17
  require 'smartdown/model/predicate/named'
18
+ require 'smartdown/model/predicate/combined'
19
+ require 'smartdown/model/predicate/comparison/greater_or_equal'
20
+ require 'smartdown/model/predicate/comparison/greater'
21
+ require 'smartdown/model/predicate/comparison/less_or_equal'
22
+ require 'smartdown/model/predicate/comparison/less'
16
23
 
17
24
  module Smartdown
18
25
  module Parser
@@ -61,6 +68,18 @@ module Smartdown
61
68
  )
62
69
  }
63
70
 
71
+ rule(:date => {identifier: simple(:identifier)}) {
72
+ Smartdown::Model::Element::Question::Date.new(
73
+ identifier.to_s
74
+ )
75
+ }
76
+
77
+ rule(:salary => {identifier: simple(:identifier)}) {
78
+ Smartdown::Model::Element::Question::Salary.new(
79
+ identifier.to_s
80
+ )
81
+ }
82
+
64
83
  rule(:next_steps => { content: simple(:content) }) {
65
84
  Smartdown::Model::Element::NextSteps.new(content.to_s)
66
85
  }
@@ -92,6 +111,7 @@ module Smartdown
92
111
  }
93
112
 
94
113
  rule(:set_value => simple(:value)) { value }
114
+
95
115
  rule(:set_membership_predicate => { varname: simple(:varname), values: subtree(:values) }) {
96
116
  Smartdown::Model::Predicate::SetMembership.new(varname, values)
97
117
  }
@@ -100,6 +120,29 @@ module Smartdown
100
120
  Smartdown::Model::Predicate::Named.new(name)
101
121
  }
102
122
 
123
+
124
+ rule(:combined_predicate => {first_predicate: subtree(:first_predicate), and_predicates: subtree(:and_predicates) }) {
125
+ Smartdown::Model::Predicate::Combined.new([first_predicate]+and_predicates)
126
+ }
127
+
128
+ rule(:comparison_predicate => { varname: simple(:varname),
129
+ value: simple(:value),
130
+ operator: simple(:operator)
131
+ }) {
132
+ case operator
133
+ when "<="
134
+ Smartdown::Model::Predicate::Comparison::LessOrEqual.new(varname, value)
135
+ when "<"
136
+ Smartdown::Model::Predicate::Comparison::Less.new(varname, value)
137
+ when ">="
138
+ Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new(varname, value)
139
+ when ">"
140
+ Smartdown::Model::Predicate::Comparison::Greater.new(varname, value)
141
+ else
142
+ raise "Comparison operator not recognised"
143
+ end
144
+ }
145
+
103
146
  rule(:rule => {predicate: subtree(:predicate), outcome: simple(:outcome_name) } ) {
104
147
  Smartdown::Model::Rule.new(predicate, outcome_name)
105
148
  }
@@ -9,6 +9,16 @@ module Smartdown
9
9
  str("'") >> match("[^']").repeat.as(:expected_value) >> str("'")
10
10
  }
11
11
 
12
+ rule(:comparison_operator) {
13
+ str('>=') | str('>') | str('<=') | str('<')
14
+ }
15
+
16
+ rule(:comparison_predicate) {
17
+ identifier.as(:varname) >> some_space >>
18
+ comparison_operator.as(:operator) >> some_space >>
19
+ str("'") >> match("[^']").repeat.as(:value) >> str("'")
20
+ }
21
+
12
22
  rule(:set_value) {
13
23
  match('[^\s}]').repeat(1).as(:set_value)
14
24
  }
@@ -26,8 +36,23 @@ module Smartdown
26
36
  rule(:named_predicate) {
27
37
  question_identifier.as(:named_predicate)
28
38
  }
29
- rule(:predicates) {
30
- equality_predicate.as(:equality_predicate) | set_membership_predicate.as(:set_membership_predicate) | named_predicate
39
+
40
+ rule(:predicate) {
41
+ equality_predicate.as(:equality_predicate) |
42
+ set_membership_predicate.as(:set_membership_predicate) |
43
+ comparison_predicate.as(:comparison_predicate) |
44
+ named_predicate
45
+ }
46
+
47
+ rule (:combined_predicate) {
48
+ predicate.as(:first_predicate) >>
49
+ (some_space >> str('AND') >> some_space >>
50
+ predicate).repeat(1).as(:and_predicates)
51
+ }
52
+
53
+ rule (:predicates) {
54
+ combined_predicate.as(:combined_predicate) |
55
+ predicate
31
56
  }
32
57
 
33
58
  root(:predicates)
@@ -1,3 +1,3 @@
1
1
  module Smartdown
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -2,10 +2,15 @@ require 'smartdown/engine/predicate_evaluator'
2
2
  require 'smartdown/model/predicate/equality'
3
3
  require 'smartdown/model/predicate/set_membership'
4
4
  require 'smartdown/model/predicate/named'
5
+ require 'smartdown/model/predicate/combined'
6
+ require 'smartdown/model/predicate/comparison/greater_or_equal'
7
+ require 'smartdown/model/predicate/comparison/greater'
8
+ require 'smartdown/model/predicate/comparison/less_or_equal'
9
+ require 'smartdown/model/predicate/comparison/less'
5
10
  require 'smartdown/engine/state'
6
11
 
7
12
  describe Smartdown::Engine::PredicateEvaluator do
8
- subject(:evalutator) { described_class.new }
13
+ subject(:evaluator) { described_class.new }
9
14
 
10
15
  context "equality predicate" do
11
16
  let(:predicate) { Smartdown::Model::Predicate::Equality.new("my_var", "some value") }
@@ -15,23 +20,23 @@ describe Smartdown::Engine::PredicateEvaluator do
15
20
  let(:state) { Smartdown::Engine::State.new(current_node: "n") }
16
21
 
17
22
  it "raises an UndefinedValue error" do
18
- expect { evalutator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
23
+ expect { evaluator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
19
24
  end
20
25
  end
21
26
 
22
27
  context "state has expected variable with same value" do
23
28
  let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some value") }
24
29
 
25
- it "evalutes to true" do
26
- expect(evalutator.evaluate(predicate, state)).to eq(true)
30
+ it "evaluates to true" do
31
+ expect(evaluator.evaluate(predicate, state)).to eq(true)
27
32
  end
28
33
  end
29
34
 
30
35
  context "state has expected variable with a different value" do
31
36
  let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "some other value") }
32
37
 
33
- it "evalutes to false" do
34
- expect(evalutator.evaluate(predicate, state)).to eq(false)
38
+ it "evaluates to false" do
39
+ expect(evaluator.evaluate(predicate, state)).to eq(false)
35
40
  end
36
41
  end
37
42
  end
@@ -47,15 +52,15 @@ describe Smartdown::Engine::PredicateEvaluator do
47
52
  let(:state) { Smartdown::Engine::State.new(current_node: "n") }
48
53
 
49
54
  it "raises an UndefinedValue error" do
50
- expect { evalutator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
55
+ expect { evaluator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
51
56
  end
52
57
  end
53
58
 
54
59
  context "state has expected variable with one of the expected values" do
55
- it "evalutes to true" do
60
+ it "evaluates to true" do
56
61
  expected_values.each do |value|
57
62
  state = Smartdown::Engine::State.new(current_node: "n", varname => value)
58
- expect(evalutator.evaluate(predicate, state)).to eq(true)
63
+ expect(evaluator.evaluate(predicate, state)).to eq(true)
59
64
  end
60
65
  end
61
66
  end
@@ -63,8 +68,8 @@ describe Smartdown::Engine::PredicateEvaluator do
63
68
  context "state has expected variable with a value not in the list of expected values" do
64
69
  let(:state) { Smartdown::Engine::State.new(current_node: "n", varname => "some other value") }
65
70
 
66
- it "evalutes to false" do
67
- expect(evalutator.evaluate(predicate, state)).to eq(false)
71
+ it "evaluates to false" do
72
+ expect(evaluator.evaluate(predicate, state)).to eq(false)
68
73
  end
69
74
  end
70
75
  end
@@ -79,7 +84,7 @@ describe Smartdown::Engine::PredicateEvaluator do
79
84
  let(:state) { Smartdown::Engine::State.new(current_node: "n") }
80
85
 
81
86
  it "raises an UndefinedValue error" do
82
- expect { evalutator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
87
+ expect { evaluator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
83
88
  end
84
89
  end
85
90
 
@@ -89,10 +94,191 @@ describe Smartdown::Engine::PredicateEvaluator do
89
94
  }
90
95
 
91
96
  it "fetches the predicate value from the state" do
92
- expect(evalutator.evaluate(predicate, state)).to eq(true)
97
+ expect(evaluator.evaluate(predicate, state)).to eq(true)
93
98
  end
94
99
  end
95
100
  end
96
101
  end
97
102
 
103
+ context "combined predicate" do
104
+ let(:predicate_name) { "my_pred?" }
105
+ let(:predicate_name_2) { "my_other_pred?" }
106
+ let(:predicate) { Smartdown::Model::Predicate::Named.new(predicate_name) }
107
+ let(:predicate_2) { Smartdown::Model::Predicate::Named.new(predicate_name_2) }
108
+ let(:combined_predicate) { Smartdown::Model::Predicate::Combined.new([predicate, predicate_2])}
109
+
110
+ describe "#evaluate" do
111
+
112
+ context "both states are true" do
113
+ let(:state) {
114
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => true )
115
+ }
116
+
117
+ it "evaluates as true" do
118
+ expect(evaluator.evaluate(combined_predicate, state)).to eq(true)
119
+ end
120
+ end
121
+
122
+ context "both states are false" do
123
+ let(:state) {
124
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => false, "my_other_pred?" => false )
125
+ }
126
+
127
+ it "evaluates as false" do
128
+ expect(evaluator.evaluate(combined_predicate, state)).to eq(false)
129
+ end
130
+ end
131
+
132
+ context "one of the states is false" do
133
+ let(:state) {
134
+ Smartdown::Engine::State.new("current_node" => "n", "my_pred?" => true, "my_other_pred?" => false )
135
+ }
136
+
137
+ it "evaluates as false" do
138
+ expect(evaluator.evaluate(combined_predicate, state)).to eq(false)
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ context "comparison predicates with integers" do
146
+ let(:greater_predicate) { Smartdown::Model::Predicate::Comparison::Greater.new("my_var", "5") }
147
+ let(:greater_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("my_var", "5") }
148
+ let(:less_predicate) { Smartdown::Model::Predicate::Comparison::Less.new("my_var", "5") }
149
+ let(:less_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::LessOrEqual.new("my_var", "5") }
150
+ let(:predicates) { {
151
+ :greater => greater_predicate,
152
+ :greater_or_equal => greater_or_equal_predicate,
153
+ :less => less_predicate,
154
+ :less_or_equal => less_or_equal_predicate
155
+ } }
156
+
157
+ describe "#evaluate" do
158
+ context "state missing expected variable" do
159
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
160
+
161
+ it "raises an UndefinedValue error" do
162
+ predicates.values.each do |predicate|
163
+ expect { evaluator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
164
+ end
165
+ end
166
+ end
167
+
168
+ context "state has lower value" do
169
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "4") }
170
+ let(:results) { {
171
+ :greater => false,
172
+ :greater_or_equal => false,
173
+ :less => true,
174
+ :less_or_equal => true
175
+ } }
176
+ it "evaluates correctly" do
177
+ predicates.each do |predicate_key, predicate|
178
+ expect(evaluator.evaluate(predicate, state)).to eq(results[predicate_key])
179
+ end
180
+ end
181
+ end
182
+
183
+ context "state has identical value" do
184
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "5") }
185
+ let(:results) { {
186
+ :greater => false,
187
+ :greater_or_equal => true,
188
+ :less => false,
189
+ :less_or_equal => true
190
+ } }
191
+ it "evaluates correctly" do
192
+ predicates.each do |predicate_key, predicate|
193
+ expect(evaluator.evaluate(predicate, state)).to eq(results[predicate_key])
194
+ end
195
+ end
196
+ end
197
+
198
+ context "state has higher value" do
199
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "6") }
200
+ let(:results) { {
201
+ :greater => true,
202
+ :greater_or_equal => true,
203
+ :less => false,
204
+ :less_or_equal => false
205
+ } }
206
+ it "evaluates correctly" do
207
+ predicates.each do |predicate_key, predicate|
208
+ expect(evaluator.evaluate(predicate, state)).to eq(results[predicate_key])
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ context "comparison predicates with dates" do
216
+ let(:greater_predicate) { Smartdown::Model::Predicate::Comparison::Greater.new("my_var", "2014-1-31") }
217
+ let(:greater_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("my_var", "2014-1-31") }
218
+ let(:less_predicate) { Smartdown::Model::Predicate::Comparison::Less.new("my_var", "2014-1-31") }
219
+ let(:less_or_equal_predicate) { Smartdown::Model::Predicate::Comparison::LessOrEqual.new("my_var", "2014-1-31") }
220
+ let(:predicates) { {
221
+ :greater => greater_predicate,
222
+ :greater_or_equal => greater_or_equal_predicate,
223
+ :less => less_predicate,
224
+ :less_or_equal => less_or_equal_predicate
225
+ } }
226
+
227
+ describe "#evaluate" do
228
+ context "state missing expected variable" do
229
+ let(:state) { Smartdown::Engine::State.new(current_node: "n") }
230
+
231
+ it "raises an UndefinedValue error" do
232
+ predicates.values.each do |predicate|
233
+ expect { evaluator.evaluate(predicate, state) }.to raise_error(Smartdown::Engine::UndefinedValue)
234
+ end
235
+ end
236
+ end
237
+
238
+ context "state has lower value" do
239
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-1-30") }
240
+ let(:results) { {
241
+ :greater => false,
242
+ :greater_or_equal => false,
243
+ :less => true,
244
+ :less_or_equal => true
245
+ } }
246
+ it "evaluates correctly" do
247
+ predicates.each do |predicate_key, predicate|
248
+ expect(evaluator.evaluate(predicate, state)).to eq(results[predicate_key])
249
+ end
250
+ end
251
+ end
252
+
253
+ context "state has identical value" do
254
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-1-31") }
255
+ let(:results) { {
256
+ :greater => false,
257
+ :greater_or_equal => true,
258
+ :less => false,
259
+ :less_or_equal => true
260
+ } }
261
+ it "evaluates correctly" do
262
+ predicates.each do |predicate_key, predicate|
263
+ expect(evaluator.evaluate(predicate, state)).to eq(results[predicate_key])
264
+ end
265
+ end
266
+ end
267
+
268
+ context "state has higher value" do
269
+ let(:state) { Smartdown::Engine::State.new(current_node: "n", my_var: "2014-2-1") }
270
+ let(:results) { {
271
+ :greater => true,
272
+ :greater_or_equal => true,
273
+ :less => false,
274
+ :less_or_equal => false
275
+ } }
276
+ it "evaluates correctly" do
277
+ predicates.each do |predicate_key, predicate|
278
+ expect(evaluator.evaluate(predicate, state)).to eq(results[predicate_key])
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
98
284
  end
@@ -0,0 +1,28 @@
1
+ require 'smartdown/parser/node_parser'
2
+ require 'smartdown/parser/node_interpreter'
3
+ require 'smartdown/parser/element/date_question'
4
+
5
+ describe Smartdown::Parser::Element::DateQuestion do
6
+ subject(:parser) { described_class.new }
7
+
8
+ context "with question tag" do
9
+ let(:source) { "[date: date_of_birth]" }
10
+
11
+ it "parses" do
12
+ should parse(source).as(
13
+ date: {
14
+ identifier: "date_of_birth",
15
+ }
16
+ )
17
+ end
18
+
19
+ describe "transformed" do
20
+ let(:node_name) { "my_node" }
21
+ subject(:transformed) {
22
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
23
+ }
24
+
25
+ it { should eq(Smartdown::Model::Element::Question::Date.new("date_of_birth")) }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'smartdown/parser/node_parser'
2
+ require 'smartdown/parser/node_interpreter'
3
+ require 'smartdown/parser/element/salary_question'
4
+
5
+ describe Smartdown::Parser::Element::SalaryQuestion do
6
+ subject(:parser) { described_class.new }
7
+
8
+ context "with question tag" do
9
+ let(:source) { "[salary: mother_salary]" }
10
+
11
+ it "parses" do
12
+ should parse(source).as(
13
+ salary: {
14
+ identifier: "mother_salary",
15
+ }
16
+ )
17
+ end
18
+
19
+ describe "transformed" do
20
+ let(:node_name) { "my_node" }
21
+ subject(:transformed) {
22
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
23
+ }
24
+
25
+ it { should eq(Smartdown::Model::Element::Question::Salary.new("mother_salary")) }
26
+ end
27
+ end
28
+ end
@@ -61,5 +61,84 @@ describe Smartdown::Parser::Predicates do
61
61
  it { should eq(Smartdown::Model::Predicate::Named.new("my_pred")) }
62
62
  end
63
63
  end
64
+
65
+ describe "predicate AND predicate" do
66
+ subject(:parser) { described_class.new }
67
+
68
+ it { should parse("my_pred AND my_other_pred").as(
69
+ { combined_predicate: {
70
+ first_predicate: { named_predicate: "my_pred" },
71
+ and_predicates:
72
+ [
73
+ {named_predicate: "my_other_pred"},
74
+ ]
75
+ } }
76
+ ) }
77
+ it { should parse("my_pred AND my_other_pred AND varname in {a b c}").as(
78
+ { combined_predicate: {
79
+ first_predicate: { named_predicate: "my_pred" },
80
+ and_predicates:
81
+ [
82
+ {named_predicate: "my_other_pred"},
83
+ {set_membership_predicate:
84
+ {varname: "varname", values: [{set_value: "a"}, {set_value: "b"}, {set_value: "c"}]}
85
+ }
86
+ ]
87
+ } }
88
+ ) }
89
+ it { should_not parse("my_pred AND ") }
90
+
91
+ describe "transformed" do
92
+ let(:node_name) { "my_node" }
93
+ let(:source) { "my_pred AND my_other_pred" }
94
+ subject(:transformed) {
95
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
96
+ }
97
+
98
+ it { should eq(Smartdown::Model::Predicate::Combined.new(
99
+ [
100
+ Smartdown::Model::Predicate::Named.new("my_pred"),
101
+ Smartdown::Model::Predicate::Named.new("my_other_pred")
102
+ ]
103
+ )) }
104
+ end
105
+ end
106
+
107
+ describe "comparison predicate" do
108
+ subject(:parser) { described_class.new }
109
+ let(:greater_equal_source) { "varname >= 'value'" }
110
+ let(:greater_source) { "varname > 'value'" }
111
+ let(:less_equal_source) { "varname <= 'value'" }
112
+ let(:less_source) { "varname < 'value'" }
113
+
114
+ it { should parse(greater_equal_source).as(comparison_predicate: {varname: "varname", value: "value", operator: ">="}) }
115
+ it { should_not parse("v >= value") }
116
+ it { should_not parse("v >= 'a thing's thing'") }
117
+ it { should_not parse("v >= 'a thing\\'s thing'") }
118
+ it { should_not parse(%q{v >= "a thing"}) }
119
+
120
+ describe "transformed" do
121
+ let(:node_name) { "my_node" }
122
+ subject(:transformed) {
123
+ Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
124
+ }
125
+ context "greater" do
126
+ let(:source) { greater_equal_source }
127
+ it { should eq(Smartdown::Model::Predicate::Comparison::GreaterOrEqual.new("varname", "value")) }
128
+ end
129
+ context "stricly greater" do
130
+ let(:source) { greater_source }
131
+ it { should eq(Smartdown::Model::Predicate::Comparison::Greater.new("varname", "value")) }
132
+ end
133
+ context "lower" do
134
+ let(:source) { less_equal_source }
135
+ it { should eq(Smartdown::Model::Predicate::Comparison::LessOrEqual.new("varname", "value")) }
136
+ end
137
+ context "strictly lower" do
138
+ let(:source) { less_source }
139
+ it { should eq(Smartdown::Model::Predicate::Comparison::Less.new("varname", "value")) }
140
+ end
141
+ end
142
+ end
64
143
  end
65
144
 
@@ -4,12 +4,19 @@ require 'smartdown/model/element/markdown_heading'
4
4
  require 'smartdown/model/element/markdown_paragraph'
5
5
  require 'smartdown/model/element/start_button'
6
6
  require 'smartdown/model/element/question/multiple_choice'
7
+ require 'smartdown/model/element/question/date'
8
+ require 'smartdown/model/element/question/salary'
7
9
  require 'smartdown/model/element/conditional'
8
10
  require 'smartdown/model/next_node_rules'
9
11
  require 'smartdown/model/rule'
10
12
  require 'smartdown/model/predicate/named'
11
13
  require 'smartdown/model/predicate/equality'
12
14
  require 'smartdown/model/predicate/set_membership'
15
+ require 'smartdown/model/predicate/combined'
16
+ require 'smartdown/model/predicate/comparison/greater_or_equal'
17
+ require 'smartdown/model/predicate/comparison/less_or_equal'
18
+ require 'smartdown/model/predicate/comparison/greater'
19
+ require 'smartdown/model/predicate/comparison/less'
13
20
 
14
21
  class ModelBuilder
15
22
  def flow(name, &block)
@@ -52,6 +59,12 @@ class ModelBuilder
52
59
  @elements.last
53
60
  end
54
61
 
62
+ def date(name)
63
+ @elements ||= []
64
+ @elements << Smartdown::Model::Element::Question::Date.new(name)
65
+ @elements.last
66
+ end
67
+
55
68
  def next_steps(urls)
56
69
  @elements ||= []
57
70
  urls_with_string_keys = ::Hash[urls.map {|k,v| [k.to_s, v]}]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smartdown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2014-07-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parslet
16
- requirement: &18315460 !ruby/object:Gem::Requirement
16
+ requirement: &13419100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.6.1
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *18315460
24
+ version_requirements: *13419100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &18314660 !ruby/object:Gem::Requirement
27
+ requirement: &13417020 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 3.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *18314660
35
+ version_requirements: *13417020
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &18314100 !ruby/object:Gem::Requirement
38
+ requirement: &13819280 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *18314100
46
+ version_requirements: *13819280
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: gem_publisher
49
- requirement: &18313460 !ruby/object:Gem::Requirement
49
+ requirement: &13818580 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *18313460
57
+ version_requirements: *13818580
58
58
  description:
59
59
  email: david.heath@digital.cabinet-office.gov.uk
60
60
  executables:
@@ -71,6 +71,8 @@ files:
71
71
  - lib/smartdown/parser/directory_input.rb
72
72
  - lib/smartdown/parser/rules.rb
73
73
  - lib/smartdown/parser/node_parser.rb
74
+ - lib/smartdown/parser/element/date_question.rb
75
+ - lib/smartdown/parser/element/salary_question.rb
74
76
  - lib/smartdown/parser/element/conditional.rb
75
77
  - lib/smartdown/parser/element/markdown_paragraph.rb
76
78
  - lib/smartdown/parser/element/start_button.rb
@@ -79,8 +81,10 @@ files:
79
81
  - lib/smartdown/parser/element/next_steps.rb
80
82
  - lib/smartdown/parser/element/markdown_heading.rb
81
83
  - lib/smartdown/parser/flow_interpreter.rb
84
+ - lib/smartdown/api/date_question.rb
82
85
  - lib/smartdown/api/coversheet.rb
83
86
  - lib/smartdown/api/question.rb
87
+ - lib/smartdown/api/salary_question.rb
84
88
  - lib/smartdown/api/flow.rb
85
89
  - lib/smartdown/api/question_page.rb
86
90
  - lib/smartdown/api/state.rb
@@ -102,6 +106,12 @@ files:
102
106
  - lib/smartdown/model/predicate/named.rb
103
107
  - lib/smartdown/model/predicate/set_membership.rb
104
108
  - lib/smartdown/model/predicate/equality.rb
109
+ - lib/smartdown/model/predicate/comparison/less_or_equal.rb
110
+ - lib/smartdown/model/predicate/comparison/greater_or_equal.rb
111
+ - lib/smartdown/model/predicate/comparison/base.rb
112
+ - lib/smartdown/model/predicate/comparison/greater.rb
113
+ - lib/smartdown/model/predicate/comparison/less.rb
114
+ - lib/smartdown/model/predicate/combined.rb
105
115
  - lib/smartdown/model/node.rb
106
116
  - lib/smartdown/model/front_matter.rb
107
117
  - lib/smartdown/model/element/conditional.rb
@@ -109,6 +119,8 @@ files:
109
119
  - lib/smartdown/model/element/start_button.rb
110
120
  - lib/smartdown/model/element/next_steps.rb
111
121
  - lib/smartdown/model/element/markdown_heading.rb
122
+ - lib/smartdown/model/element/question/date.rb
123
+ - lib/smartdown/model/element/question/salary.rb
112
124
  - lib/smartdown/model/element/question/multiple_choice.rb
113
125
  - lib/smartdown/model/next_node_rules.rb
114
126
  - lib/smartdown/model/nested_rule.rb
@@ -127,9 +139,11 @@ files:
127
139
  - spec/parser/element/multiple_choice_question_spec.rb
128
140
  - spec/parser/element/conditional_spec.rb
129
141
  - spec/parser/element/start_button_parser_spec.rb
142
+ - spec/parser/element/date_question_spec.rb
130
143
  - spec/parser/element/markdown_paragraph_spec.rb
131
144
  - spec/parser/element/markdown_heading_spec.rb
132
145
  - spec/parser/element/front_matter_spec.rb
146
+ - spec/parser/element/salary_question_spec.rb
133
147
  - spec/parser/node_parser_spec.rb
134
148
  - spec/support/model_builder.rb
135
149
  - spec/spec_helper.rb
@@ -168,7 +182,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
182
  version: '0'
169
183
  segments:
170
184
  - 0
171
- hash: -3191894024105276380
185
+ hash: -352555312520548432
172
186
  required_rubygems_version: !ruby/object:Gem::Requirement
173
187
  none: false
174
188
  requirements:
@@ -177,7 +191,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
191
  version: '0'
178
192
  segments:
179
193
  - 0
180
- hash: -3191894024105276380
194
+ hash: -352555312520548432
181
195
  requirements: []
182
196
  rubyforge_project:
183
197
  rubygems_version: 1.8.11
@@ -196,9 +210,11 @@ test_files:
196
210
  - spec/parser/element/multiple_choice_question_spec.rb
197
211
  - spec/parser/element/conditional_spec.rb
198
212
  - spec/parser/element/start_button_parser_spec.rb
213
+ - spec/parser/element/date_question_spec.rb
199
214
  - spec/parser/element/markdown_paragraph_spec.rb
200
215
  - spec/parser/element/markdown_heading_spec.rb
201
216
  - spec/parser/element/front_matter_spec.rb
217
+ - spec/parser/element/salary_question_spec.rb
202
218
  - spec/parser/node_parser_spec.rb
203
219
  - spec/support/model_builder.rb
204
220
  - spec/spec_helper.rb