smartdown 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.md +26 -54
  2. data/lib/smartdown/api/flow.rb +5 -0
  3. data/lib/smartdown/api/previous_question.rb +6 -6
  4. data/lib/smartdown/api/previous_question_page.rb +5 -1
  5. data/lib/smartdown/api/question.rb +4 -0
  6. data/lib/smartdown/api/state.rb +18 -6
  7. data/lib/smartdown/model/answer/base.rb +1 -1
  8. data/lib/smartdown/model/answer/multiple_choice.rb +4 -0
  9. data/lib/smartdown/model/answer/salary.rb +15 -0
  10. data/lib/smartdown/model/scenarios/question.rb +7 -0
  11. data/lib/smartdown/model/scenarios/scenario.rb +7 -0
  12. data/lib/smartdown/model/scenarios/scenario_set.rb +7 -0
  13. data/lib/smartdown/parser/directory_input.rb +2 -2
  14. data/lib/smartdown/parser/input_set.rb +2 -2
  15. data/lib/smartdown/parser/scenario_sets_interpreter.rb +68 -0
  16. data/lib/smartdown/parser/snippet_pre_parser.rb +1 -1
  17. data/lib/smartdown/version.rb +1 -1
  18. data/spec/acceptance/flow_spec.rb +67 -0
  19. data/spec/api/previous_question_spec.rb +25 -0
  20. data/spec/api/state_spec.rb +23 -0
  21. data/spec/fixtures/acceptance/animal-example-multiple/animal-example-multiple.txt +25 -0
  22. data/spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_safe_pet.txt +69 -0
  23. data/spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_tigers_are_fine.txt +9 -0
  24. data/spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_trained_with_lions.txt +3 -0
  25. data/spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_trained_with_tigers.txt +3 -0
  26. data/spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_untrained_with_lions.txt +3 -0
  27. data/spec/fixtures/acceptance/animal-example-multiple/questions/question_1.txt +15 -0
  28. data/spec/fixtures/acceptance/animal-example-multiple/questions/question_2.txt +25 -0
  29. data/spec/fixtures/acceptance/animal-example-multiple/questions/question_3.txt +12 -0
  30. data/spec/fixtures/acceptance/animal-example-multiple/scenarios/lions.txt +23 -0
  31. data/spec/fixtures/acceptance/animal-example-multiple/scenarios/tigers_and_cats.txt +3 -0
  32. data/spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_can_outrun_a_lion.txt +3 -0
  33. data/spec/fixtures/acceptance/animal-example-simple/questions/question_2.txt +1 -1
  34. data/spec/fixtures/acceptance/animal-example-simple/questions/question_3.txt +7 -0
  35. data/spec/fixtures/acceptance/animal-example-simple/scenarios/lions.txt +16 -0
  36. data/spec/fixtures/acceptance/animal-example-simple/scenarios/tigers_and_cats.txt +3 -0
  37. data/spec/model/answer/multiple_choice_spec.rb +18 -0
  38. data/spec/model/answer/salary_spec.rb +35 -0
  39. data/spec/parser/directory_input_spec.rb +4 -4
  40. data/spec/support/flow_input_interface.rb +1 -1
  41. metadata +53 -11
data/README.md CHANGED
@@ -10,8 +10,6 @@ The language is designed to look like
10
10
  to allow expression of logical rules, questions and conditional blocks of
11
11
  text.
12
12
 
13
- It's a work-in-progress so some of the features described in this readme haven't been implemented yet. These are indicated with (tbd).
14
-
15
13
  ## Overview
16
14
 
17
15
  A single smartdown flow has a cover sheet, a set of questions, a set of
@@ -34,9 +32,9 @@ flow:
34
32
  | |-- medical_y.txt
35
33
  | `-- ...
36
34
  |-- scenarios
37
- | |-- 1.test
38
- | |-- 2.test
39
- | `-- 3.test
35
+ | |-- 1.txt
36
+ | |-- 2.txt
37
+ | `-- 3.txt
40
38
  |-- questions
41
39
  | |-- planning_to_leave_airport.txt
42
40
  | |-- purpose_of_visit.txt
@@ -108,30 +106,6 @@ to your outbound flight.
108
106
  * no: No
109
107
  ```
110
108
 
111
- ### Country (tbd)
112
-
113
- ```markdown
114
- ## What passport do you have?
115
-
116
- [country: passport_country]
117
- ```
118
-
119
- Presents a drop-down list of countries from the built-in list.
120
-
121
- Use front matter to exclude/include countries
122
-
123
- ```markdown
124
- ---
125
- passport_country:
126
- exclude_countries: country1, country2
127
- include_countries: {country3: "Country 3", country4: "Country 4"}
128
- ---
129
-
130
- ## What passport do you have?
131
-
132
- [country: passport_country]
133
- ```
134
-
135
109
  ### Date
136
110
 
137
111
  ```markdown
@@ -148,30 +122,12 @@ passport_country:
148
122
 
149
123
  Asks for an arbitrary text input.
150
124
 
151
- ### Money (tbd)
152
-
153
- ```markdown
154
- [money]
155
- ```
156
-
157
- Asks for a numerical input which can have decimals and optional thousand-separating commas.
158
-
159
125
  ### Salary
160
126
 
161
127
  ```markdown
162
128
  [salary: salary_value]
