datashift_journey 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +391 -0
- data/Rakefile +26 -0
- data/app/assets/stylesheets/datashift_journey/partials/_state_jumper_toolbar.scss.erb +27 -0
- data/app/controllers/concerns/datashift_journey/error_renderer.rb +9 -0
- data/app/controllers/concerns/datashift_journey/review_renderer.rb +21 -0
- data/app/controllers/concerns/datashift_journey/token_based_access.rb +23 -0
- data/app/controllers/concerns/datashift_journey/validate_state.rb +59 -0
- data/app/controllers/datashift_journey/abandon_enrollments_controller.rb +14 -0
- data/app/controllers/datashift_journey/abandonments_controller.rb +17 -0
- data/app/controllers/datashift_journey/api/v1/states_controller.rb +49 -0
- data/app/controllers/datashift_journey/application_controller.rb +53 -0
- data/app/controllers/datashift_journey/errors_controller.rb +24 -0
- data/app/controllers/datashift_journey/journey_ends_controller.rb +19 -0
- data/app/controllers/datashift_journey/journey_plans_controller.rb +166 -0
- data/app/controllers/datashift_journey/page_states_controller.rb +49 -0
- data/app/controllers/datashift_journey/reviews_controller.rb +32 -0
- data/app/controllers/datashift_journey/state_jumper_controller.rb +51 -0
- data/app/factories/datashift_journey/form_object_factory.rb +68 -0
- data/app/forms/datashift_journey/collector/base_collector_form.rb +60 -0
- data/app/forms/datashift_journey/concerns/form_mixin.rb +64 -0
- data/app/forms/datashift_journey/null_form.rb +20 -0
- data/app/helpers/datashift_journey/application_helper.rb +50 -0
- data/app/helpers/datashift_journey/back_link_helper.rb +9 -0
- data/app/models/datashift_journey/collector/data_node.rb +18 -0
- data/app/models/datashift_journey/collector/form_definition.rb +35 -0
- data/app/models/datashift_journey/collector/form_field.rb +61 -0
- data/app/models/datashift_journey/journey_review.rb +65 -0
- data/app/models/datashift_journey/review_data_section.rb +32 -0
- data/app/serializers/datashift_journey/collector/page_state_serializer.rb +9 -0
- data/app/serializers/state_machines/state/state_serializer.rb +5 -0
- data/app/views/datashift_journey/collector/_generic_form.html.erb +14 -0
- data/app/views/datashift_journey/errors/401.html.erb +13 -0
- data/app/views/datashift_journey/errors/403.html.erb +13 -0
- data/app/views/datashift_journey/errors/404.html.erb +13 -0
- data/app/views/datashift_journey/errors/422.html.erb +11 -0
- data/app/views/datashift_journey/errors/500.html.erb +13 -0
- data/app/views/datashift_journey/errors/503.html.erb +11 -0
- data/app/views/datashift_journey/errors/invalid_authenticity_token.html.erb +14 -0
- data/app/views/datashift_journey/journey_ends/new.html.erb +5 -0
- data/app/views/datashift_journey/journey_ends/show.html.erb +5 -0
- data/app/views/datashift_journey/journey_plans/_form.html.erb +14 -0
- data/app/views/datashift_journey/journey_plans/_render_fields.html.erb +24 -0
- data/app/views/datashift_journey/journey_plans/edit.html.erb +6 -0
- data/app/views/datashift_journey/journey_plans/new.html.erb +6 -0
- data/app/views/datashift_journey/shared/_default_actions.html.erb +6 -0
- data/app/views/datashift_journey/shared/_errors.html.erb +19 -0
- data/app/views/datashift_journey/shared/_submit_action.html.erb +5 -0
- data/app/views/datashift_journey/state_jumper/_toolbar.html.erb +16 -0
- data/config/brakeman.ignore +42 -0
- data/config/i18n-tasks.yml +103 -0
- data/config/initializers/exceptions_app.rb +3 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/initializers/rswag-api.rb +14 -0
- data/config/initializers/rswag-ui.rb +9 -0
- data/config/locales/en.yml +39 -0
- data/config/routes.rb +44 -0
- data/lib/datashift_journey/collector/field_snippet.rb +12 -0
- data/lib/datashift_journey/collector/page_state_snippet.rb +12 -0
- data/lib/datashift_journey/collector/snippet.rb +14 -0
- data/lib/datashift_journey/configuration.rb +103 -0
- data/lib/datashift_journey/engine.rb +38 -0
- data/lib/datashift_journey/exceptions.rb +26 -0
- data/lib/datashift_journey/helpers/back_link.rb +58 -0
- data/lib/datashift_journey/journey/machine_builder.rb +59 -0
- data/lib/datashift_journey/prepare_data_for_review.rb +219 -0
- data/lib/datashift_journey/reference_generator.rb +51 -0
- data/lib/datashift_journey/state_machines/branch_sequence_map.rb +35 -0
- data/lib/datashift_journey/state_machines/extensions.rb +40 -0
- data/lib/datashift_journey/state_machines/planner.rb +206 -0
- data/lib/datashift_journey/state_machines/sequence.rb +86 -0
- data/lib/datashift_journey/state_machines/state_machine_core_ext.rb +72 -0
- data/lib/datashift_journey/version.rb +3 -0
- data/lib/datashift_journey.rb +57 -0
- data/lib/generators/datashift_journey/collector/collector_generator.rb +43 -0
- data/lib/generators/datashift_journey/collector/install_collector_generator.rb +61 -0
- data/lib/generators/datashift_journey/collector/install_mongo_collector_generator.rb +44 -0
- data/lib/generators/datashift_journey/collector/templates/collector_concern.rb.tt +34 -0
- data/lib/generators/datashift_journey/collector/templates/collector_migration.rb.tt +46 -0
- data/lib/generators/datashift_journey/forms_generator.rb +45 -0
- data/lib/generators/datashift_journey/generate_common.rb +33 -0
- data/lib/generators/datashift_journey/setup/USAGE +12 -0
- data/lib/generators/datashift_journey/setup/setup_generator.rb +44 -0
- data/lib/generators/datashift_journey/setup/templates/initializer.rb.tt +17 -0
- data/lib/generators/datashift_journey/setup/templates/model_concern.rb.tt +37 -0
- data/lib/generators/datashift_journey/templates/base_form.rb.tt +7 -0
- data/lib/generators/datashift_journey/templates/collector_form.rb.tt +18 -0
- data/lib/generators/datashift_journey/templates/collector_view.rb.tt +15 -0
- data/lib/generators/datashift_journey/templates/journey_plan_form.rb.tt +23 -0
- data/lib/generators/datashift_journey/templates/journey_plan_view.rb.tt +15 -0
- data/lib/generators/datashift_journey/views_generator.rb +35 -0
- data/lib/tasks/state_machine.thor +48 -0
- data/spec/datashift_journey/complex_journey_spec.rb +132 -0
- data/spec/datashift_journey/machine_builder_spec.rb +268 -0
- data/spec/datashift_journey/planner_spec.rb +129 -0
- data/spec/datashift_journey/sequence_spec.rb +20 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +16 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/forms/base_form.rb +8 -0
- data/spec/dummy/app/forms/business_details_form.rb +17 -0
- data/spec/dummy/app/forms/business_type_form.rb +17 -0
- data/spec/dummy/app/forms/contact_details_form.rb +17 -0
- data/spec/dummy/app/forms/enter_reg_number_form.rb +17 -0
- data/spec/dummy/app/forms/new_or_renew_form.rb +17 -0
- data/spec/dummy/app/forms/postal_address_form.rb +17 -0
- data/spec/dummy/app/forms/question1_form.rb +9 -0
- data/spec/dummy/app/forms/question2_form.rb +12 -0
- data/spec/dummy/app/forms/sole_trader_name_form.rb +17 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/payment.rb +5 -0
- data/spec/dummy/app/services/datashift_journey/models/collector_journey.rb +51 -0
- data/spec/dummy/app/views/_question1.html.erb +13 -0
- data/spec/dummy/app/views/_question2.html.erb +9 -0
- data/spec/dummy/app/views/layouts/alternative.html.erb +14 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/pages/home.html.erb +6 -0
- data/spec/dummy/app/views/pages/start.html.erb +5 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +39 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +47 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +47 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/datashift_journey.rb +6 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +27 -0
- data/spec/dummy/config/routes.rb +28 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20160101091218_create_dummy_checkout.rb +43 -0
- data/spec/dummy/db/migrate/20161221100703_datashift_journey_create_collector.rb +56 -0
- data/spec/dummy/db/schema.rb +142 -0
- data/spec/dummy/lib/version.rb +1 -0
- data/spec/factories/collector_factory.rb +16 -0
- data/spec/factories/collector_snippet_factory.rb +9 -0
- data/spec/factories/collector_state_page_factory.rb +12 -0
- data/spec/factories/data_node_factory.rb +9 -0
- data/spec/factories/form_factory.rb +6 -0
- data/spec/features/basic_navigation_spec.rb +125 -0
- data/spec/helpers/application_helper_spec.rb +40 -0
- data/spec/integration/collector/page_state_spec.rb +45 -0
- data/spec/models/collector/collector_spec.rb +100 -0
- data/spec/models/collector/page_state_spec.rb +30 -0
- data/spec/rails_helper.rb +73 -0
- data/spec/requests/collector/api/v1/page_state_spec.rb +85 -0
- data/spec/requests/collector/api/v1/states_spec.rb +28 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/support/asserts.rb +27 -0
- data/spec/support/mailer_macros.rb +25 -0
- data/spec/support/page_objects/base_page_object.rb +77 -0
- data/spec/swagger_helper.rb +25 -0
- metadata +425 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
|
|
5
|
+
module Journey
|
|
6
|
+
|
|
7
|
+
# These tests split out testing diff elements, into separate tests, so for each
|
|
8
|
+
# we need a clean empty class as the State Machines are held at the class level
|
|
9
|
+
# and this is easier than trying to manage diff state machines in one class
|
|
10
|
+
|
|
11
|
+
class Checkout < ActiveRecord::Base
|
|
12
|
+
belongs_to :bill_address, class_name: 'Address'
|
|
13
|
+
belongs_to :ship_address, class_name: 'Address'
|
|
14
|
+
belongs_to :payment
|
|
15
|
+
|
|
16
|
+
def payment_card
|
|
17
|
+
payment.present? ? payment.card : ''
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class CheckoutEmpty < ActiveRecord::Base; end
|
|
22
|
+
class CheckoutA < ActiveRecord::Base; end
|
|
23
|
+
class CheckoutB < ActiveRecord::Base; end
|
|
24
|
+
class CheckoutC < ActiveRecord::Base; end
|
|
25
|
+
|
|
26
|
+
class Payment < ActiveRecord::Base
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
RSpec.describe MachineBuilder do
|
|
30
|
+
describe 'DSL' do
|
|
31
|
+
before(:all) do
|
|
32
|
+
# Only required as we are testing multiple machines using same Class here
|
|
33
|
+
# under normal circumstances build 1 journey per class
|
|
34
|
+
::StateMachines::Machine.ignore_method_conflicts = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'CheckoutEmpty - Simple MachineBuilder with no transitions' do
|
|
38
|
+
let(:klass) { DatashiftJourney.journey_plan_class }
|
|
39
|
+
|
|
40
|
+
before(:all) do
|
|
41
|
+
DatashiftJourney.journey_plan_class = 'DatashiftJourney::Journey::CheckoutEmpty'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'does not break the extended Rails class' do
|
|
45
|
+
expect(klass.new).to be
|
|
46
|
+
expect(klass.create).to be
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'can build an empty machine with a given name' do
|
|
50
|
+
machine = Journey::MachineBuilder.create_journey_plan(machine_name: :checkout_empty)
|
|
51
|
+
|
|
52
|
+
expect(machine).to be_a ::StateMachines::Machine
|
|
53
|
+
|
|
54
|
+
expect(machine.name).to eq :checkout_empty
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context 'CheckoutA - Simple Sequence' do
|
|
59
|
+
before(:all) do
|
|
60
|
+
DatashiftJourney.journey_plan_class = 'DatashiftJourney::Journey::CheckoutA'
|
|
61
|
+
|
|
62
|
+
@machine = MachineBuilder.create_journey_plan(machine_name: :checkout_a, initial: :page1) do
|
|
63
|
+
sequence :page1, :page2, :page3, :page4
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
let(:klass) { DatashiftJourney.journey_plan_class }
|
|
68
|
+
|
|
69
|
+
it 'enables a sequential journey to be planned via list' do
|
|
70
|
+
expect(@machine).to be_a ::StateMachines::Machine
|
|
71
|
+
|
|
72
|
+
expect(@machine).to eq CheckoutA.state_machines[:checkout_a]
|
|
73
|
+
|
|
74
|
+
checkout = CheckoutA.new
|
|
75
|
+
|
|
76
|
+
# Methods are generated based on the StateMachine name, so where the default is state
|
|
77
|
+
# you get the current column name from 'state_name' so for our tests becomes checkout_a_name
|
|
78
|
+
expect(checkout.checkout_a_name).to eq :page1
|
|
79
|
+
expect(checkout.checkout_a).to eq 'page1'
|
|
80
|
+
expect(checkout.page1?).to eq true
|
|
81
|
+
expect(checkout.page4?).to eq false
|
|
82
|
+
|
|
83
|
+
# SO StateMachines::PathCollection seems to map related transitions together, and
|
|
84
|
+
# progressively, so by the last it reports all 6 possible transitions (back/next )
|
|
85
|
+
# not sure based on what yet
|
|
86
|
+
expect(checkout.checkout_a_paths.last.size).to eq 6
|
|
87
|
+
|
|
88
|
+
# events is a StateMachines::EventCollection
|
|
89
|
+
# puts CheckoutA.state_machines[:checkout_a].events.each{|e| puts e.inspect }
|
|
90
|
+
expect(CheckoutA.state_machines[:checkout_a].events.keys.sort).to eq [:back, :next]
|
|
91
|
+
|
|
92
|
+
# puts CheckoutA.state_machines[:checkout_a].events[:back].known_states.inspect
|
|
93
|
+
expect(
|
|
94
|
+
CheckoutA.state_machines[:checkout_a].events[:back].known_states.sort
|
|
95
|
+
).to eq [:page1, :page2, :page3, :page4]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'creates back & next transitions for sequential journey' do
|
|
99
|
+
checkout = CheckoutA.new
|
|
100
|
+
|
|
101
|
+
# initially can only go fwd
|
|
102
|
+
# puts CheckoutA.state_machines[:checkout_a].events.transitions_for(checkout).inspect
|
|
103
|
+
|
|
104
|
+
expect(checkout.checkout_a_events.size).to eq 1
|
|
105
|
+
expect(checkout.checkout_a_transitions.size).to eq 1
|
|
106
|
+
|
|
107
|
+
expect(checkout.can_back?).to eq false
|
|
108
|
+
expect(checkout.can_next?).to eq true
|
|
109
|
+
checkout.next!
|
|
110
|
+
|
|
111
|
+
# puts checkout.methods.sort.grep( /trans/ ).inspect
|
|
112
|
+
|
|
113
|
+
# now we should be able to go back and fwd
|
|
114
|
+
expect(checkout.checkout_a_events.size).to eq 2
|
|
115
|
+
expect(checkout.checkout_a_transitions.size).to eq 2
|
|
116
|
+
|
|
117
|
+
expect(checkout.can_back?).to eq true
|
|
118
|
+
expect(checkout.can_next?).to eq true
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'CheckoutB - Simple Sequence as Array' do
|
|
123
|
+
before(:all) do
|
|
124
|
+
DatashiftJourney.journey_plan_class = 'DatashiftJourney::Journey::CheckoutB'
|
|
125
|
+
|
|
126
|
+
@machine = MachineBuilder.create_journey_plan(machine_name: :checkout_b, initial: :array1) do
|
|
127
|
+
sequence [:array1, :array2, :array3]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
let(:klass) { DatashiftJourney.journey_plan_class }
|
|
132
|
+
|
|
133
|
+
it 'enables a sequential journey to be planned via array' do
|
|
134
|
+
expect(
|
|
135
|
+
CheckoutB.state_machines[:checkout_b].events[:back].known_states.sort
|
|
136
|
+
).to eq [:array1, :array2, :array3]
|
|
137
|
+
|
|
138
|
+
# If I add in This line :
|
|
139
|
+
# puts CheckoutB.state_machine.states.map(&:name).inspect
|
|
140
|
+
#
|
|
141
|
+
# It causes the next line to then fail - but it passes ok once outs is commented out !
|
|
142
|
+
# NoMethodError:
|
|
143
|
+
# undefined method `state=' for #<CheckoutB:0x00000005489018>
|
|
144
|
+
# Did you mean? state?
|
|
145
|
+
# ruby-2.3.1/gems/activemodel-4.2.6/lib/active_model/attribute_methods.rb:433:in `method_missing'
|
|
146
|
+
# ruby-2.3.1/gems/state_machines-0.4.0/lib/state_machines/machine.rb:1074:in `write'
|
|
147
|
+
|
|
148
|
+
checkout = klass.new
|
|
149
|
+
|
|
150
|
+
expect(checkout.checkout_b_name).to eq :array1
|
|
151
|
+
expect(checkout.checkout_b).to eq 'array1'
|
|
152
|
+
|
|
153
|
+
expect(checkout.array2?).to eq false
|
|
154
|
+
|
|
155
|
+
expect(checkout.can_back?).to eq false
|
|
156
|
+
expect(checkout.can_next?).to eq true
|
|
157
|
+
checkout.next!
|
|
158
|
+
expect(checkout.can_back?).to eq true
|
|
159
|
+
expect(checkout.can_next?).to eq true
|
|
160
|
+
checkout.next!
|
|
161
|
+
expect(checkout.can_back?).to eq true
|
|
162
|
+
expect(checkout.can_next?).to eq false
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
context 'Checkout - Complete API' do
|
|
167
|
+
# See
|
|
168
|
+
before(:all) do
|
|
169
|
+
DatashiftJourney.journey_plan_class = 'DatashiftJourney::Journey::Checkout'
|
|
170
|
+
|
|
171
|
+
[:visa, :mastercard, :paypal].each { |p| Payment.create(name: p) }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
let(:payment_types) { [:visa, :mastercard, :paypal] }
|
|
175
|
+
|
|
176
|
+
# STATE ENGINE DEFINITION
|
|
177
|
+
|
|
178
|
+
it 'enables a complete journey to be planned via simple DSL', duff: true do
|
|
179
|
+
MachineBuilder.create_journey_plan(initial: :ship_address) do
|
|
180
|
+
sequence [:ship_address, :bill_address]
|
|
181
|
+
|
|
182
|
+
# first define the sequences
|
|
183
|
+
branch_sequence :visa_sequence, [:page_visa_1, :page_visa_2]
|
|
184
|
+
|
|
185
|
+
branch_sequence :mastercard_sequence, [:page_mastercard_1, :page_mastercard_2, :page_mastercard_3]
|
|
186
|
+
|
|
187
|
+
branch_sequence :paypal_sequence, []
|
|
188
|
+
|
|
189
|
+
# now define the parent state and the routing criteria to each sequence
|
|
190
|
+
|
|
191
|
+
split_on_equality(:payment,
|
|
192
|
+
'payment_card', # Helper method on Checkout that returns card type from Payment
|
|
193
|
+
visa_sequence: 'visa',
|
|
194
|
+
mastercard_sequence: 'mastercard',
|
|
195
|
+
paypal_sequence: 'paypal')
|
|
196
|
+
# byebug
|
|
197
|
+
sequence [:review, :complete]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
checkout = DatashiftJourney.journey_plan_class.new
|
|
201
|
+
|
|
202
|
+
# puts checkout.state_names.inspect
|
|
203
|
+
# puts Checkout.state_machine.states.map(&:name).inspect
|
|
204
|
+
|
|
205
|
+
expect(checkout.state?(:ship_address)).to eq true
|
|
206
|
+
expect(checkout.can_back?).to eq false # this is the initial state
|
|
207
|
+
expect(checkout.can_next?).to eq true
|
|
208
|
+
checkout.next!
|
|
209
|
+
|
|
210
|
+
expect_state_canback_cannext_and_next!(checkout, :bill_address)
|
|
211
|
+
|
|
212
|
+
expect_state_matches(checkout, :payment)
|
|
213
|
+
# But non of the conditions to move on from payment have been met yet so cannot next
|
|
214
|
+
expect(checkout.can_next?).to eq false
|
|
215
|
+
expect(checkout.can_back?).to eq true
|
|
216
|
+
|
|
217
|
+
# TODO: - should we also create an Event per state ?
|
|
218
|
+
# Implications to how to manage acceptable transitions to that event
|
|
219
|
+
# expect(checkout.payment?).to eq true
|
|
220
|
+
|
|
221
|
+
checkout.create_payment!(card: :mastercard)
|
|
222
|
+
|
|
223
|
+
# now the conditions should have been met - one block (mastercard_page) should match payment_card value
|
|
224
|
+
expect(checkout.can_next?).to eq true
|
|
225
|
+
checkout.next!
|
|
226
|
+
|
|
227
|
+
expect_state_canback_cannext_and_next!(checkout, :page_mastercard_1)
|
|
228
|
+
expect_state_canback_cannext_and_next!(checkout, :page_mastercard_2)
|
|
229
|
+
expect_state_canback_cannext_and_next!(checkout, :page_mastercard_3)
|
|
230
|
+
|
|
231
|
+
expect_state_matches(checkout, :review)
|
|
232
|
+
|
|
233
|
+
expect(checkout.can_next?).to eq true
|
|
234
|
+
|
|
235
|
+
# We should go back based on same conditions
|
|
236
|
+
expect(checkout.can_back?).to eq true
|
|
237
|
+
checkout.back!
|
|
238
|
+
|
|
239
|
+
expect_state_canback_cannext_and_next!(checkout, :page_mastercard_3)
|
|
240
|
+
|
|
241
|
+
expect_state_matches(checkout, :review)
|
|
242
|
+
|
|
243
|
+
checkout.next!
|
|
244
|
+
|
|
245
|
+
expect_state_matches(checkout, :complete)
|
|
246
|
+
# End point so no next
|
|
247
|
+
expect(checkout.can_next?).to eq false
|
|
248
|
+
expect(checkout.can_back?).to eq true
|
|
249
|
+
|
|
250
|
+
# TODO: Implement BACK with event transitions - reverse criteria of the start of split next
|
|
251
|
+
|
|
252
|
+
# Now go all way back to split point and try another path
|
|
253
|
+
# checkout.back until(!checkout.can_back? || checkout.payment?)
|
|
254
|
+
#
|
|
255
|
+
# expect_state_matches( checkout, :payment )
|
|
256
|
+
#
|
|
257
|
+
# checkout.payment.update(card: :paypal)
|
|
258
|
+
#
|
|
259
|
+
# checkout.next!
|
|
260
|
+
#
|
|
261
|
+
# check_state_and_next!(checkout, :paypal_page )
|
|
262
|
+
# check_state_and_next!(checkout, :review )
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
|
|
5
|
+
module StateMachines
|
|
6
|
+
|
|
7
|
+
RSpec.describe Planner do
|
|
8
|
+
describe 'DSL' do
|
|
9
|
+
context 'Complex Sequences' do
|
|
10
|
+
it 'enables a split sequence to be first item', duff: true do
|
|
11
|
+
class SomeJourney
|
|
12
|
+
attr_accessor :new_or_renew_value
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
DatashiftJourney.journey_plan_class = 'DatashiftJourney::StateMachines::SomeJourney'
|
|
16
|
+
|
|
17
|
+
Journey::MachineBuilder.create_journey_plan_for(SomeJourney, initial: :new_or_renew) do
|
|
18
|
+
# first define the sequences
|
|
19
|
+
branch_sequence :new_sequence, [:new_sequence_start_page]
|
|
20
|
+
|
|
21
|
+
branch_sequence :renew_sequence, [:renew_sequence_start_page, :enter_reg_number]
|
|
22
|
+
|
|
23
|
+
# now define the parent state and the routing criteria to each sequence
|
|
24
|
+
split_on_equality(:new_or_renew,
|
|
25
|
+
'new_or_renew_value', # Helper method on Collector
|
|
26
|
+
new_sequence: 'new',
|
|
27
|
+
renew_sequence: 'renew')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
journey = DatashiftJourney.journey_plan_class.new
|
|
31
|
+
|
|
32
|
+
# First branch
|
|
33
|
+
journey.new_or_renew_value = 'new'
|
|
34
|
+
|
|
35
|
+
expect_state_matches(journey, :new_or_renew)
|
|
36
|
+
expect(journey.can_back?).to eq false # this is the initial state
|
|
37
|
+
expect(journey.can_next?).to eq true
|
|
38
|
+
journey.next!
|
|
39
|
+
|
|
40
|
+
expect_state_matches(journey, :new_sequence_start_page)
|
|
41
|
+
expect(journey.can_back?).to eq true
|
|
42
|
+
expect(journey.can_next?).to eq false # the end
|
|
43
|
+
|
|
44
|
+
journey.back!
|
|
45
|
+
expect_state_matches(journey, :new_or_renew)
|
|
46
|
+
|
|
47
|
+
# Other branch
|
|
48
|
+
journey = DatashiftJourney.journey_plan_class.new
|
|
49
|
+
|
|
50
|
+
journey.new_or_renew_value = 'renew'
|
|
51
|
+
|
|
52
|
+
expect_state_matches(journey, :new_or_renew)
|
|
53
|
+
expect(journey.can_back?).to eq false # this is the initial state
|
|
54
|
+
expect(journey.can_next?).to eq true
|
|
55
|
+
journey.next!
|
|
56
|
+
|
|
57
|
+
expect_state_canback_cannext_and_next!(journey, :renew_sequence_start_page)
|
|
58
|
+
|
|
59
|
+
expect_state_matches(journey, :enter_reg_number)
|
|
60
|
+
expect(journey.can_back?).to eq true
|
|
61
|
+
expect(journey.can_next?).to eq false # the end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'enables consecutive split sequences which all self terminate' do
|
|
66
|
+
class SomeJourney1
|
|
67
|
+
attr_accessor :new_or_renew_value
|
|
68
|
+
attr_accessor :business_type_value
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
DatashiftJourney.journey_plan_class = 'DatashiftJourney::StateMachines::SomeJourney1'
|
|
72
|
+
|
|
73
|
+
Journey::MachineBuilder.create_journey_plan(initial: :new_or_renew) do
|
|
74
|
+
# test the empty sequences - next should hit split state of next sequence ()
|
|
75
|
+
branch_sequence :new_sequence, []
|
|
76
|
+
|
|
77
|
+
branch_sequence :renew_sequence, [:renew_sequence_start_page, :enter_reg_number]
|
|
78
|
+
|
|
79
|
+
# now define the parent state and the routing criteria to each sequence
|
|
80
|
+
split_on_equality(:new_or_renew,
|
|
81
|
+
'new_or_renew_value', # Helper method on Collector
|
|
82
|
+
new_sequence: 'new',
|
|
83
|
+
renew_sequence: 'renew')
|
|
84
|
+
|
|
85
|
+
branch_sequence :sole_trader_sequence, [:sole_trader_start]
|
|
86
|
+
branch_sequence :partnership_sequence, [:partnership_start]
|
|
87
|
+
branch_sequence :limited_company_sequence, [:limited_company_start]
|
|
88
|
+
|
|
89
|
+
split_on_equality(:business_type,
|
|
90
|
+
'business_type_value',
|
|
91
|
+
sole_trader_sequence: 'sole_trader',
|
|
92
|
+
partnership_sequence: 'partnership',
|
|
93
|
+
limited_company_sequence: 'limited_company')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
journey = DatashiftJourney.journey_plan_class.new
|
|
97
|
+
|
|
98
|
+
# First branch
|
|
99
|
+
journey.new_or_renew_value = 'new'
|
|
100
|
+
|
|
101
|
+
expect_state_matches(journey, :new_or_renew)
|
|
102
|
+
expect(journey.can_back?).to eq false # this is the initial state
|
|
103
|
+
|
|
104
|
+
# new sequence is empty - use case where we just stored the split value and moved onto next sequence
|
|
105
|
+
# which is :business_type
|
|
106
|
+
expect(journey.can_next?).to eq true
|
|
107
|
+
|
|
108
|
+
journey.next!
|
|
109
|
+
|
|
110
|
+
expect_state_matches(journey, :business_type)
|
|
111
|
+
expect(journey.can_back?).to eq true
|
|
112
|
+
|
|
113
|
+
# We don't yet have a valid value for business_type_value so should not be able to proceed
|
|
114
|
+
expect(journey.can_next?).to eq false
|
|
115
|
+
|
|
116
|
+
journey.business_type_value = 'partnership'
|
|
117
|
+
|
|
118
|
+
expect(journey.can_next?).to eq true
|
|
119
|
+
|
|
120
|
+
journey.next!
|
|
121
|
+
|
|
122
|
+
expect_state_matches(journey, :partnership_start)
|
|
123
|
+
expect(journey.can_back?).to eq true
|
|
124
|
+
expect(journey.can_next?).to eq false
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
module DatashiftJourney
|
|
4
|
+
module StateMachines
|
|
5
|
+
|
|
6
|
+
RSpec.describe Sequence do
|
|
7
|
+
let(:list) { [:review, :complete] }
|
|
8
|
+
|
|
9
|
+
describe 'intializing an instance' do
|
|
10
|
+
it 'should create a simple list of states' do
|
|
11
|
+
sequence = Sequence.new(list.flatten)
|
|
12
|
+
|
|
13
|
+
expect(sequence).to be_a Sequence
|
|
14
|
+
expect(sequence.states).to eq list
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
data/spec/dummy/Rakefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
|
2
|
+
// listed below.
|
|
3
|
+
//
|
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
|
6
|
+
//
|
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
|
8
|
+
// compiled file.
|
|
9
|
+
//
|
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
|
11
|
+
// about supported directives.
|
|
12
|
+
//
|
|
13
|
+
//= require_tree .
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
|
11
|
+
* file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
16
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class BusinessDetailForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:business_details
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
# collection :data_nodes do
|
|
11
|
+
# property :field_value
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Example basic validation - has field been filled in :
|
|
15
|
+
# validates :field_value, presence: true
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class BusinessTypeForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:business_type
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
# collection :data_nodes do
|
|
11
|
+
# property :field_value
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Example basic validation - has field been filled in :
|
|
15
|
+
# validates :field_value, presence: true
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class ContactDetailForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:contact_details
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
# collection :data_nodes do
|
|
11
|
+
# property :field_value
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Example basic validation - has field been filled in :
|
|
15
|
+
# validates :field_value, presence: true
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class EnterRegNumberForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:enter_reg_number
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
# collection :data_nodes do
|
|
11
|
+
# property :field_value
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Example basic validation - has field been filled in :
|
|
15
|
+
# validates :field_value, presence: true
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class NewOrRenewForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:new_or_renew
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
collection :data_nodes do
|
|
11
|
+
property :field
|
|
12
|
+
|
|
13
|
+
# Example basic validation - has field been filled in :
|
|
14
|
+
validates :field, presence: true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class PostalAddressForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:postal_address
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
# collection :data_nodes do
|
|
11
|
+
# property :field_value
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Example basic validation - has field been filled in :
|
|
15
|
+
# validates :field_value, presence: true
|
|
16
|
+
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class SoleTraderNameForm < DatashiftJourney::Collector::BaseCollectorForm
|
|
2
|
+
|
|
3
|
+
def params_key
|
|
4
|
+
:sole_trader_name
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Data collection performed by base class but if you need custom processing, the forms
|
|
8
|
+
# fields are represented as a series of DataNode objects, with the actual data saved in :field_value
|
|
9
|
+
#
|
|
10
|
+
# collection :data_nodes do
|
|
11
|
+
# property :field_value
|
|
12
|
+
# end
|
|
13
|
+
|
|
14
|
+
# Example basic validation - has field been filled in :
|
|
15
|
+
# validates :field_value, presence: true
|
|
16
|
+
|
|
17
|
+
end
|