doop 0.0.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.
Files changed (33) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +203 -0
  6. data/Rakefile +5 -0
  7. data/app/assets/images/dash.gif +0 -0
  8. data/app/assets/images/info-icon.png +0 -0
  9. data/app/assets/images/progress-tick-large.png +0 -0
  10. data/app/helpers/doop_helper.rb +51 -0
  11. data/bin/.gitignore +0 -0
  12. data/doop.gemspec +26 -0
  13. data/lib/doop.rb +313 -0
  14. data/lib/doop/version.rb +3 -0
  15. data/lib/doop_controller.rb +164 -0
  16. data/lib/generators/doopgovuk/USAGE +10 -0
  17. data/lib/generators/doopgovuk/doopgovuk_generator.rb +34 -0
  18. data/lib/generators/doopgovuk/templates/app/assets/stylesheets/demo.css.scss +188 -0
  19. data/lib/generators/doopgovuk/templates/app/controllers/demo_controller.rb +155 -0
  20. data/lib/generators/doopgovuk/templates/app/views/demo/_preamble.html.erb +33 -0
  21. data/lib/generators/doopgovuk/templates/app/views/demo/_summary.html.erb +10 -0
  22. data/lib/generators/doopgovuk/templates/app/views/demo/_your_details.html.erb +55 -0
  23. data/lib/generators/doopgovuk/templates/app/views/demo/index.html.erb +3 -0
  24. data/lib/generators/doopgovuk/templates/app/views/demo/index.js.erb +4 -0
  25. data/lib/generators/doopgovuk/templates/app/views/doop/_error.html.erb +3 -0
  26. data/lib/generators/doopgovuk/templates/app/views/doop/_info_box.html.erb +4 -0
  27. data/lib/generators/doopgovuk/templates/app/views/doop/_navbar.html.erb +23 -0
  28. data/lib/generators/doopgovuk/templates/app/views/doop/_question.html.erb +19 -0
  29. data/lib/generators/doopgovuk/templates/app/views/doop/_question_form.html.erb +15 -0
  30. data/lib/generators/doopgovuk/templates/app/views/layouts/application.html.erb +22 -0
  31. data/spec/doop_spec.rb +448 -0
  32. data/spec/spec_helper.rb +2 -0
  33. metadata +136 -0
