smartdown 0.8.1 → 0.8.2
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 +37 -0
- data/lib/smartdown/engine/conditional_resolver.rb +6 -4
- data/lib/smartdown/engine/transition.rb +5 -6
- data/lib/smartdown/model/element/question/date.rb +1 -1
- data/lib/smartdown/model/element/question/multiple_choice.rb +1 -1
- data/lib/smartdown/model/element/question/salary.rb +1 -1
- data/lib/smartdown/model/element/question/text.rb +1 -1
- data/lib/smartdown/parser/base.rb +12 -0
- data/lib/smartdown/parser/element/conditional.rb +8 -1
- data/lib/smartdown/parser/element/date_question.rb +1 -0
- data/lib/smartdown/parser/element/multiple_choice_question.rb +1 -0
- data/lib/smartdown/parser/element/salary_question.rb +1 -0
- data/lib/smartdown/parser/element/text_question.rb +1 -0
- data/lib/smartdown/parser/node_transform.rb +17 -10
- data/lib/smartdown/parser/option_pairs_transform.rb +11 -0
- data/lib/smartdown/version.rb +1 -1
- data/spec/acceptance/flow_spec.rb +4 -0
- data/spec/engine/conditional_resolver_spec.rb +82 -0
- data/spec/engine_spec.rb +6 -1
- data/spec/fixtures/acceptance/animal-example-multiple/questions/question_1.txt +3 -1
- data/spec/fixtures/acceptance/animal-example-multiple/questions/question_2.txt +1 -1
- data/spec/fixtures/acceptance/animal-example-multiple/questions/question_4.txt +10 -0
- data/spec/parser/element/conditional_spec.rb +180 -1
- data/spec/parser/element/date_question_spec.rb +28 -0
- data/spec/parser/element/multiple_choice_question_spec.rb +39 -1
- data/spec/parser/element/salary_question_spec.rb +33 -4
- data/spec/parser/element/text_question_spec.rb +33 -4
- data/spec/parser/node_parser_spec.rb +4 -2
- data/spec/parser/option_pairs_transform_spec.rb +36 -0
- data/spec/support/model_builder.rb +16 -22
- data/spec/support_specs/model_builder_spec.rb +39 -2
- metadata +16 -11
data/README.md
CHANGED
@@ -128,6 +128,18 @@ Asks for an arbitrary text input.
|
|
128
128
|
[salary: salary_value]
|
129
129
|
```
|
130
130
|
|
131
|
+
## Aliases
|
132
|
+
|
133
|
+
An alias lets you referrer to any question identifier by its question intentifer or its alias.
|
134
|
+
|
135
|
+
```markdown
|
136
|
+
## Are you Clark Kent?
|
137
|
+
|
138
|
+
[choice: clark_kent, alias: superman]
|
139
|
+
* yes: Yes
|
140
|
+
* no: No
|
141
|
+
```
|
142
|
+
|
131
143
|
## Next steps
|
132
144
|
|
133
145
|
Markdown to be displayed as part of an outcome to direct the users to other information of potential interest to them.
|
@@ -228,6 +240,31 @@ Text if true
|
|
228
240
|
$ENDIF
|
229
241
|
```
|
230
242
|
|
243
|
+
Similarly, it is also possible to specify an elseif clause. These can be
|
244
|
+
chained together indefinitely. It is also possible to keep an else
|
245
|
+
clause at the end like so:
|
246
|
+
|
247
|
+
```markdown
|
248
|
+
$IF pred1?
|
249
|
+
|
250
|
+
Text if pred1 true
|
251
|
+
|
252
|
+
$ELSEIF pred2?
|
253
|
+
|
254
|
+
Text if pred1 false and pred2 true
|
255
|
+
|
256
|
+
$ELSEIF pred3?
|
257
|
+
|
258
|
+
Text if pred1 and pred2 false, and pred3 true
|
259
|
+
|
260
|
+
$ELSE
|
261
|
+
|
262
|
+
Text if pred1, pred2, pred3 are false
|
263
|
+
|
264
|
+
$ENDIF
|
265
|
+
```
|
266
|
+
|
267
|
+
|
231
268
|
## Interpolation
|
232
269
|
|
233
270
|
It's possible to interpolate values from calculations, responses to questions, plugins etc.
|
@@ -10,20 +10,22 @@ module Smartdown
|
|
10
10
|
private
|
11
11
|
def evaluate(conditional, state)
|
12
12
|
if conditional.predicate.evaluate(state)
|
13
|
-
conditional.true_case
|
13
|
+
selected_branch = conditional.true_case
|
14
14
|
else
|
15
|
-
conditional.false_case
|
15
|
+
selected_branch = conditional.false_case
|
16
16
|
end
|
17
|
+
resolve_conditionals selected_branch, state
|
17
18
|
end
|
18
19
|
|
19
20
|
def resolve_conditionals(elements, state)
|
21
|
+
return unless elements
|
20
22
|
elements.map do |element|
|
21
|
-
if element.is_a?
|
23
|
+
if element.is_a? Smartdown::Model::Element::Conditional
|
22
24
|
evaluate(element, state)
|
23
25
|
else
|
24
26
|
element
|
25
27
|
end
|
26
|
-
end.flatten
|
28
|
+
end.flatten.compact
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -33,10 +33,6 @@ module Smartdown
|
|
33
33
|
node.start_button && node.start_button.start_node
|
34
34
|
end
|
35
35
|
|
36
|
-
def input_variable_names_from_question
|
37
|
-
node.questions.map(&:name)
|
38
|
-
end
|
39
|
-
|
40
36
|
def first_matching_rule(rules)
|
41
37
|
catch(:match) do
|
42
38
|
throw_first_matching_rule_in(rules)
|
@@ -63,8 +59,11 @@ module Smartdown
|
|
63
59
|
|
64
60
|
def state_with_responses
|
65
61
|
result = state.put(node.name, answers.map(&:to_s))
|
66
|
-
|
67
|
-
result = result.put(
|
62
|
+
node.questions.each_with_index do |question, index|
|
63
|
+
result = result.put(question.name, answers[index])
|
64
|
+
if question.alias
|
65
|
+
result = result.put(question.alias, answers[index])
|
66
|
+
end
|
68
67
|
end
|
69
68
|
result
|
70
69
|
end
|
@@ -14,6 +14,8 @@ module Smartdown
|
|
14
14
|
rule(:some_space) { space_char.repeat(1) }
|
15
15
|
rule(:ws) { ws_char.repeat }
|
16
16
|
rule(:non_ws) { non_ws.repeat }
|
17
|
+
rule(:comma) { str(",") }
|
18
|
+
rule(:colon) { str(":") }
|
17
19
|
|
18
20
|
rule(:whitespace_terminated_string) {
|
19
21
|
non_ws_char >> (non_ws_char | space_char.repeat(1) >> non_ws_char).repeat
|
@@ -30,6 +32,16 @@ module Smartdown
|
|
30
32
|
rule(:bullet) {
|
31
33
|
match('[*-]')
|
32
34
|
}
|
35
|
+
|
36
|
+
rule(:option_pair) {
|
37
|
+
comma >>
|
38
|
+
optional_space >>
|
39
|
+
identifier.as(:key) >>
|
40
|
+
colon >>
|
41
|
+
optional_space >>
|
42
|
+
identifier.as(:value) >>
|
43
|
+
optional_space
|
44
|
+
}
|
33
45
|
end
|
34
46
|
end
|
35
47
|
end
|
@@ -19,13 +19,20 @@ module Smartdown
|
|
19
19
|
(markdown_blocks_inside_conditional.as(:false_case) >> newline).maybe
|
20
20
|
}
|
21
21
|
|
22
|
+
rule(:elseif_clause) {
|
23
|
+
str("$ELSEIF ") >> (Predicates.new.as(:predicate) >>
|
24
|
+
optional_space >> newline.repeat(2) >>
|
25
|
+
(markdown_blocks_inside_conditional.as(:true_case) >> newline).maybe >>
|
26
|
+
((elseif_clause | else_clause).maybe)).as(:conditional).repeat(1,1).as(:false_case)
|
27
|
+
}
|
28
|
+
|
22
29
|
rule(:conditional_clause) {
|
23
30
|
(
|
24
31
|
str("$IF ") >>
|
25
32
|
Predicates.new.as(:predicate) >>
|
26
33
|
optional_space >> newline.repeat(2) >>
|
27
34
|
(markdown_blocks_inside_conditional.as(:true_case) >> newline).maybe >>
|
28
|
-
else_clause.maybe >>
|
35
|
+
(else_clause | elseif_clause).maybe >>
|
29
36
|
str("$ENDIF") >> optional_space >> line_ending
|
30
37
|
).as(:conditional)
|
31
38
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'parslet/transform'
|
2
|
+
require 'smartdown/parser/option_pairs_transform'
|
2
3
|
require 'smartdown/model/node'
|
3
4
|
require 'smartdown/model/front_matter'
|
4
5
|
require 'smartdown/model/rule'
|
@@ -64,27 +65,33 @@ module Smartdown
|
|
64
65
|
[url.to_s, label.to_s]
|
65
66
|
}
|
66
67
|
|
67
|
-
|
68
|
+
|
69
|
+
rule(:multiple_choice => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs), options: subtree(:choices)}) {
|
68
70
|
Smartdown::Model::Element::Question::MultipleChoice.new(
|
69
|
-
identifier.to_s,
|
71
|
+
identifier.to_s,
|
72
|
+
Hash[choices],
|
73
|
+
Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('alias', nil),
|
70
74
|
)
|
71
75
|
}
|
72
76
|
|
73
|
-
rule(:date => {identifier: simple(:identifier)}) {
|
77
|
+
rule(:date => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs)}) {
|
74
78
|
Smartdown::Model::Element::Question::Date.new(
|
75
|
-
identifier.to_s
|
79
|
+
identifier.to_s,
|
80
|
+
Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('alias', nil)
|
76
81
|
)
|
77
82
|
}
|
78
83
|
|
79
|
-
rule(:salary => {identifier: simple(:identifier)}) {
|
84
|
+
rule(:salary => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs)}) {
|
80
85
|
Smartdown::Model::Element::Question::Salary.new(
|
81
|
-
identifier.to_s
|
86
|
+
identifier.to_s,
|
87
|
+
Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('alias', nil)
|
82
88
|
)
|
83
89
|
}
|
84
90
|
|
85
|
-
rule(:text => {identifier: simple(:identifier)}) {
|
91
|
+
rule(:text => {identifier: simple(:identifier), :option_pairs => subtree(:option_pairs)}) {
|
86
92
|
Smartdown::Model::Element::Question::Text.new(
|
87
|
-
identifier.to_s
|
93
|
+
identifier.to_s,
|
94
|
+
Smartdown::Parser::OptionPairs.transform(option_pairs).fetch('alias', nil)
|
88
95
|
)
|
89
96
|
}
|
90
97
|
|
@@ -146,10 +153,10 @@ module Smartdown
|
|
146
153
|
Smartdown::Model::Predicate::Function.new(name.to_s, [])
|
147
154
|
}
|
148
155
|
|
149
|
-
rule(:comparison_predicate => { varname: simple(:varname),
|
156
|
+
rule(:comparison_predicate => { varname: simple(:varname),
|
150
157
|
value: simple(:value),
|
151
158
|
operator: simple(:operator)
|
152
|
-
}) {
|
159
|
+
}) {
|
153
160
|
case operator
|
154
161
|
when "<="
|
155
162
|
Smartdown::Model::Predicate::Comparison::LessOrEqual.new(varname.to_s, value.to_s)
|
data/lib/smartdown/version.rb
CHANGED
@@ -123,6 +123,10 @@ describe Smartdown::Api::Flow do
|
|
123
123
|
specify { expect(flow.state("y",["lion", "no", "no"]).accepted_responses).to eq ["lion", "no", "no"] }
|
124
124
|
specify { expect(flow.state("y",["lion", "no", "no"]).current_answers).to eq [] }
|
125
125
|
end
|
126
|
+
|
127
|
+
context "with aliased identifier overwritten with later answer" do
|
128
|
+
specify { expect(flow.state("y",["bulldog", "lion", "no", "no"]).current_node.name).to eq "outcome_untrained_with_lions" }
|
129
|
+
end
|
126
130
|
end
|
127
131
|
end
|
128
132
|
|
@@ -57,4 +57,86 @@ describe Smartdown::Engine::ConditionalResolver do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
60
|
+
context "a node with nested conditionals" do
|
61
|
+
let(:node) {
|
62
|
+
model_builder.node("outcome_no_visa_needed") do
|
63
|
+
conditional do
|
64
|
+
named_predicate "pred1?"
|
65
|
+
true_case do
|
66
|
+
paragraph("True case")
|
67
|
+
end
|
68
|
+
false_case do
|
69
|
+
conditional do
|
70
|
+
named_predicate "pred2?"
|
71
|
+
true_case do
|
72
|
+
paragraph("False True case")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
}
|
79
|
+
|
80
|
+
context "first pred is true" do
|
81
|
+
let(:state) {
|
82
|
+
Smartdown::Engine::State.new(
|
83
|
+
current_node: node.name,
|
84
|
+
pred1?: true,
|
85
|
+
pred2?: "Doesn't matter"
|
86
|
+
)
|
87
|
+
}
|
88
|
+
|
89
|
+
let(:expected_node_after_presentation) {
|
90
|
+
model_builder.node("outcome_no_visa_needed") do
|
91
|
+
paragraph("True case")
|
92
|
+
end
|
93
|
+
}
|
94
|
+
|
95
|
+
it "should resolve the conditional and preserve the 'True case' paragraph block" do
|
96
|
+
expect(conditional_resolver.call(node, state)).to eq(expected_node_after_presentation)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
context "first pred is false" do
|
102
|
+
context "second pred is true" do
|
103
|
+
let(:state) {
|
104
|
+
Smartdown::Engine::State.new(
|
105
|
+
current_node: node.name,
|
106
|
+
pred1?: false,
|
107
|
+
pred2?: true
|
108
|
+
)
|
109
|
+
}
|
110
|
+
|
111
|
+
let(:expected_node_after_presentation) {
|
112
|
+
model_builder.node("outcome_no_visa_needed") do
|
113
|
+
paragraph("False True case")
|
114
|
+
end
|
115
|
+
}
|
116
|
+
|
117
|
+
it "should resolve the conditional and preserve the 'True case' paragraph block" do
|
118
|
+
expect(conditional_resolver.call(node, state)).to eq(expected_node_after_presentation)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "second pred is false" do
|
123
|
+
let(:state) {
|
124
|
+
Smartdown::Engine::State.new(
|
125
|
+
current_node: node.name,
|
126
|
+
pred1?: false,
|
127
|
+
pred2?: false
|
128
|
+
)
|
129
|
+
}
|
130
|
+
|
131
|
+
let(:expected_node_after_presentation) {
|
132
|
+
model_builder.node("outcome_no_visa_needed") do
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
it "should resolve the conditional and resolve no false case to be empty" do
|
137
|
+
expect(conditional_resolver.call(node, state)).to eq(expected_node_after_presentation)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
60
142
|
end
|
data/spec/engine_spec.rb
CHANGED
@@ -24,7 +24,8 @@ describe Smartdown::Engine do
|
|
24
24
|
greek: "Greek",
|
25
25
|
british: "British",
|
26
26
|
usa: "USA"
|
27
|
-
}
|
27
|
+
},
|
28
|
+
"passport_type?"
|
28
29
|
)
|
29
30
|
next_node_rules do
|
30
31
|
rule do
|
@@ -181,6 +182,10 @@ describe Smartdown::Engine do
|
|
181
182
|
it "has recorded input" do
|
182
183
|
expect(subject.get("what_passport_do_you_have?")).to eq("greek")
|
183
184
|
end
|
185
|
+
|
186
|
+
it "recorded input is accessiable via question alias" do
|
187
|
+
expect(subject.get("passport_type?")).to eq("greek")
|
188
|
+
end
|
184
189
|
end
|
185
190
|
|
186
191
|
context "USA passport" do
|
@@ -4,12 +4,14 @@
|
|
4
4
|
|
5
5
|
Text before the options.
|
6
6
|
|
7
|
-
[choice: question_1]
|
7
|
+
[choice: question_1, alias: type_of_feline]
|
8
8
|
* lion: Lion
|
9
9
|
* tiger: Tiger
|
10
10
|
* cat: Cat
|
11
|
+
* bulldog: Bulldog
|
11
12
|
|
12
13
|
This is an optional note after question.
|
13
14
|
|
15
|
+
* type_of_feline is 'bulldog' => question_4
|
14
16
|
* question_1 in {lion tiger} => question_2
|
15
17
|
* otherwise => outcome_safe_pet
|
@@ -102,7 +102,6 @@ SOURCE
|
|
102
102
|
}
|
103
103
|
end
|
104
104
|
end
|
105
|
-
end
|
106
105
|
|
107
106
|
context "simple IF-ELSE" do
|
108
107
|
let(:source) { <<-SOURCE
|
@@ -150,5 +149,185 @@ SOURCE
|
|
150
149
|
end
|
151
150
|
end
|
152
151
|
|
152
|
+
context "simple ELSE-IF" do
|
153
|
+
let(:source) { <<-SOURCE
|
154
|
+
$IF pred1?
|
155
|
+
|
156
|
+
#{true1_body}
|
157
|
+
|
158
|
+
$ELSEIF pred2?
|
159
|
+
|
160
|
+
#{true2_body}
|
161
|
+
|
162
|
+
$ENDIF
|
163
|
+
SOURCE
|
164
|
+
}
|
165
|
+
|
166
|
+
context "with one-line true case" do
|
167
|
+
let(:true1_body) { "Text if first predicate is true" }
|
168
|
+
let(:true2_body) { "Text if first predicate is false and the second is true" }
|
169
|
+
|
170
|
+
|
171
|
+
it {
|
172
|
+
should parse(source).as(
|
173
|
+
conditional: {
|
174
|
+
predicate: {named_predicate: "pred1?"},
|
175
|
+
true_case: [{p: "#{true1_body}\n"}],
|
176
|
+
false_case: [{conditional: {
|
177
|
+
predicate: {named_predicate: "pred2?"},
|
178
|
+
true_case: [{p: "#{true2_body}\n"}],
|
179
|
+
}}]
|
180
|
+
}
|
181
|
+
)
|
182
|
+
}
|
183
|
+
|
184
|
+
describe "transformed" do
|
185
|
+
subject(:transformed) {
|
186
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
187
|
+
}
|
188
|
+
|
189
|
+
it {
|
190
|
+
should eq(
|
191
|
+
Smartdown::Model::Element::Conditional.new(
|
192
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
193
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true1_body + "\n")],
|
194
|
+
[Smartdown::Model::Element::Conditional.new(
|
195
|
+
Smartdown::Model::Predicate::Named.new("pred2?"),
|
196
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true2_body + "\n")]
|
197
|
+
)]
|
198
|
+
)
|
199
|
+
)
|
200
|
+
}
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context "double ELSE-IF" do
|
207
|
+
let(:source) { <<-SOURCE
|
208
|
+
$IF pred1?
|
209
|
+
|
210
|
+
#{true1_body}
|
211
|
+
|
212
|
+
$ELSEIF pred2?
|
213
|
+
|
214
|
+
#{true2_body}
|
215
|
+
|
216
|
+
$ELSEIF pred3?
|
217
|
+
|
218
|
+
#{true3_body}
|
219
|
+
|
220
|
+
$ENDIF
|
221
|
+
SOURCE
|
222
|
+
}
|
223
|
+
|
224
|
+
context "with one-line true case" do
|
225
|
+
let(:true1_body) { "Text if first predicate is true" }
|
226
|
+
let(:true2_body) { "Text if first predicate is false and the second is true" }
|
227
|
+
let(:true3_body) { "Text if first and second predicates are false and the third is true" }
|
228
|
+
|
229
|
+
it {
|
230
|
+
should parse(source).as(
|
231
|
+
conditional: {
|
232
|
+
predicate: {named_predicate: "pred1?"},
|
233
|
+
true_case: [{p: "#{true1_body}\n"}],
|
234
|
+
false_case: [{conditional: {
|
235
|
+
predicate: {named_predicate: "pred2?"},
|
236
|
+
true_case: [{p: "#{true2_body}\n"}],
|
237
|
+
false_case: [{conditional: {
|
238
|
+
predicate: {named_predicate: "pred3?"},
|
239
|
+
true_case: [{p: "#{true3_body}\n"}],
|
240
|
+
}}]
|
241
|
+
}}]
|
242
|
+
}
|
243
|
+
)
|
244
|
+
}
|
245
|
+
|
246
|
+
describe "transformed" do
|
247
|
+
subject(:transformed) {
|
248
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
249
|
+
}
|
250
|
+
|
251
|
+
it {
|
252
|
+
should eq(
|
253
|
+
Smartdown::Model::Element::Conditional.new(
|
254
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
255
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true1_body + "\n")],
|
256
|
+
[Smartdown::Model::Element::Conditional.new(
|
257
|
+
Smartdown::Model::Predicate::Named.new("pred2?"),
|
258
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true2_body + "\n")],
|
259
|
+
[Smartdown::Model::Element::Conditional.new(
|
260
|
+
Smartdown::Model::Predicate::Named.new("pred3?"),
|
261
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true3_body + "\n")]
|
262
|
+
)]
|
263
|
+
)]
|
264
|
+
)
|
265
|
+
)
|
266
|
+
}
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context "ELSE-IF with an ELSE" do
|
272
|
+
let(:source) { <<-SOURCE
|
273
|
+
$IF pred1?
|
274
|
+
|
275
|
+
#{true1_body}
|
276
|
+
|
277
|
+
$ELSEIF pred2?
|
278
|
+
|
279
|
+
#{true2_body}
|
280
|
+
|
281
|
+
$ELSE
|
282
|
+
|
283
|
+
#{false_body}
|
284
|
+
|
285
|
+
$ENDIF
|
286
|
+
SOURCE
|
287
|
+
}
|
288
|
+
|
289
|
+
context "with one-line true case" do
|
290
|
+
let(:true1_body) { "Text if first predicate is true" }
|
291
|
+
let(:true2_body) { "Text if first predicate is false and the second is true" }
|
292
|
+
let(:false_body) { "Text both predicates are false" }
|
293
|
+
|
294
|
+
|
295
|
+
it {
|
296
|
+
should parse(source).as(
|
297
|
+
conditional: {
|
298
|
+
predicate: {named_predicate: "pred1?"},
|
299
|
+
true_case: [{p: "#{true1_body}\n"}],
|
300
|
+
false_case: [{conditional: {
|
301
|
+
predicate: {named_predicate: "pred2?"},
|
302
|
+
true_case: [{p: "#{true2_body}\n"}],
|
303
|
+
false_case: [{p: "#{false_body}\n"}]
|
304
|
+
}}]
|
305
|
+
}
|
306
|
+
)
|
307
|
+
}
|
308
|
+
|
309
|
+
|
310
|
+
describe "transformed" do
|
311
|
+
subject(:transformed) {
|
312
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
313
|
+
}
|
314
|
+
|
315
|
+
it {
|
316
|
+
should eq(
|
317
|
+
Smartdown::Model::Element::Conditional.new(
|
318
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
319
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true1_body + "\n")],
|
320
|
+
[Smartdown::Model::Element::Conditional.new(
|
321
|
+
Smartdown::Model::Predicate::Named.new("pred2?"),
|
322
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(true2_body + "\n")],
|
323
|
+
[Smartdown::Model::Element::MarkdownParagraph.new(false_body + "\n")]
|
324
|
+
)]
|
325
|
+
)
|
326
|
+
)
|
327
|
+
}
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
end
|
153
332
|
end
|
154
333
|
|
@@ -12,6 +12,7 @@ describe Smartdown::Parser::Element::DateQuestion do
|
|
12
12
|
should parse(source).as(
|
13
13
|
date: {
|
14
14
|
identifier: "date_of_birth",
|
15
|
+
option_pairs: [],
|
15
16
|
}
|
16
17
|
)
|
17
18
|
end
|
@@ -25,4 +26,31 @@ describe Smartdown::Parser::Element::DateQuestion do
|
|
25
26
|
it { should eq(Smartdown::Model::Element::Question::Date.new("date_of_birth")) }
|
26
27
|
end
|
27
28
|
end
|
29
|
+
|
30
|
+
context "with question tag and an alias" do
|
31
|
+
let(:source) { "[date: date_of_birth, alias: date_for_adoption_or_birth]" }
|
32
|
+
|
33
|
+
it "parses" do
|
34
|
+
should parse(source).as(
|
35
|
+
date: {
|
36
|
+
identifier: "date_of_birth",
|
37
|
+
option_pairs: [
|
38
|
+
{
|
39
|
+
key: 'alias',
|
40
|
+
value: 'date_for_adoption_or_birth',
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "transformed" do
|
48
|
+
let(:node_name) { "my_node" }
|
49
|
+
subject(:transformed) {
|
50
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
51
|
+
}
|
52
|
+
|
53
|
+
it { should eq(Smartdown::Model::Element::Question::Date.new("date_of_birth", "date_for_adoption_or_birth")) }
|
54
|
+
end
|
55
|
+
end
|
28
56
|
end
|
@@ -21,7 +21,8 @@ describe Smartdown::Parser::Element::MultipleChoiceQuestion do
|
|
21
21
|
options: [
|
22
22
|
{value: "yes", label: "Yes"},
|
23
23
|
{value: "no", label: "No"}
|
24
|
-
]
|
24
|
+
],
|
25
|
+
option_pairs: [],
|
25
26
|
}
|
26
27
|
)
|
27
28
|
end
|
@@ -36,6 +37,43 @@ describe Smartdown::Parser::Element::MultipleChoiceQuestion do
|
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
40
|
+
context "with question tag and alias" do
|
41
|
+
let(:source) {
|
42
|
+
[
|
43
|
+
"[choice: yes_or_no, alias: no_or_yes]",
|
44
|
+
"* yes: Yes",
|
45
|
+
"* no: No"
|
46
|
+
].join("\n")
|
47
|
+
}
|
48
|
+
|
49
|
+
it "parses" do
|
50
|
+
should parse(source).as(
|
51
|
+
multiple_choice: {
|
52
|
+
identifier: "yes_or_no",
|
53
|
+
options: [
|
54
|
+
{value: "yes", label: "Yes"},
|
55
|
+
{value: "no", label: "No"},
|
56
|
+
],
|
57
|
+
option_pairs: [
|
58
|
+
{
|
59
|
+
key: 'alias',
|
60
|
+
value: 'no_or_yes',
|
61
|
+
}
|
62
|
+
],
|
63
|
+
}
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "transformed" do
|
68
|
+
let(:node_name) { "my_node" }
|
69
|
+
subject(:transformed) {
|
70
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
71
|
+
}
|
72
|
+
|
73
|
+
it { should eq(Smartdown::Model::Element::Question::MultipleChoice.new("yes_or_no", {"yes"=>"Yes", "no"=>"No"}, "no_or_yes")) }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
39
77
|
context "without question tag" do
|
40
78
|
let(:source) {
|
41
79
|
[
|
@@ -10,10 +10,11 @@ describe Smartdown::Parser::Element::SalaryQuestion do
|
|
10
10
|
|
11
11
|
it "parses" do
|
12
12
|
should parse(source).as(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
salary: {
|
14
|
+
identifier: "mother_salary",
|
15
|
+
option_pairs:[],
|
16
|
+
}
|
17
|
+
)
|
17
18
|
end
|
18
19
|
|
19
20
|
describe "transformed" do
|
@@ -25,4 +26,32 @@ describe Smartdown::Parser::Element::SalaryQuestion do
|
|
25
26
|
it { should eq(Smartdown::Model::Element::Question::Salary.new("mother_salary")) }
|
26
27
|
end
|
27
28
|
end
|
29
|
+
|
30
|
+
context "with question tag and alias" do
|
31
|
+
let(:source) { "[salary: mother_salary, alias: mums_salary]" }
|
32
|
+
|
33
|
+
it "parses" do
|
34
|
+
should parse(source).as(
|
35
|
+
salary: {
|
36
|
+
identifier: "mother_salary",
|
37
|
+
option_pairs: [
|
38
|
+
{
|
39
|
+
key: 'alias',
|
40
|
+
value: 'mums_salary',
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "transformed" do
|
48
|
+
let(:node_name) { "my_node" }
|
49
|
+
subject(:transformed) {
|
50
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
51
|
+
}
|
52
|
+
|
53
|
+
it { should eq(Smartdown::Model::Element::Question::Salary.new("mother_salary", "mums_salary")) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
28
57
|
end
|
@@ -10,10 +10,11 @@ describe Smartdown::Parser::Element::TextQuestion do
|
|
10
10
|
|
11
11
|
it "parses" do
|
12
12
|
should parse(source).as(
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
text: {
|
14
|
+
identifier: "hometown",
|
15
|
+
option_pairs: [],
|
16
|
+
},
|
17
|
+
)
|
17
18
|
end
|
18
19
|
|
19
20
|
describe "transformed" do
|
@@ -25,4 +26,32 @@ describe Smartdown::Parser::Element::TextQuestion do
|
|
25
26
|
it { should eq(Smartdown::Model::Element::Question::Text.new("hometown")) }
|
26
27
|
end
|
27
28
|
end
|
29
|
+
|
30
|
+
context "with question tag and alias" do
|
31
|
+
let(:source) { "[text: hometown, alias: birthplace]" }
|
32
|
+
|
33
|
+
it "parses" do
|
34
|
+
should parse(source).as(
|
35
|
+
text: {
|
36
|
+
identifier: "hometown",
|
37
|
+
option_pairs:[
|
38
|
+
{
|
39
|
+
key: 'alias',
|
40
|
+
value: 'birthplace',
|
41
|
+
}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "transformed" do
|
48
|
+
let(:node_name) { "my_node" }
|
49
|
+
subject(:transformed) {
|
50
|
+
Smartdown::Parser::NodeInterpreter.new(node_name, source, parser: parser).interpret
|
51
|
+
}
|
52
|
+
|
53
|
+
it { should eq(Smartdown::Model::Element::Question::Text.new("hometown", "birthplace")) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
28
57
|
end
|
@@ -75,7 +75,8 @@ SOURCE
|
|
75
75
|
options: [
|
76
76
|
{value: "yes", label: "Yes"},
|
77
77
|
{value: "no", label: "No"}
|
78
|
-
]
|
78
|
+
],
|
79
|
+
option_pairs: []}
|
79
80
|
}
|
80
81
|
]
|
81
82
|
})
|
@@ -106,7 +107,8 @@ SOURCE
|
|
106
107
|
options: [
|
107
108
|
{value: "yes", label: "Yes"},
|
108
109
|
{value: "no", label: "No"}
|
109
|
-
]
|
110
|
+
],
|
111
|
+
option_pairs: []}
|
110
112
|
},
|
111
113
|
{h1: "Next node rules"},
|
112
114
|
{next_node_rules: [{rule: {predicate: {named_predicate: "pred1?"}, outcome: "outcome"}}]}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'smartdown/parser/option_pairs_transform'
|
2
|
+
|
3
|
+
describe Smartdown::Parser::OptionPairs do
|
4
|
+
describe '.transform' do
|
5
|
+
subject(:transform) { Smartdown::Parser::OptionPairs.transform(input) }
|
6
|
+
|
7
|
+
context 'blank array' do
|
8
|
+
let(:input) { [] }
|
9
|
+
it 'returns empty hash' do
|
10
|
+
expect(transform).to eql({})
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'single array of option pairs hash passed' do
|
15
|
+
let(:input) { [{key: 'dog', value: 'woof'}] }
|
16
|
+
it 'returns hash containing key value pairs' do
|
17
|
+
expect(transform).to eql({'dog' => 'woof'})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'single array of option pairs hash passed' do
|
22
|
+
let(:input) { [{key: 'dog', value: 'woof'}, {key: 'cat', value: 'meow'}] }
|
23
|
+
it 'returns hash containing key value pairs' do
|
24
|
+
expect(transform).to eql({'dog' => 'woof', 'cat' => 'meow'})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'single array of option pairs hash passed value not string' do
|
29
|
+
let(:input) { [{key: 'number', value: 1}] }
|
30
|
+
it 'returns hash containing key value pairs calling after calling .to_s' do
|
31
|
+
expect(transform).to eql({'number' => '1'})
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -53,10 +53,10 @@ class ModelBuilder
|
|
53
53
|
@elements.last
|
54
54
|
end
|
55
55
|
|
56
|
-
def multiple_choice(name, options)
|
56
|
+
def multiple_choice(name, options, question_alias=nil)
|
57
57
|
@elements ||= []
|
58
58
|
options_with_string_keys = ::Hash[options.map {|k,v| [k.to_s, v]}]
|
59
|
-
@elements << Smartdown::Model::Element::Question::MultipleChoice.new(name, options_with_string_keys)
|
59
|
+
@elements << Smartdown::Model::Element::Question::MultipleChoice.new(name, options_with_string_keys, question_alias)
|
60
60
|
@elements.last
|
61
61
|
end
|
62
62
|
|
@@ -81,52 +81,46 @@ class ModelBuilder
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def rule(predicate = nil, outcome = nil, &block)
|
84
|
-
@predicate = predicate
|
85
|
-
@outcome = outcome
|
84
|
+
@predicate = [predicate].compact
|
85
|
+
@outcome = [outcome].compact
|
86
86
|
@rules ||= []
|
87
87
|
instance_eval(&block) if block_given?
|
88
|
-
@rules << Smartdown::Model::Rule.new(@predicate, @outcome)
|
88
|
+
@rules << Smartdown::Model::Rule.new(@predicate.pop, @outcome.pop)
|
89
89
|
@rules.last
|
90
90
|
end
|
91
91
|
|
92
92
|
def conditional(&block)
|
93
|
-
@predicate
|
94
|
-
@true_case
|
95
|
-
@false_case
|
93
|
+
@predicate ||= []
|
94
|
+
@true_case ||= []
|
95
|
+
@false_case ||= []
|
96
96
|
@elements ||= []
|
97
97
|
instance_eval(&block) if block_given?
|
98
|
-
@elements << Smartdown::Model::Element::Conditional.new(@predicate, @true_case, @false_case)
|
98
|
+
@elements << Smartdown::Model::Element::Conditional.new(@predicate.pop, [@true_case.pop], [@false_case.pop])
|
99
99
|
@elements.last
|
100
100
|
end
|
101
101
|
|
102
102
|
def true_case(&block)
|
103
|
-
@outer_elements = @elements
|
104
|
-
@elements = []
|
105
103
|
instance_eval(&block) if block_given?
|
106
|
-
@true_case
|
107
|
-
@
|
108
|
-
@true_case
|
104
|
+
@true_case << @elements.pop
|
105
|
+
@true_case.last
|
109
106
|
end
|
110
107
|
|
111
108
|
def false_case(&block)
|
112
|
-
@outer_elements = @elements
|
113
|
-
@elements = []
|
114
109
|
instance_eval(&block) if block_given?
|
115
|
-
@false_case
|
116
|
-
@
|
117
|
-
@false_case
|
110
|
+
@false_case << @elements.pop
|
111
|
+
@false_case.last
|
118
112
|
end
|
119
113
|
|
120
114
|
def named_predicate(name)
|
121
|
-
@predicate
|
115
|
+
@predicate << Smartdown::Model::Predicate::Named.new(name)
|
122
116
|
end
|
123
117
|
|
124
118
|
def set_membership_predicate(varname, values)
|
125
|
-
@predicate
|
119
|
+
@predicate << Smartdown::Model::Predicate::SetMembership.new(varname, values)
|
126
120
|
end
|
127
121
|
|
128
122
|
def outcome(name)
|
129
|
-
@outcome
|
123
|
+
@outcome << name
|
130
124
|
end
|
131
125
|
|
132
126
|
module DSL
|
@@ -15,7 +15,7 @@ describe ModelBuilder do
|
|
15
15
|
let(:node2) {
|
16
16
|
Smartdown::Model::Node.new("what_passport_do_you_have?", [
|
17
17
|
Smartdown::Model::Element::MarkdownHeading.new("What passport do you have?"),
|
18
|
-
Smartdown::Model::Element::Question::MultipleChoice.new("what_passport_do_you_have?", {"greek" => "Greek", "british" => "British"}),
|
18
|
+
Smartdown::Model::Element::Question::MultipleChoice.new("what_passport_do_you_have?", {"greek" => "Greek", "british" => "British"}, "passport_type?"),
|
19
19
|
Smartdown::Model::NextNodeRules.new([
|
20
20
|
Smartdown::Model::Rule.new(
|
21
21
|
Smartdown::Model::Predicate::Named.new("eea_passport?"),
|
@@ -44,7 +44,8 @@ describe ModelBuilder do
|
|
44
44
|
{
|
45
45
|
greek: "Greek",
|
46
46
|
british: "British"
|
47
|
-
}
|
47
|
+
},
|
48
|
+
"passport_type?"
|
48
49
|
)
|
49
50
|
next_node_rules do
|
50
51
|
rule do
|
@@ -89,6 +90,42 @@ describe ModelBuilder do
|
|
89
90
|
it "builds a conditional" do
|
90
91
|
should eq(expected)
|
91
92
|
end
|
93
|
+
|
94
|
+
context "nested conditionals" do
|
95
|
+
let(:expected) {
|
96
|
+
Smartdown::Model::Element::Conditional.new(
|
97
|
+
Smartdown::Model::Predicate::Named.new("pred1?"),
|
98
|
+
[Smartdown::Model::Element::MarkdownParagraph.new("True case")],
|
99
|
+
[Smartdown::Model::Element::Conditional.new(
|
100
|
+
Smartdown::Model::Predicate::Named.new("pred2?"),
|
101
|
+
[Smartdown::Model::Element::MarkdownParagraph.new("False True case")],
|
102
|
+
[Smartdown::Model::Element::MarkdownParagraph.new("False False case")]
|
103
|
+
)]
|
104
|
+
)
|
105
|
+
}
|
106
|
+
|
107
|
+
subject(:model) {
|
108
|
+
builder.conditional do
|
109
|
+
named_predicate "pred1?"
|
110
|
+
true_case do
|
111
|
+
paragraph("True case")
|
112
|
+
end
|
113
|
+
false_case do
|
114
|
+
conditional do
|
115
|
+
named_predicate "pred2?"
|
116
|
+
true_case do
|
117
|
+
paragraph("False True case")
|
118
|
+
end
|
119
|
+
false_case do
|
120
|
+
paragraph("False False case")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
}
|
126
|
+
|
127
|
+
it { should eq(expected) }
|
128
|
+
end
|
92
129
|
end
|
93
130
|
|
94
131
|
describe "#rule" do
|
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.8.
|
4
|
+
version: 0.8.2
|
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: &
|
16
|
+
requirement: &5594140 !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: *
|
24
|
+
version_requirements: *5594140
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &5593360 !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: *
|
35
|
+
version_requirements: *5593360
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &5592880 !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: *
|
46
|
+
version_requirements: *5592880
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: gem_publisher
|
49
|
-
requirement: &
|
49
|
+
requirement: &5592080 !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: *
|
57
|
+
version_requirements: *5592080
|
58
58
|
description:
|
59
59
|
email: david.heath@digital.cabinet-office.gov.uk
|
60
60
|
executables:
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- lib/smartdown/parser/element/markdown_heading.rb
|
86
86
|
- lib/smartdown/parser/element/text_question.rb
|
87
87
|
- lib/smartdown/parser/flow_interpreter.rb
|
88
|
+
- lib/smartdown/parser/option_pairs_transform.rb
|
88
89
|
- lib/smartdown/api/date_question.rb
|
89
90
|
- lib/smartdown/api/coversheet.rb
|
90
91
|
- lib/smartdown/api/question.rb
|
@@ -163,6 +164,7 @@ files:
|
|
163
164
|
- spec/parser/element/front_matter_spec.rb
|
164
165
|
- spec/parser/element/salary_question_spec.rb
|
165
166
|
- spec/parser/element/text_question_spec.rb
|
167
|
+
- spec/parser/option_pairs_transform_spec.rb
|
166
168
|
- spec/parser/node_parser_spec.rb
|
167
169
|
- spec/parser/snippet_pre_parser_spec.rb
|
168
170
|
- spec/support/flow_input_interface.rb
|
@@ -204,6 +206,7 @@ files:
|
|
204
206
|
- spec/fixtures/acceptance/animal-example-multiple/questions/question_3.txt
|
205
207
|
- spec/fixtures/acceptance/animal-example-multiple/questions/question_1.txt
|
206
208
|
- spec/fixtures/acceptance/animal-example-multiple/questions/question_2.txt
|
209
|
+
- spec/fixtures/acceptance/animal-example-multiple/questions/question_4.txt
|
207
210
|
- spec/fixtures/acceptance/cover-sheet/cover-sheet.txt
|
208
211
|
- spec/fixtures/acceptance/one-question/one-question.txt
|
209
212
|
- spec/fixtures/acceptance/one-question/questions/q1.txt
|
@@ -242,7 +245,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
242
245
|
version: '0'
|
243
246
|
segments:
|
244
247
|
- 0
|
245
|
-
hash: -
|
248
|
+
hash: -1665618218665725274
|
246
249
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
247
250
|
none: false
|
248
251
|
requirements:
|
@@ -251,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
254
|
version: '0'
|
252
255
|
segments:
|
253
256
|
- 0
|
254
|
-
hash: -
|
257
|
+
hash: -1665618218665725274
|
255
258
|
requirements: []
|
256
259
|
rubyforge_project:
|
257
260
|
rubygems_version: 1.8.11
|
@@ -278,6 +281,7 @@ test_files:
|
|
278
281
|
- spec/parser/element/front_matter_spec.rb
|
279
282
|
- spec/parser/element/salary_question_spec.rb
|
280
283
|
- spec/parser/element/text_question_spec.rb
|
284
|
+
- spec/parser/option_pairs_transform_spec.rb
|
281
285
|
- spec/parser/node_parser_spec.rb
|
282
286
|
- spec/parser/snippet_pre_parser_spec.rb
|
283
287
|
- spec/support/flow_input_interface.rb
|
@@ -319,6 +323,7 @@ test_files:
|
|
319
323
|
- spec/fixtures/acceptance/animal-example-multiple/questions/question_3.txt
|
320
324
|
- spec/fixtures/acceptance/animal-example-multiple/questions/question_1.txt
|
321
325
|
- spec/fixtures/acceptance/animal-example-multiple/questions/question_2.txt
|
326
|
+
- spec/fixtures/acceptance/animal-example-multiple/questions/question_4.txt
|
322
327
|
- spec/fixtures/acceptance/cover-sheet/cover-sheet.txt
|
323
328
|
- spec/fixtures/acceptance/one-question/one-question.txt
|
324
329
|
- spec/fixtures/acceptance/one-question/questions/q1.txt
|