163
129
  ```
164
130
 
165
- ### Checkbox (tbd)
166
-
167
- ```markdown
168
- ## Will you pass through UK Border Control?
169
-
170
- [checkbox: uk_border_control]
171
- * [ ] yes: Yes
172
- * [ ] no: No
173
- ```
174
-
175
131
  ## Next steps
176
132
 
177
133
  Markdown to be displayed as part of an outcome to direct the users to other information of potential interest to them.
@@ -305,20 +261,36 @@ More copy...
305
261
  Where `snippet_name` is in a `snippets/` directory in the flow root with a `.txt`
306
262
  extension, eg `my-flow-name/snippets/my_snippet.txt`.
307
263
 
308
- The contents of `my_snippet` will be inseted into the outcome/question.
264
+ The contents of `my_snippet` will be inserted into the outcome/question.
265
+
266
+ ## Scenarios
267
+
268
+ Scenarios are meant to be run as test to check that a Smartdown flow behaves
269
+ in a certain way given some input data.
309
270
 
310
- ## Named predicates (tbd)
271
+ ###Scenario files
311
272
 
312
- Named predicates
273
+ There can be as many scenario files as one wishes, with no restriction on name. Each
274
+ scenario file should contain scenarios written as documented below.
313
275
 
314
- ## Plugin API (tbd)
276
+ ###Format
315
277
 
316
- A plugin API will be provided to allow more complex calculations to be defined
317
- in an external ruby class.
278
+ Each scenario is made of:
279
+ * a description (optional)
280
+ * list of questions pages (each question page starts with a -), inside which questions to answers are defined
281
+ * name of the outcome
282
+
283
+ ```
284
+ # Description
285
+ - name_of_q1_p1: answer_to_q1_p1
286
+ - name_of_q1_p2: answer_to_q1_p2
287
+ name_of_q2_p2: answer_to_q2_p2
288
+ outcome_the_result
289
+ ```
318
290
 
319
291
  ## Software design
320
292
 
321
- The software design can be seen in this diagram:
293
+ The initial plan for software design can be seen in this diagram:
322
294
 
323
295
  ![Software design](https://raw.githubusercontent.com/alphagov/smartdown/master/doc/design.png)
324
296
 
@@ -4,13 +4,18 @@ require 'smartdown/api/state'
4
4
  require 'smartdown/api/coversheet'
5
5
  require 'smartdown/api/question_page'
6
6
  require 'smartdown/api/outcome'
7
+ require 'smartdown/parser/scenario_sets_interpreter'
7
8
 
8
9
  module Smartdown
9
10
  module Api
10
11
  class Flow
12
+
13
+ attr_reader :scenario_sets
14
+
11
15
  def initialize(smartdown_input, initial_state = {})
12
16
  @smartdown_flow = Smartdown::Parser::FlowInterpreter.new(smartdown_input).interpret
13
17
  @engine = Smartdown::Engine.new(@smartdown_flow, initial_state)
18
+ @scenario_sets = Smartdown::Parser::ScenarioSetsInterpreter.new(smartdown_input).interpret
14
19
  end
15
20
 
16
21
  def state(started, responses)
@@ -5,19 +5,19 @@ module Smartdown
5
5
 
6
6
  def_delegators :@question, :title, :options
7
7
 
8
- attr_reader :response, :question
8
+ attr_reader :answer, :question
9
9
 
10
10
  def initialize(elements, response)
11
- @response = response
12
- if elements.find{|element| element.is_a? Smartdown::Model::Element::Question::MultipleChoice}
11
+ if element = elements.find{|element| element.is_a? Smartdown::Model::Element::Question::MultipleChoice}
13
12
  @question = MultipleChoice.new(elements)
14
- elsif elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Date}
13
+ elsif element = elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Date}
15
14
  @question = DateQuestion.new(elements)
16
- elsif elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Salary}
15
+ elsif element = elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Salary}
17
16
  @question = SalaryQuestion.new(elements)
18
- elsif elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Text}
17
+ elsif element = elements.find{|element| element.is_a? Smartdown::Model::Element::Question::Text}
19
18
  @question = TextQuestion.new(elements)
20
19
  end
20
+ @answer = element.answer_type.new(response, element) if element
21
21
  end
22
22
 
23
23
  end
@@ -22,8 +22,12 @@ module Smartdown
22
22
  @responses = responses
23
23
  end
24
24
 
25
+ def answers
26
+ questions.map(&:answer)
27
+ end
28
+
25
29
  def questions
26
- elements.slice_before do |element|
30
+ @questions ||= elements.slice_before do |element|
27
31
  element.is_a? Smartdown::Model::Element::MarkdownHeading
28
32
  end.each_with_index.map do |question_element_group, index|
29
33
  Smartdown::Api::PreviousQuestion.new(question_element_group, responses[index])
@@ -10,6 +10,10 @@ module Smartdown
10
10
  elements.first.content
11
11
  end
12
12
 
13
+ def name
14
+ elements.find{ |element| element.class.to_s.include?("Smartdown::Model::Element::Question") }.name
15
+ end
16
+
13
17
  def body
14
18
  elements_before_smartdown = elements[1..-1].take_while{|element| !smartdown_element?(element)}
15
19
  build_govspeak(elements_before_smartdown)
@@ -12,6 +12,16 @@ module Smartdown
12
12
  @responses = responses
13
13
  end
14
14
 
15
+ def answers
16
+ previous_question_pages(responses).map { |previous_question_page|
17
+ previous_question_page.answers
18
+ }.flatten
19
+ end
20
+
21
+ def previous_question_pages(responses)
22
+ @previous_question_pages ||= build_question_pages(responses)
23
+ end
24
+
15
25
  def started?
16
26
  !current_node.is_a? Smartdown::Api::Coversheet
17
27
  end
@@ -20,12 +30,6 @@ module Smartdown
20
30
  current_node.is_a? Smartdown::Api::Outcome
21
31
  end
22
32
 
23
- def previous_question_pages(responses)
24
- previous_questionpage_smartdown_nodes.map do |smartdown_questionpage_node|
25
- Smartdown::Api::PreviousQuestionPage.new(smartdown_questionpage_node, responses)
26
- end
27
- end
28
-
29
33
  def current_question_number
30
34
  responses.count + 1
31
35
  end
@@ -34,6 +38,14 @@ module Smartdown
34
38
 
35
39
  attr_reader :smartdown_state, :previous_questionpage_smartdown_nodes
36
40
 
41
+ def build_question_pages(responses)
42
+ resp = responses.dup
43
+ previous_questionpage_smartdown_nodes.map do |smartdown_questionpage_node|
44
+ page_questions = resp.take(smartdown_questionpage_node.questions.count)
45
+ resp = resp.drop(smartdown_questionpage_node.questions.count)
46
+ Smartdown::Api::PreviousQuestionPage.new(smartdown_questionpage_node, page_questions)
47
+ end
48
+ end
37
49
  end
38
50
  end
39
51
  end
@@ -5,7 +5,7 @@ module Smartdown
5
5
  extend Forwardable
6
6
  include Comparable
7
7
 
8
- def_delegators :value, :to_s, :to_i, :to_f, :+, :-, :*, :/
8
+ def_delegators :value, :to_s, :humanize, :to_i, :to_f, :+, :-, :*, :/
9
9
 
10
10
  def value_type
11
11
  ::String
@@ -7,6 +7,10 @@ module Smartdown
7
7
  def value_type
8
8
  ::String
9
9
  end
10
+
11
+ def humanize
12
+ question.choices.fetch(value)
13
+ end
10
14
  end
11
15
  end
12
16
  end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require_relative "base"
2
3
 
3
4
  module Smartdown
@@ -14,6 +15,11 @@ module Smartdown
14
15
  "#{'%.2f' % amount_per_period} per #{period}"
15
16
  end
16
17
 
18
+ def humanize
19
+ whole, decimal = separate_by_comma(amount_per_period)
20
+ "£#{whole}.#{decimal} per #{period}"
21
+ end
22
+
17
23
  private
18
24
  def parse_value(value)
19
25
  @amount_per_period, @period = value.split(/-|\W*per\W*/)
@@ -31,6 +37,15 @@ module Smartdown
31
37
  amount_per_period
32
38
  end
33
39
  end
40
+
41
+ def separate_by_comma(number)
42
+ left, right = ('%.2f' % number).split('.')
43
+ left.gsub!(/(\d)(?=(\d\d\d)+(?!\d))/) do |digit_to_delimit|
44
+ "#{digit_to_delimit},"
45
+ end
46
+ right = "%02d" % right.to_i
47
+ [left, right]
48
+ end
34
49
  end
35
50
  end
36
51
  end
@@ -0,0 +1,7 @@
1
+ module Smartdown
2
+ module Model
3
+ module Scenarios
4
+ Question = Struct.new(:name, :answer)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Smartdown
2
+ module Model
3
+ module Scenarios
4
+ Scenario = Struct.new(:description, :question_groups, :outcome)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Smartdown
2
+ module Model
3
+ module Scenarios
4
+ ScenarioSet = Struct.new(:name, :scenarios)
5
+ end
6
+ end
7
+ end
@@ -19,7 +19,7 @@ module Smartdown
19
19
  read_dir("outcomes")
20
20
  end
21
21
 
22
- def scenarios
22
+ def scenario_sets
23
23
  read_dir("scenarios")
24
24
  end
25
25
 
@@ -32,7 +32,7 @@ module Smartdown
32
32
  coversheet: coversheet.to_s,
33
33
  questions: questions.map(&:to_s),
34
34
  outcomes: outcomes.map(&:to_s),
35
- scenarios: scenarios.map(&:to_s)
35
+ scenario_sets: scenario_sets.map(&:to_s)
36
36
  }
37
37
  end
38
38
 
@@ -1,14 +1,14 @@
1
1
  module Smartdown
2
2
  module Parser
3
3
  class InputSet
4
- attr_reader :coversheet, :questions, :outcomes, :snippets, :scenarios
4
+ attr_reader :coversheet, :questions, :outcomes, :snippets, :scenario_sets
5
5
 
6
6
  def initialize(params = {})
7
7
  @coversheet = params[:coversheet]
8
8
  @questions = params[:questions]
9
9
  @outcomes = params[:outcomes]
10
10
  @snippets = params[:snippets]
11
- @scenarios = params[:scenarios]
11
+ @scenario_sets = params[:scenario_sets]
12
12
  end
13
13
  end
14
14
 
@@ -0,0 +1,68 @@
1
+ require "smartdown/model/scenarios/scenario_set"
2
+ require "smartdown/model/scenarios/scenario"
3
+ require "smartdown/model/scenarios/question"
4
+
5
+ module Smartdown
6
+ module Parser
7
+ class ScenarioSetsInterpreter
8
+
9
+ def initialize(smartdown_input)
10
+ @smartdown_input = smartdown_input
11
+ end
12
+
13
+ def interpret
14
+ @smartdown_input.scenario_sets.map { |scenario_set| interpret_scenario_set(scenario_set) }
15
+ end
16
+
17
+ private
18
+ def interpret_scenario_set(scenario_set)
19
+ Smartdown::Model::Scenarios::ScenarioSet.new(
20
+ scenario_set.name,
21
+ scenario_set.read.split("\n\n").map { |scenario_string| interpret_scenario(scenario_string) }
22
+ )
23
+ end
24
+
25
+ def interpret_scenario(scenario_string)
26
+ scenario_lines = scenario_string.split("\n")
27
+ if scenario_lines.first.start_with?("#")
28
+ description = scenario_lines.first[1..-1].strip
29
+ scenario_lines = scenario_lines[1..-1]
30
+ end
31
+ outcome = scenario_lines.last
32
+ question_pages = group_questions_by_page(scenario_lines[0..-2])
33
+ question_groups = question_pages.map { |question_page| interpret_question_page(question_page) }
34
+ Smartdown::Model::Scenarios::Scenario.new(
35
+ description,
36
+ question_groups,
37
+ outcome,
38
+ )
39
+ end
40
+
41
+ def group_questions_by_page(scenario_lines)
42
+ result = []
43
+ scenario_lines.each do |scenario_line|
44
+ if scenario_line.start_with?("-")
45
+ result << [scenario_line[1..-1]]
46
+ else
47
+ result.last << scenario_line
48
+ end
49
+ end
50
+ result
51
+ end
52
+
53
+ def interpret_question_page(question_page)
54
+ question_page.map { |question|
55
+ interpret_question(question)
56
+ }
57
+ end
58
+
59
+ def interpret_question(question_string)
60
+ name, answer = question_string.split(":").map(&:strip)
61
+ Smartdown::Model::Scenarios::Question.new(
62
+ name,
63
+ answer,
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
@@ -17,7 +17,7 @@ module Smartdown
17
17
  questions: input_data.questions.map { |question_data| parse_node_input(question_data) },
18
18
  outcomes: input_data.outcomes.map { |outcome_data| parse_node_input(outcome_data) },
19
19
  snippets: input_data.snippets,
20
- scenarios: input_data.scenarios,
20
+ scenario_sets: input_data.scenario_sets,
21
21
  })
22
22
  end
23
23
 
@@ -1,3 +1,3 @@
1
1
  module Smartdown
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -0,0 +1,67 @@
1
+ require 'smartdown/api/flow'
2
+
3
+ describe Smartdown::Api::Flow do
4
+
5
+ def fixture(name)
6
+ File.dirname(__FILE__) + "/../fixtures/acceptance/#{name}/#{name}.txt"
7
+ end
8
+
9
+ let(:input) { Smartdown::Parser::DirectoryInput.new(fixture("animal-example-simple")) }
10
+ subject(:flow) { Smartdown::Api::Flow.new(input) }
11
+
12
+ context "flow with one question per page" do
13
+ it "has two scenario_sets" do
14
+ expect(flow.scenario_sets.size).to eq(2)
15
+ end
16
+
17
+ it "has scenario_set names" do
18
+ expect(flow.scenario_sets.map(&:name)).to match_array(["lions", "tigers_and_cats"])
19
+ end
20
+
21
+ it "has scenarios with description, questions and outcome" do
22
+ tigers_and_cats_scenario_set = flow.scenario_sets.find { |scenario_set| scenario_set.name == "tigers_and_cats" }
23
+ expect(tigers_and_cats_scenario_set.scenarios.size).to eq(1)
24
+ expect(tigers_and_cats_scenario_set.scenarios.first.description).to eq("User has a cat")
25
+ expect(tigers_and_cats_scenario_set.scenarios.first.question_groups.size).to eq(1)
26
+ expect(tigers_and_cats_scenario_set.scenarios.first.outcome).to eq("outcome_safe_pet")
27
+ end
28
+
29
+ it "has scenario questions with name and answer" do
30
+ tigers_and_cats_scenario_set = flow.scenario_sets.find { |scenario_set| scenario_set.name == "tigers_and_cats" }
31
+ tigers_and_cats_scenario = tigers_and_cats_scenario_set.scenarios.first
32
+ expect(tigers_and_cats_scenario.question_groups.first.first.name).to eq("question_1")
33
+ expect(tigers_and_cats_scenario.question_groups.first.first.answer).to eq("cat")
34
+ end
35
+ end
36
+
37
+ context "flow with two questions per page" do
38
+ let(:input) { Smartdown::Parser::DirectoryInput.new(fixture("animal-example-multiple")) }
39
+ it "has two scenario_sets" do
40
+ expect(flow.scenario_sets.size).to eq(2)
41
+ end
42
+
43
+ it "has scenario_set names" do
44
+ expect(flow.scenario_sets.map(&:name)).to match_array(["lions", "tigers_and_cats"])
45
+ end
46
+
47
+ it "has scenarios with description, questions and outcome" do
48
+ tigers_and_cats_scenario_set = flow.scenario_sets.find { |scenario_set| scenario_set.name == "lions" }
49
+ expect(tigers_and_cats_scenario_set.scenarios.size).to eq(4)
50
+ expect(tigers_and_cats_scenario_set.scenarios.first.description).to eq("User has trained with lions, not with tigers")
51
+ expect(tigers_and_cats_scenario_set.scenarios.first.question_groups.size).to eq(2)
52
+ expect(tigers_and_cats_scenario_set.scenarios.first.outcome).to eq("outcome_trained_with_lions")
53
+ end
54
+
55
+ it "has scenario questions with name and answer" do
56
+ tigers_and_cats_scenario_set = flow.scenario_sets.find { |scenario_set| scenario_set.name == "lions" }
57
+ tigers_and_cats_scenario = tigers_and_cats_scenario_set.scenarios.first
58
+ expect(tigers_and_cats_scenario.question_groups[1][0].name).to eq("trained_for_lions")
59
+ expect(tigers_and_cats_scenario.question_groups[1][0].answer).to eq("yes")
60
+ expect(tigers_and_cats_scenario.question_groups[1][1].name).to eq("trained_for_tigers")
61
+ expect(tigers_and_cats_scenario.question_groups[1][1].answer).to eq("no")
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+
@@ -0,0 +1,25 @@
1
+ require 'smartdown/api/previous_question'
2
+ require 'smartdown/api/multiple_choice'
3
+
4
+ describe Smartdown::Api::PreviousQuestion do
5
+
6
+ subject(:previous_question) { Smartdown::Api::PreviousQuestion.new(elements, response)}
7
+ let(:elements) { [ multiple_choice_element ] }
8
+ let(:multiple_choice_element) { Smartdown::Model::Element::Question::MultipleChoice.new }
9
+ let(:response) { double(:response) }
10
+ let(:multiple_choice_class) { double(:multiple_choice_class, :new => nil) }
11
+ let(:answer_type) { double(:answer_type) }
12
+
13
+ describe "#question" do
14
+ it "is of the correct type" do
15
+ expect(previous_question.question).to be_instance_of(Smartdown::Api::MultipleChoice)
16
+ end
17
+ end
18
+
19
+ describe "#answer" do
20
+ it "is of the correct type" do
21
+ expect(previous_question.answer).to be_instance_of(Smartdown::Model::Answer::MultipleChoice)
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,23 @@
1
+ require 'smartdown/api/state'
2
+
3
+ describe Smartdown::Api::State do
4
+
5
+ subject(:state) { Smartdown::Api::State.new(current_node, previous_questionpage_smartdown_nodes, responses)}
6
+ let(:current_node) { double(:current_node) }
7
+ let(:previous_questionpage_smartdown_nodes) { [question_page_node_1, question_page_node_2] }
8
+ let(:question_page_node_1) { double(:question_page_node_1, :questions => [double, double]) }
9
+ let(:question_page_node_2) { double(:question_page_node_2, :questions => [double]) }
10
+ let(:responses) { ["a", "b", "c"] }
11
+ let(:previous_question_page_class) { double(:previous_question_page_class, :new => nil)}
12
+
13
+ describe "#previous_question_pages" do
14
+ it "creates question pages with their corresponding responses" do
15
+ stub_const("Smartdown::Api::PreviousQuestionPage", previous_question_page_class)
16
+ state.previous_question_pages(responses)
17
+ expect(Smartdown::Api::PreviousQuestionPage).to have_received(:new)
18
+ .with(question_page_node_1, ["a", "b"])
19
+ expect(Smartdown::Api::PreviousQuestionPage).to have_received(:new)
20
+ .with(question_page_node_2, ["c"])
21
+ end
22
+ end
23
+ end
@@ -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, you won't forget your training for another %{years_until_training_forgotten(training_date)} years.
@@ -0,0 +1,3 @@
1
+ # You've trained with tigers
2
+
3
+ Grrrrreat!
@@ -0,0 +1,3 @@
1
+ # You're not trained with lions
2
+
3
+ Nice to know you.
@@ -0,0 +1,15 @@
1
+ # What type of feline do you have?
2
+
3
+ # Check the serial number by the tail.
4
+
5
+ Text before the options.
6
+
7
+ [choice: question_1]
8
+ * lion: Lion
9
+ * tiger: Tiger
10
+ * cat: Cat
11
+
12
+ This is an optional note after question.
13
+
14
+ * question_1 in {lion tiger} => question_2
15
+ * otherwise => outcome_safe_pet
@@ -0,0 +1,25 @@
1
+ # How good is your big cat training?
2
+
3
+ # Are you trained for lions?
4
+
5
+ Think very carefully.
6
+
7
+ [choice: trained_for_lions]
8
+ * yes: Yes
9
+ * no: No
10
+
11
+ # Are you trained for tigers?
12
+
13
+ Think very carefully again.
14
+
15
+ [choice: trained_for_tigers]
16
+ * yes: Yes
17
+ * maybe: I've been to a zoo once
18
+ * no: No
19
+
20
+ * question_1 is 'lion'
21
+ * trained_for_lions is 'yes' => question_3
22
+ * otherwise => outcome_untrained_with_lions
23
+ * otherwise
24
+ * trained_for_tigers is 'yes' => question_3
25
+ * otherwise => outcome_tigers_are_fine
@@ -0,0 +1,12 @@
1
+ # Training date?
2
+
3
+ When did you do your %{question_1} training?
4
+
5
+ [date: training_date]
6
+
7
+ * question_1 is 'lion'
8
+ * trained_for_lions is 'yes' => outcome_trained_with_lions
9
+ * otherwise => outcome_untrained_with_lions
10
+ * otherwise
11
+ * trained_for_tigers is 'yes' => outcome_trained_with_tigers
12
+ * otherwise => outcome_tigers_are_fine
@@ -0,0 +1,23 @@
1
+ #User has trained with lions, not with tigers
2
+ - question_1: lion
3
+ - trained_for_lions: yes
4
+ trained_for_tigers: no
5
+ outcome_trained_with_lions
6
+
7
+ #User has trained with lions and tigers
8
+ - question_1: lion
9
+ - trained_for_lions: yes
10
+ trained_for_lions: yes
11
+ outcome_can_outrun_a_lion
12
+
13
+ #User has trained neither with lions, nor with tigers
14
+ - question_1: lion
15
+ - trained_for_lions: no
16
+ trained_for_tigers: no
17
+ outcome_untrained_with_lions
18
+
19
+ #User has trained with tigers, not with lions
20
+ - question_1: lion
21
+ - trained_for_lions: no
22
+ trained_for_tigers: yes
23
+ outcome_untrained_with_lions
@@ -0,0 +1,3 @@
1
+ #User has a cat
2
+ - question_1: cat
3
+ outcome_safe_pet
@@ -0,0 +1,3 @@
1
+ # You can outrun a lion
2
+
3
+ You'll be alright, I think.
@@ -6,5 +6,5 @@
6
6
 
7
7
  * question_1 is 'lion'
8
8
  * trained_for_lions is 'yes' => outcome_trained_with_lions
9
- * otherwise => outcome_untrained_with_lions
9
+ * otherwise => question_3
10
10
  * otherwise => outcome_tigers_are_fine
@@ -0,0 +1,7 @@
1
+ # How fast can you run?
2
+
3
+ [text: how_fast]
4
+
5
+ * how_fast is 'fast' => outcome_can_outrun_a_lion
6
+ * otherwise => outcome_untrained_with_lions
7
+
@@ -0,0 +1,16 @@
1
+ # User has trained with lions
2
+ - question_1: lion
3
+ - trained_for_lions: yes
4
+ outcome_trained_with_lions
5
+
6
+ # User has not trained with lions but can run fast
7
+ - question_1: lion
8
+ - trained_for_lions: no
9
+ - how_fast: fast
10
+ outcome_can_outrun_a_lion
11
+
12
+ # User has not trained with lions and runs very slowly
13
+ - question_1: lion
14
+ - trained_for_lions: no
15
+ - how_fast: very slowly
16
+ outcome_untrained_with_lions
@@ -0,0 +1,3 @@
1
+ # User has a cat
2
+ - question_1: cat
3
+ outcome_safe_pet
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ require 'smartdown/model/answer/multiple_choice'
3
+
4
+ describe Smartdown::Model::Answer::MultipleChoice do
5
+
6
+ subject(:answer) { described_class.new("answer", multiple_choice_question) }
7
+ let(:multiple_choice_question) {
8
+ Smartdown::Model::Element::Question::MultipleChoice.new("question",
9
+ {"answer" => "Beautiful answer"}
10
+ )
11
+ }
12
+
13
+ describe "#humanize" do
14
+ it "returns the humanized answer" do
15
+ expect(answer.humanize).to eq "Beautiful answer"
16
+ end
17
+ end
18
+ end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'smartdown/model/answer/date'
2
3
  require 'smartdown/model/element/question/date'
3
4
 
@@ -13,6 +14,40 @@ describe Smartdown::Model::Answer::Salary do
13
14
  expect(instance.to_s).to eql("500.00 per week")
14
15
  end
15
16
 
17
+ describe "#humanize" do
18
+ it "declares itself in the initial format provided" do
19
+ expect(instance.humanize).to eql("£500.00 per week")
20
+ end
21
+
22
+ context "amounts over 999" do
23
+ let(:salary_string) { "15000-week" }
24
+ it "adds commas" do
25
+ expect(instance.humanize).to eql("£15,000.00 per week")
26
+ end
27
+ end
28
+
29
+ context "amounts with .525 decimal part" do
30
+ let(:salary_string) { "15000.525-week" }
31
+ it "adds commas" do
32
+ expect(instance.humanize).to eql("£15,000.52 per week")
33
+ end
34
+ end
35
+
36
+ context "amounts with .555 decimal part" do
37
+ let(:salary_string) { "15000.555-week" }
38
+ it "adds commas" do
39
+ expect(instance.humanize).to eql("£15,000.56 per week")
40
+ end
41
+ end
42
+
43
+ context "amounts with .556 decimal part" do
44
+ let(:salary_string) { "15000.556-week" }
45
+ it "adds commas" do
46
+ expect(instance.humanize).to eql("£15,000.56 per week")
47
+ end
48
+ end
49
+ end
50
+
16
51
  context "declared by week" do
17
52
  let(:salary_string) { "500-week" }
18
53
  specify { expect(instance.value).to eql 500.0 * 52 }
@@ -40,11 +40,11 @@ describe Smartdown::Parser::DirectoryInput do
40
40
  end
41
41
  end
42
42
 
43
- describe "#scenarios" do
43
+ describe "#scenario_sets" do
44
44
  it "returns an InputFile for every file in the scenarios folder" do
45
- expect(input.scenarios).to match([instance_of(Smartdown::Parser::InputFile)])
46
- expect(input.scenarios.first.name).to eq("s1")
47
- expect(input.scenarios.first.read).to eq("scenario one\n")
45
+ expect(input.scenario_sets).to match([instance_of(Smartdown::Parser::InputFile)])
46
+ expect(input.scenario_sets.first.name).to eq("s1")
47
+ expect(input.scenario_sets.first.read).to eq("scenario one\n")
48
48
  end
49
49
  end
50
50
 
@@ -2,6 +2,6 @@ shared_examples "flow input interface" do
2
2
  it { should respond_to(:coversheet) }
3
3
  it { should respond_to(:questions) }
4
4
  it { should respond_to(:outcomes) }
5
- it { should respond_to(:scenarios) }
5
+ it { should respond_to(:scenario_sets) }
6
6
  it { should respond_to(:snippets) }
7
7
  end
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.6.0
4
+ version: 0.7.0
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: &18240820 !ruby/object:Gem::Requirement
16
+ requirement: &14489700 !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: *18240820
24
+ version_requirements: *14489700
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &18238780 !ruby/object:Gem::Requirement
27
+ requirement: &14488200 !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: *18238780
35
+ version_requirements: *14488200
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rake
38
- requirement: &18237320 !ruby/object:Gem::Requirement
38
+ requirement: &14504920 !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: *18237320
46
+ version_requirements: *14504920
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: gem_publisher
49
- requirement: &18235860 !ruby/object:Gem::Requirement
49
+ requirement: &14504180 !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: *18235860
57
+ version_requirements: *14504180
58
58
  description:
59
59
  email: david.heath@digital.cabinet-office.gov.uk
60
60
  executables:
@@ -73,6 +73,7 @@ files:
73
73
  - lib/smartdown/parser/directory_input.rb
74
74
  - lib/smartdown/parser/rules.rb
75
75
  - lib/smartdown/parser/node_parser.rb
76
+ - lib/smartdown/parser/scenario_sets_interpreter.rb
76
77
  - lib/smartdown/parser/element/date_question.rb
77
78
  - lib/smartdown/parser/element/salary_question.rb
78
79
  - lib/smartdown/parser/element/conditional.rb
@@ -105,6 +106,9 @@ files:
105
106
  - lib/smartdown/engine/transition.rb
106
107
  - lib/smartdown/engine/interpolator.rb
107
108
  - lib/smartdown/engine/errors.rb
109
+ - lib/smartdown/model/scenarios/question.rb
110
+ - lib/smartdown/model/scenarios/scenario_set.rb
111
+ - lib/smartdown/model/scenarios/scenario.rb
108
112
  - lib/smartdown/model/flow.rb
109
113
  - lib/smartdown/model/predicate/named.rb
110
114
  - lib/smartdown/model/predicate/set_membership.rb
@@ -142,6 +146,7 @@ files:
142
146
  - README.md
143
147
  - spec/acceptance/parsing_spec.rb
144
148
  - spec/acceptance/smartdown_cli_spec.rb
149
+ - spec/acceptance/flow_spec.rb
145
150
  - spec/parser/directory_input_spec.rb
146
151
  - spec/parser/integration/cover_sheet_spec.rb
147
152
  - spec/parser/base_spec.rb
@@ -162,6 +167,8 @@ files:
162
167
  - spec/parser/snippet_pre_parser_spec.rb
163
168
  - spec/support/flow_input_interface.rb
164
169
  - spec/support/model_builder.rb
170
+ - spec/api/state_spec.rb
171
+ - spec/api/previous_question_spec.rb
165
172
  - spec/spec_helper.rb
166
173
  - spec/engine/state_spec.rb
167
174
  - spec/engine/interpolator_spec.rb
@@ -175,13 +182,28 @@ files:
175
182
  - spec/fixtures/acceptance/question-and-outcome/question-and-outcome.txt
176
183
  - spec/fixtures/acceptance/question-and-outcome/outcomes/o1.txt
177
184
  - spec/fixtures/acceptance/question-and-outcome/questions/q1.txt
185
+ - spec/fixtures/acceptance/animal-example-simple/scenarios/tigers_and_cats.txt
186
+ - spec/fixtures/acceptance/animal-example-simple/scenarios/lions.txt
178
187
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_untrained_with_lions.txt
179
188
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_tigers_are_fine.txt
189
+ - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_can_outrun_a_lion.txt
180
190
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_safe_pet.txt
181
191
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_trained_with_lions.txt
182
192
  - spec/fixtures/acceptance/animal-example-simple/animal-example-simple.txt
193
+ - spec/fixtures/acceptance/animal-example-simple/questions/question_3.txt
183
194
  - spec/fixtures/acceptance/animal-example-simple/questions/question_1.txt
184
195
  - spec/fixtures/acceptance/animal-example-simple/questions/question_2.txt
196
+ - spec/fixtures/acceptance/animal-example-multiple/scenarios/tigers_and_cats.txt
197
+ - spec/fixtures/acceptance/animal-example-multiple/scenarios/lions.txt
198
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_trained_with_tigers.txt
199
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_untrained_with_lions.txt
200
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_tigers_are_fine.txt
201
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_safe_pet.txt
202
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_trained_with_lions.txt
203
+ - spec/fixtures/acceptance/animal-example-multiple/animal-example-multiple.txt
204
+ - spec/fixtures/acceptance/animal-example-multiple/questions/question_3.txt
205
+ - spec/fixtures/acceptance/animal-example-multiple/questions/question_1.txt
206
+ - spec/fixtures/acceptance/animal-example-multiple/questions/question_2.txt
185
207
  - spec/fixtures/acceptance/cover-sheet/cover-sheet.txt
186
208
  - spec/fixtures/acceptance/one-question/one-question.txt
187
209
  - spec/fixtures/acceptance/one-question/questions/q1.txt
@@ -193,6 +215,7 @@ files:
193
215
  - spec/model/node_spec.rb
194
216
  - spec/model/flow_spec.rb
195
217
  - spec/model/answer/money_spec.rb
218
+ - spec/model/answer/multiple_choice_spec.rb
196
219
  - spec/model/answer/text_spec.rb
197
220
  - spec/model/answer/base_spec.rb
198
221
  - spec/model/answer/salary_spec.rb
@@ -218,7 +241,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
218
241
  version: '0'
219
242
  segments:
220
243
  - 0
221
- hash: -4119241983513724352
244
+ hash: -1023483134513177718
222
245
  required_rubygems_version: !ruby/object:Gem::Requirement
223
246
  none: false
224
247
  requirements:
@@ -227,7 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
227
250
  version: '0'
228
251
  segments:
229
252
  - 0
230
- hash: -4119241983513724352
253
+ hash: -1023483134513177718
231
254
  requirements: []
232
255
  rubyforge_project:
233
256
  rubygems_version: 1.8.11
@@ -237,6 +260,7 @@ summary: Interactive question-answer flows using markdown-like external DSL
237
260
  test_files:
238
261
  - spec/acceptance/parsing_spec.rb
239
262
  - spec/acceptance/smartdown_cli_spec.rb
263
+ - spec/acceptance/flow_spec.rb
240
264
  - spec/parser/directory_input_spec.rb
241
265
  - spec/parser/integration/cover_sheet_spec.rb
242
266
  - spec/parser/base_spec.rb
@@ -257,6 +281,8 @@ test_files:
257
281
  - spec/parser/snippet_pre_parser_spec.rb
258
282
  - spec/support/flow_input_interface.rb
259
283
  - spec/support/model_builder.rb
284
+ - spec/api/state_spec.rb
285
+ - spec/api/previous_question_spec.rb
260
286
  - spec/spec_helper.rb
261
287
  - spec/engine/state_spec.rb
262
288
  - spec/engine/interpolator_spec.rb
@@ -270,13 +296,28 @@ test_files:
270
296
  - spec/fixtures/acceptance/question-and-outcome/question-and-outcome.txt
271
297
  - spec/fixtures/acceptance/question-and-outcome/outcomes/o1.txt
272
298
  - spec/fixtures/acceptance/question-and-outcome/questions/q1.txt
299
+ - spec/fixtures/acceptance/animal-example-simple/scenarios/tigers_and_cats.txt
300
+ - spec/fixtures/acceptance/animal-example-simple/scenarios/lions.txt
273
301
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_untrained_with_lions.txt
274
302
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_tigers_are_fine.txt
303
+ - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_can_outrun_a_lion.txt
275
304
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_safe_pet.txt
276
305
  - spec/fixtures/acceptance/animal-example-simple/outcomes/outcome_trained_with_lions.txt
277
306
  - spec/fixtures/acceptance/animal-example-simple/animal-example-simple.txt
307
+ - spec/fixtures/acceptance/animal-example-simple/questions/question_3.txt
278
308
  - spec/fixtures/acceptance/animal-example-simple/questions/question_1.txt
279
309
  - spec/fixtures/acceptance/animal-example-simple/questions/question_2.txt
310
+ - spec/fixtures/acceptance/animal-example-multiple/scenarios/tigers_and_cats.txt
311
+ - spec/fixtures/acceptance/animal-example-multiple/scenarios/lions.txt
312
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_trained_with_tigers.txt
313
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_untrained_with_lions.txt
314
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_tigers_are_fine.txt
315
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_safe_pet.txt
316
+ - spec/fixtures/acceptance/animal-example-multiple/outcomes/outcome_trained_with_lions.txt
317
+ - spec/fixtures/acceptance/animal-example-multiple/animal-example-multiple.txt
318
+ - spec/fixtures/acceptance/animal-example-multiple/questions/question_3.txt
319
+ - spec/fixtures/acceptance/animal-example-multiple/questions/question_1.txt
320
+ - spec/fixtures/acceptance/animal-example-multiple/questions/question_2.txt
280
321
  - spec/fixtures/acceptance/cover-sheet/cover-sheet.txt
281
322
  - spec/fixtures/acceptance/one-question/one-question.txt
282
323
  - spec/fixtures/acceptance/one-question/questions/q1.txt
@@ -288,6 +329,7 @@ test_files:
288
329
  - spec/model/node_spec.rb
289
330
  - spec/model/flow_spec.rb
290
331
  - spec/model/answer/money_spec.rb
332
+ - spec/model/answer/multiple_choice_spec.rb
291
333
  - spec/model/answer/text_spec.rb
292
334
  - spec/model/answer/base_spec.rb
293
335
  - spec/model/answer/salary_spec.rb