@@ -0,0 +1,33 @@
1
+ <%=question_form doop, res do %>
2
+ <%=question "/page/preamble" do |root, answer| %>
3
+
4
+ <%=question "/page/preamble/debug_on" do |root,answer| %>
5
+ <button name="b_answer" value="Yes">Yes, turn debugging on</button><br/>
6
+ <button name="b_answer" value="No">No, turn debugging off</button>
7
+ <% end %>
8
+
9
+ <%=question "/page/preamble/enrolled_before" do |root,answer| %>
10
+ <button name="b_answer" value="Yes">Yes, I've applied before</button><br/>
11
+ <button name="b_answer" value="No">No, I haven't applied before</button>
12
+ <% end %>
13
+
14
+ <%=question "/page/preamble/year_last_applied" do |root,answer| %>
15
+ <%=select_tag( "b_answer", "<option>2013</option><option>2012</option>".html_safe ) %>
16
+ <br/>
17
+ <button>Continue</button>
18
+ <% end %>
19
+
20
+ <%=question "/page/preamble/reason_for_applying" do |root,answer| %>
21
+ <p>Why are you applying this year? Please be as specific as possible, and give examples</p>
22
+ <%=error res, :reason_for_applying_error %>
23
+ <%=text_area_tag "b_answer", answer, rows: 10, cols: 100 %>
24
+ <br/>
25
+ <button>Continue</button>
26
+ <% end %>
27
+
28
+ <% when_answered "/page/preamble" do %>
29
+ <button>Continue and Save</button>
30
+ <% end %>
31
+ <% end %>
32
+
33
+ <% end %>
@@ -0,0 +1,10 @@
1
+
2
+ <%=question_form doop, res do %>
3
+ <%=question "/page/summary" do |root, answer| %>
4
+ <%=question "/page/summary/terms_and_conditions" do |root, answer| %>
5
+ <p>I have answered the questions to the best of my knowledge and agree to abide by the terms and conditions of this service.</p>
6
+ <button>I have read and understood the terms and conditions</button>
7
+ <% end %>
8
+
9
+ <% end %>
10
+ <% end %>
@@ -0,0 +1,55 @@
1
+ <%=question_form doop, res do %>
2
+ <%=question "/page/your_details" do |root, answer| %>
3
+
4
+ <%=question "/page/your_details/your_name" do |root,answer| %>
5
+ <div class="textfield">
6
+ <label for="firstname">First names</label>
7
+ <input id="firstname" name="b_firstname" type="text" value="<%=answer["firstname"]%>"/>
8
+ </div>
9
+ <div class="textfield">
10
+ <label for="surname">Surname</label>
11
+ <input id="surname" name="b_surname" type="text" value="<%=answer["surname"]%>"/>
12
+ <br/>
13
+ <button>Continue</button><br/>
14
+ <% end %>
15
+
16
+ <%=question "/page/your_details/address_history" do |root,answer| %>
17
+
18
+ <p>We need to know where you have lived for the past 6 years</p>
19
+
20
+ <% list "/page/your_details/address_history/address__(\\d+)" do |path,index| %>
21
+ <%=question path, :title => "Address #{index}" do |root,answer| %>
22
+ <div class="textfield">
23
+ <label for="address1">Address</label>
24
+ <input id="address1" type="text" name="b_address1" value="<%=answer['address1']%>"/>
25
+ </div>
26
+ <div class="textfield">
27
+ <input id="address2" type="text" name="b_address2" value="<%=answer['address2']%>"/>
28
+ </div>
29
+
30
+ <div class="textfield">
31
+ <input id="address3" type="text" name="b_address3" value="<%=answer['address3']%>"/>
32
+ </div>
33
+ <div class="textfield">
34
+ <label for="postcode">Postcode</label>
35
+ <input id="postcode" type="text" name="b_postcode" value="<%=answer['postcode']%>"/>
36
+ </div>
37
+ <% if index > 1 %>
38
+ <button class="button-secondary" name="remove_address">Remove</button><br/>
39
+ <% end %>
40
+ <button>Continue</button>
41
+ <% end %>
42
+ <% end %>
43
+ <% when_answered "/page/your_details/address_history" do %>
44
+ <button name="add_address" class="button-secondary">Add another address</button><br/>
45
+ <button>Continue</button>
46
+ <% end %>
47
+
48
+ <% end %>
49
+
50
+ <% when_answered "/page/your_details" do %>
51
+ <button>Continue and Save</button>
52
+ <% end %>
53
+ <% end %>
54
+
55
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <div id="question_section">
2
+ <%= render res[:page], :doop => doop, :res => res %>
3
+ </div>
@@ -0,0 +1,4 @@
1
+ $("#question_section").html( "<%= escape_javascript( render res[:page], :doop => doop, :res => res ) %>" )
2
+ $("#nav_path").val("")
3
+ $("#back_a_page").val("")
4
+ $("#change_answer").val("")
@@ -0,0 +1,3 @@
1
+ <div class="error">
2
+ <span><%= msg %></span>
3
+ </div>
@@ -0,0 +1,4 @@
1
+ <div class="info_box">
2
+ <%=image_tag "info-icon.png", :class => "info_img" %>
3
+ <% content.call %>
4
+ </div>
@@ -0,0 +1,23 @@
1
+ <div class="navbar">
2
+ <ol>
3
+ <%
4
+ i = 1
5
+ pages = res[:all_pages]
6
+ page_path = res[:page_path]
7
+ pages.each do |p|
8
+ nav_name = doop[p]["_nav_name"]
9
+
10
+ if p == page_path %>
11
+ <li class="doing"><%="#{i}. #{nav_name}"%></li>
12
+ <% elsif doop[p]["_answered"] %>
13
+ <li class="done"><a href="#" onclick="$('#nav_path').val('<%=p %>').click(); return false"><%="#{i}. #{nav_name}" %></a></li>
14
+ <% else %>
15
+ <li class="todo"><%="#{i}. #{nav_name}"%></li>
16
+ <%
17
+ end
18
+ i +=1
19
+ end %>
20
+ </ol>
21
+
22
+ </div>
23
+
@@ -0,0 +1,19 @@
1
+ <% if root["_open"] %>
2
+ <% if indent %><div class="indent"><% end %>
3
+ <div class="question-open">
4
+ <h2><%=title != nil ? title : root["_question"]%></h2>
5
+ <% content.call(root, answer) %>
6
+ </div>
7
+ <% if indent %> </div"><% end %>
8
+ <% end %>
9
+
10
+ <% if !root["_open"] && root["_answered"] %>
11
+ <% if indent %><div class="indent"><% end %>
12
+ <div class="question-closed">
13
+ <div class="title"><%=title != nil ? title : root["_question"]%></div>
14
+ <div class="answer">
15
+ <a href="#" class="button" onclick="$('#change_answer_path').val('<%=path %>').click(); return false"><%=root['_summary']%></a>
16
+ </div>
17
+ </div>
18
+ <% if indent %> </div"><% end %>
19
+ <% end %>
@@ -0,0 +1,15 @@
1
+ <%=form_tag( {action: "answer"}, :authenticity_token => true, :remote => true ) do %>
2
+ <div class="question_form">
3
+ <%=render "doop/navbar", :res => res, :doop => doop %>
4
+ <h1><%=res[:page_title]%></h1>
5
+ <% content.call %>
6
+ </div>
7
+
8
+ <% if res[:debug_on] %>
9
+ <br/><br/><textarea cols="115" rows="20"><%= h(doop.dump)%></textarea>
10
+ <% end %>
11
+ <button id="nav_path" style="display: none;" name="nav_path"></button>
12
+ <button id="back_a_page" name="back_a_page" style="display: none;"></button>
13
+ <button id="change_answer_path" style="display: none;" name="change_answer_path"></button>
14
+ <input type="hidden" name="doop_data" value="<%=request["doop_data"]%>"/>
15
+ <% end %>
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+
3
+ <% content_for :head do %>
4
+ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
5
+ <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
6
+ <%= csrf_meta_tags %>
7
+ <% end %>
8
+
9
+ <% content_for :content do %>
10
+ <div class="outer-block">
11
+ <div class="inner-block">
12
+ <div class="phase-banner">
13
+ <strong class="phase-tag">BETA</strong>
14
+ <span>This is a new service – your <a href="#">feedback</a> will help us to improve it.</span>
15
+ </div>
16
+
17
+ <%= yield %>
18
+ </div>
19
+ </div>
20
+ <% end %>
21
+
22
+ <%= render template: "layouts/govuk_template" %>
@@ -0,0 +1,448 @@
1
+ require "spec_helper"
2
+
3
+ describe "Doop" do
4
+
5
+ describe "automatic binding" do
6
+
7
+ let(:question) {
8
+ q = Doop::Doop.new
9
+ q.yaml = (
10
+ <<-EOS
11
+
12
+ root: {
13
+ age: {},
14
+ address: {
15
+ address_line_1: { _answer: "21 The Grove" },
16
+ address_line_2: { _answer: "Telford" },
17
+ address_line_3: {}
18
+ },
19
+
20
+ pets: {
21
+ pet__8: { _answer: "Claude" },
22
+ pet__3: { _answer: "Lucy" }
23
+ }
24
+
25
+ }
26
+
27
+ EOS
28
+ )
29
+ q.init
30
+ q.ask_next
31
+ q
32
+ }
33
+
34
+
35
+ it "attempts to bind anything starting with b_ to the answer" do
36
+ expect(question.currently_asked).to eq( "/root/age" )
37
+ question.answer( { "b_age"=>10 } )
38
+ expect( question["/root/age/_answer"]["age"] ).to eq(10)
39
+
40
+ end
41
+
42
+ end
43
+
44
+ describe "structure management" do
45
+
46
+ let(:question) {
47
+ q = Doop::Doop.new
48
+ q.yaml =
49
+ <<-EOS
50
+
51
+ root: {
52
+ age: {_answer: 25, _answered: true},
53
+ address: {
54
+ address_line_1: { _answer: "21 The Grove" },
55
+ address_line_2: { _answer: "Telford" },
56
+ address_line_3: {}
57
+ },
58
+
59
+ pets: {
60
+ pet__8: { _answer: "Claude" },
61
+ pet__3: { _answer: "Lucy" }
62
+ }
63
+
64
+ }
65
+
66
+ EOS
67
+ q.init
68
+ q.ask_next
69
+ q
70
+ }
71
+
72
+ it "is initialized with a YAML data structure" do
73
+ end
74
+
75
+ it "can be serialized" do
76
+ dump = question.dump
77
+ expect(dump).to include( "root" )
78
+ expect(dump).to eq( Doop::Doop.new(dump).dump )
79
+ end
80
+
81
+ it "allows questions to be accessed like a file system" do
82
+ expect(question["/root/age/_answer"] ).to eq(25)
83
+ expect(question["/root/age"] ).not_to be_nil
84
+ expect(question["/root/address/address_line_1/_answer"] ).to eq("21 The Grove")
85
+ expect(question["/root/address/address_line_3/_answer"] ).to eq(nil)
86
+ end
87
+
88
+ it "allows questions to be answered" do
89
+ question["/root/address/address_line_3/_answer"] = "Shropshire"
90
+ expect(question["/root/address/address_line_3/_answer"] ).to eq("Shropshire")
91
+
92
+ yaml = <<-EOS
93
+
94
+ address_line_1: { _answer: "AAA" }
95
+ address_line_2: { _answer: "BBB" }
96
+
97
+ EOS
98
+
99
+ question["/root/address"] = YAML.load(yaml)
100
+ expect(question["/root/address/address_line_1/_answer"] ).to eq("AAA")
101
+
102
+ end
103
+
104
+ it "allows questions to be added" do
105
+ question.add( "/root/address/address_line_4" )
106
+ question["/root/address/address_line_4/_answer"] = "XXX"
107
+ expect(question["/root/address/address_line_4/_answer"] ).to eq("XXX")
108
+ end
109
+
110
+ it "allows questions to be removed" do
111
+ question.remove( "/root/address/address_line_1" )
112
+ expect(question["/root/address/address_line_1"] ).to eq(nil)
113
+ end
114
+
115
+ it "allows questions to be moved" do
116
+ question.move( "/root/address/address_line_1", "/root/address/address_line_6" )
117
+ expect(question["/root/address/address_line_1"] ).to eq(nil)
118
+ expect(question["/root/address/address_line_6/_answer"] ).to eq("21 The Grove")
119
+ end
120
+
121
+ it "allows question to be renumbered in sequence" do
122
+ question.renumber( "/root/pets" )
123
+ expect(question["/root/pets/pet__1/_answer"] ).to eq("Lucy")
124
+ expect(question["/root/pets/pet__2/_answer"] ).to eq("Claude")
125
+ end
126
+
127
+ it "automatically adds meta data. Meta data starts with _" do
128
+
129
+ expect(question["/root/pets/_answered"] ).not_to be_nil
130
+ expect(question["/root/pets/_open"] ).not_to be_nil
131
+ expect(question["/root/pets/_enabled"] ).not_to be_nil
132
+ expect(question["/root/pets/_answer"] ).to be_nil
133
+
134
+ end
135
+
136
+ end
137
+
138
+
139
+ describe "navigation and answering" do
140
+
141
+
142
+ let(:question) {
143
+ q = Doop::Doop.new
144
+ q.yaml=
145
+ <<-EOS
146
+
147
+ root: {
148
+ age: { _question: "How old are you?"},
149
+ address: {
150
+ _question: "What is your address",
151
+ address_line__1: { _question: "Address Line 1"},
152
+ address_line__2: { _question: "Address Line 2"},
153
+ address_line__3: { _question: "Address Line 3"}
154
+ }
155
+ }
156
+
157
+ EOS
158
+ q.init
159
+ q.ask_next
160
+ q
161
+ }
162
+
163
+ it "gets the next unaswered question" do
164
+ expect(question.currently_asked).to eq( "/root/age" )
165
+ end
166
+
167
+
168
+ it "allows question to be answered in order" do
169
+ expect(question.currently_asked).to eq( "/root/age" )
170
+ question.answer( { "answer" => 36 } )
171
+ expect(question["/root/age/_answer"]).to eq( 36 )
172
+ expect(question["/root/address/_open"]).to eq( true )
173
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
174
+ question.answer( { "answer" => "address1" } )
175
+ expect(question.currently_asked).to eq( "/root/address/address_line__2" )
176
+ question.answer( {"answer" => "address2" } )
177
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
178
+ question.answer( {"answer" => "address3" } )
179
+ expect(question.currently_asked).to eq( "/root/address" )
180
+ question.answer( { "answer" => "#{question['/root/address/address_line__1']}, #{question['/root/address/address_line__2']}, #{question['/root/address/address_line__3']}" } )
181
+ expect(question.currently_asked).to eq( "/root" )
182
+ question.answer( { "answer" => "done" } )
183
+ expect(question.currently_asked).to be_nil
184
+ end
185
+
186
+ it "allows questions to be disabled" do
187
+ question.disable("/root/address")
188
+ expect(question.currently_asked).to eq( "/root/age" )
189
+ question.answer( {"answer" => 36 } )
190
+ expect(question.currently_asked).to eq( "/root" )
191
+ end
192
+
193
+ it "allows earlier questions to be changed" do
194
+ expect(question.currently_asked).to eq( "/root/age" )
195
+ question.answer( {"answer" => 36} )
196
+ expect(question["/root/age/_answer"]).to eq( 36 )
197
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
198
+ question.answer( {"answer" => "address1" } )
199
+ expect(question.currently_asked).to eq( "/root/address/address_line__2" )
200
+ question.answer( {"answer" => "address2"} )
201
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
202
+ question.change( "/root/address/address_line__1" )
203
+ dump = question.dump
204
+ expect( question.dump ).to eq( Doop::Doop.new(dump).dump )
205
+
206
+
207
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
208
+ question.answer( {"answer" => "address1"} )
209
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
210
+
211
+ question.change( "/root/age" )
212
+ expect(question.currently_asked).to eq( "/root/age" )
213
+ question.answer({ "answer" => 35} )
214
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
215
+ end
216
+
217
+ it "provides a mechanism to see if all questions are answered under a given path" do
218
+
219
+ expect(question.currently_asked).to eq( "/root/age" )
220
+ question.answer( {"answer" => 36} )
221
+ expect(question["/root/age/_answer"]).to eq( 36 )
222
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
223
+ question.answer( {"answer" => "address1" } )
224
+ expect(question.currently_asked).to eq( "/root/address/address_line__2" )
225
+ question.answer( {"answer" => "address2"} )
226
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
227
+ question.answer({ "answer" => "address3"} )
228
+ expect(question.all_answered("/root/address")).to eq(true)
229
+ expect(question.all_answered("/root")).to eq(false)
230
+ question.answer({ "answer" => "provided"} )
231
+ expect(question.all_answered("/root")).to eq(true)
232
+ question.answer({ "answer" => "provided"} )
233
+ expect(question.all_answered("/")).to eq(true)
234
+ end
235
+
236
+ end
237
+
238
+ describe "on question answered callbacks" do
239
+
240
+ let(:question) {
241
+ q = Doop::Doop.new
242
+ q.yaml=
243
+ <<-EOS
244
+
245
+ root: {
246
+ address: {
247
+ _question: "What is your address",
248
+ address_line__1: { _question: "Address Line 1"},
249
+ address_line__2: { _question: "Address Line 2"},
250
+ address_line__3: { _question: "Address Line 3"}
251
+ },
252
+ age: {
253
+ _question: "How old are you?",
254
+ _on_answer_handler: "how_old_are_you"
255
+ }
256
+
257
+ }
258
+
259
+ EOS
260
+ q.init
261
+ q.ask_next
262
+ q
263
+
264
+ }
265
+
266
+ it "allows a callback when a question is answered. This allows flows to be changed based on the answer" do
267
+
268
+ question.on_answer "/root/address/address_line__1" do |root, path, context, answer|
269
+ question.enable( "/root/address/address_line__2", context[:button] == :ok )
270
+ root["_answer"] = "answered!"
271
+ root["_summary"] = "my summary"
272
+ root["_answered"] = true
273
+
274
+ end
275
+
276
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
277
+ question.answer( {:button => :skip} )
278
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
279
+ question.change( "/root/address/address_line__1" )
280
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
281
+ question.answer( {:button => :ok} )
282
+ expect(question.currently_asked).to eq( "/root/address/address_line__2" )
283
+ end
284
+
285
+ it "allows callbacks based on a regex expression" do
286
+ question.on_answer "/root/address/address_line__(.*)" do |root, path, context|
287
+ root["_answer"] = "answered!"
288
+ root["_answered"] = true
289
+ end
290
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
291
+ question.answer( {} )
292
+ expect(question["/root/address/address_line__1/_answer"]).to eq( "answered!" )
293
+ question.answer( {} )
294
+ expect(question["/root/address/address_line__2/_answer"]).to eq( "answered!" )
295
+ end
296
+
297
+ it "allows named handlers to be used" do
298
+ question.on_answer "how_old_are_you" do |root,path,context,answer|
299
+ root["_answered"] = true
300
+ end
301
+ question.answer( {} )
302
+ question.answer( {} )
303
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
304
+ question.answer( {"b_answer" => "test"} )
305
+ expect( question[ "/root/address/address_line__3/_answer" ] ).to eq( "test" )
306
+ question.answer( {} )
307
+ expect(question.currently_asked).to eq( "/root/age" )
308
+ question.answer( {"b_age" => 30 } )
309
+ expect(question["/root/age/_answer/age"]).to eq( 30 )
310
+ end
311
+
312
+ end
313
+
314
+ describe "on child question answered callbacks" do
315
+
316
+ let(:nested_question) {
317
+ q = Doop::Doop.new
318
+
319
+ q.yaml=
320
+ <<-EOS
321
+ root: {
322
+ a: {
323
+ b: {
324
+ c1: {},
325
+ c2: {}
326
+ }
327
+ }
328
+ }
329
+ EOS
330
+ q.init
331
+ q.ask_next
332
+ q
333
+
334
+ }
335
+
336
+ let(:question) {
337
+ q = Doop::Doop.new
338
+ q.yaml =
339
+ <<-EOS
340
+
341
+ root: {
342
+ address: {
343
+ _question: "What is your address",
344
+ address_line__1: { _question: "Address Line 1"},
345
+ address_line__2: { _question: "Address Line 2"},
346
+ address_line__3: { _question: "Address Line 3"}
347
+ },
348
+ age: {
349
+ _question: "How old are you?",
350
+ _on_answer_handler: "how_old_are_you"
351
+ }
352
+
353
+ }
354
+
355
+ EOS
356
+ q.init
357
+ q.ask_next
358
+ q
359
+
360
+ }
361
+
362
+ it "allows a callback when all nested questions have been answered" do
363
+ question.on_all_nested_answer "/root/address" do |root,path,context|
364
+ root["_answer"] = "answered!"
365
+ root["_answered"] = true
366
+ end
367
+
368
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
369
+ question.answer( {} )
370
+ expect(question.currently_asked).to eq( "/root/address/address_line__2" )
371
+ question.answer( {} )
372
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
373
+ question.answer( {} )
374
+ # /root/address should be answered by the callback
375
+ expect(question.currently_asked).to eq( "/root/age" )
376
+ end
377
+
378
+ it "recursively descends the question tree, making call backs" do
379
+ nested_question.on_all_nested_answer "/root/a/b" do |root,path,context|
380
+ root["_answer"] = "answered!"
381
+ root["_answered"] = true
382
+ end
383
+ nested_question.on_all_nested_answer "/root/a" do |root,path,context|
384
+ root["_answer"] = "answered!"
385
+ root["_answered"] = true
386
+ end
387
+
388
+ expect(nested_question.currently_asked).to eq( "/root/a/b/c1" )
389
+ nested_question.answer( {} )
390
+ expect(nested_question.currently_asked).to eq( "/root/a/b/c2" )
391
+ nested_question.answer( {} )
392
+ expect(nested_question.currently_asked).to eq( "/root" )
393
+ end
394
+
395
+ end
396
+
397
+ describe "on the fly structure manipulation" do
398
+
399
+ let(:question) {
400
+ q = Doop::Doop.new
401
+ q.yaml =
402
+ <<-EOS
403
+
404
+ root: {
405
+ address: {
406
+ _question: "What is your address",
407
+ address_line__1: { _question: "Address Line 1"},
408
+ address_line__2: { _question: "Address Line 2"},
409
+ address_line__3: { _question: "Address Line 3"}
410
+ },
411
+ age: {
412
+ _question: "How old are you?",
413
+ _on_answer_handler: "how_old_are_you"
414
+ }
415
+
416
+ }
417
+
418
+ EOS
419
+ q.init
420
+ q.ask_next
421
+ q
422
+
423
+ }
424
+
425
+ it "allows questions to be removed and added in flight" do
426
+
427
+ expect(question.currently_asked).to eq( "/root/address/address_line__1" )
428
+ question.answer( {"answer"=>"address1"} )
429
+ question.answer( {"answer"=>"address2"} )
430
+ question.answer( {"answer"=>"address3"} )
431
+ expect(question.currently_asked).to eq( "/root/address" )
432
+
433
+ expect( question["/root/address/address_line__1/_answer"] ).to eq( "address1" )
434
+
435
+ question.remove( "/root/address/address_line__1" )
436
+ question.renumber( "/root/address" )
437
+ question.ask_next
438
+ expect( question["/root/address/address_line__1/_answer"] ).to eq( "address2" )
439
+
440
+ question.add( "/root/address/address_line__3" )
441
+ question.ask_next
442
+ expect(question.currently_asked).to eq( "/root/address/address_line__3" )
443
+
444
+ end
445
+ end
446
+
447
+ end
448
+