flow_machine 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ require "ostruct"
2
+
3
+ RSpec.describe FlowMachine::WorkflowState do
4
+ class DraftClass < described_class
5
+ def self.state_name
6
+ :draft
7
+ end
8
+ on_exit { object.published_at = :exited_draft }
9
+ event :publish do
10
+ transition to: :published
11
+ end
12
+ end
13
+
14
+ class PublishedClass < described_class
15
+ def self.state_name
16
+ :published
17
+ end
18
+ on_enter :entering
19
+ on_exit :exiting
20
+ def entering
21
+ object.published_at = :entered_published
22
+ end
23
+
24
+ def exiting
25
+ object.published_at = :exited_published
26
+ end
27
+ event :draft do
28
+ transition to: :draft
29
+ end
30
+ event :to_self do
31
+ transition to: :published
32
+ end
33
+ end
34
+
35
+ class WorkflowTestClass
36
+ include FlowMachine::Workflow
37
+ state DraftClass
38
+ state PublishedClass
39
+ end
40
+
41
+ let(:workflow) { WorkflowTestClass.new(object) }
42
+
43
+ describe "when transitioning from draft to published" do
44
+ let(:object) { OpenStruct.new(state: :draft) }
45
+
46
+ it "triggers the entering of published state" do
47
+ expect { workflow.publish }.to change(object, :published_at).to be(:entered_published)
48
+ end
49
+ end
50
+
51
+ describe "when transitioning from published to draft" do
52
+ let(:object) { OpenStruct.new(state: :published, published_at: :something) }
53
+
54
+ it "triggers the exiting of published state" do
55
+ expect { workflow.draft }.to change(object, :published_at).from(:something).to(:exited_published)
56
+ end
57
+ end
58
+
59
+ describe "when transitioning from published to published" do
60
+ let(:object) { OpenStruct.new(state: :published, published_at: :something) }
61
+
62
+ it "does not trigger the on_enter or on_exit" do
63
+ expect { workflow.to_self }.not_to change(object, :published_at)
64
+ end
65
+ end
66
+ end
@@ -1,155 +1,168 @@
1
1
  RSpec.describe FlowMachine::WorkflowState do
2
2
  class StateTestClass < described_class
3
- def self.state_name; :test; end
3
+ def self.state_name
4
+ :test
5
+ end
6
+
4
7
  def guard1?; end
8
+
5
9
  def guard2?; end
10
+
6
11
  def after_hook; end
7
12
  end
8
13
 
9
14
  class WorkflowTestClass
10
15
  include FlowMachine::Workflow
11
16
  def workflow_guard?; end
17
+
12
18
  def workflow_hook; end
13
19
  end
14
20
 
15
21
  let(:state_class) { Class.new(StateTestClass) }
16
22
  let(:workflow_class) { Class.new WorkflowTestClass }
17
23
 
18
- before :each do
24
+ before do
19
25
  workflow_class.state state_class
20
26
  end
21
27
 
22
28
  let(:object) { double state: :test, changes: {}, save: true, new_record?: true }
23
29
  let(:workflow) { workflow_class.new(object) }
24
30
 
25
- describe 'defining events' do
26
- context 'a basic event' do
31
+ describe "defining events" do
32
+ context "a basic event" do
33
+ subject(:state) { state_class.new(workflow) }
27
34
 
28
35
  before { state_class.event :event1 }
29
- subject(:state) { state_class.new(workflow) }
30
36
 
31
- it 'defines the event on the object' do
37
+ it "defines the event on the object" do
32
38
  expect(state).to respond_to :event1
33
39
  end
34
40
 
35
- it 'defines may_event1?' do
41
+ it "defines may_event1?" do
36
42
  expect(state).to respond_to :may_event1?
37
43
  end
38
44
 
39
- it 'defines the bang event' do
45
+ it "defines the bang event" do
40
46
  expect(state).to respond_to :event1!
41
47
  end
42
48
  end
43
49
  end
44
50
 
45
- describe 'invalid transitions' do
51
+ describe "invalid transitions" do
46
52
  let(:state_class2) do
47
53
  Class.new(described_class) do
48
- def self.state_name; :test2; end
54
+ def self.state_name
55
+ :test2
56
+ end
49
57
  end
50
58
  end
51
59
 
52
- before :each do
60
+ before do
53
61
  state_class.event(:event1) { transition to: :test2 }
54
62
  workflow_class.refresh_state_methods!
55
63
  workflow_class.state state_class2
56
64
  end
57
65
 
58
- it 'returns false when trying to transition to the current state' do
66
+ it "returns false when trying to transition to the current state" do
59
67
  expect(object).to receive(:state).and_return :test2
60
68
  expect(workflow.event1).to be false
61
69
  end
62
70
 
63
- it 'sets an invalid_event error when trying to transition to the curent state' do
71
+ it "sets an invalid_event error when trying to transition to the curent state" do
64
72
  expect(object).to receive(:state).and_return :test2
65
73
  workflow.event1
66
74
  expect(workflow.guard_errors).to eq([:invalid_event])
67
75
  end
68
76
 
69
- it 'sets an invalid_event error when trying to may to the current state' do
77
+ it "sets an invalid_event error when trying to may to the current state" do
70
78
  expect(object).to receive(:state).and_return :test2
71
79
  expect(workflow).not_to be_may_event1
72
80
  expect(workflow.guard_errors).to eq([:invalid_event])
73
81
  end
74
82
  end
75
83
 
76
- describe 'guards' do
84
+ describe "guards" do
77
85
  subject(:state) { workflow.current_state }
78
86
 
79
- context 'a single guard' do
87
+ context "a single guard" do
80
88
  before { state_class.event(:event1, guard: :guard1?) {} }
81
- it 'calls the guard' do
89
+
90
+ it "calls the guard" do
82
91
  expect(state).to receive(:guard1?).and_return true
83
92
  state.event1
84
93
  end
85
94
 
86
- describe 'may?' do
87
- it 'is able to transition if the guard returns true' do
95
+ describe "may?" do
96
+ it "is able to transition if the guard returns true" do
88
97
  expect(state).to receive(:guard1?).and_return true
89
98
  expect(state.may_event1?).to be true
90
99
  end
91
100
 
92
- it 'is not able to transition if the guard returns false' do
101
+ it "is not able to transition if the guard returns false" do
93
102
  expect(state).to receive(:guard1?).and_return false
94
103
  expect(state.may_event1?).to be false
95
104
  end
96
105
  end
97
106
  end
98
107
 
99
- context 'the guard method is on the workflow instead' do
108
+ context "the guard method is on the workflow instead" do
100
109
  before { state_class.event(:event1, guard: :workflow_guard?) {} }
101
- it 'calls the guard' do
110
+
111
+ it "calls the guard" do
102
112
  expect(workflow).to receive(:workflow_guard?).and_return true
103
113
  state.event1
104
114
  end
105
115
  end
106
116
 
107
- context 'the guard method is on the object' do
117
+ context "the guard method is on the object" do
108
118
  before { state_class.event(:event1, guard: :object_guard?) {} }
109
- it 'calls the guard' do
119
+
120
+ it "calls the guard" do
110
121
  expect(object).to receive(:object_guard?).and_return true
111
122
  state.event1
112
123
  end
113
124
  end
114
125
 
115
- context 'multiple guards' do
126
+ context "multiple guards" do
116
127
  before do
117
- state_class.event(:event1, guard: [:guard1?, :guard2?]) {}
128
+ state_class.event(:event1, guard: %i[guard1? guard2?]) {}
118
129
  workflow_class.refresh_state_methods!
119
130
  end
120
- it 'calls all the guards' do
131
+
132
+ it "calls all the guards" do
121
133
  expect(state).to receive(:guard1?).and_return true
122
134
  expect(state).to receive(:guard2?).and_return true
123
135
  state.event1
124
136
  end
125
137
 
126
- context 'one guard returns false' do
127
- before :each do
138
+ context "one guard returns false" do
139
+ before do
128
140
  expect(state).to receive(:guard1?).and_return false
129
141
  expect(state).to receive(:guard2?).and_return true
130
142
  end
131
143
 
132
- it 'gets the guard errors on may_event' do
144
+ it "gets the guard errors on may_event" do
133
145
  workflow.may_event1?
134
146
  expect(workflow.guard_errors).to eq([:guard1?])
135
147
  end
136
148
 
137
- it 'gets the guard errors on transition' do
149
+ it "gets the guard errors on transition" do
138
150
  workflow.event1
139
151
  expect(workflow.guard_errors).to eq([:guard1?])
140
152
  end
141
153
  end
142
154
  end
143
155
 
144
- it 'does not call the event block if the guard fails' do
145
- state_class.event(:event1, guard: [:guard1?]) { raise 'Should not call block' }
156
+ it "does not call the event block if the guard fails" do
157
+ state_class.event(:event1, guard: [:guard1?]) { raise "Should not call block" }
146
158
  expect(state).to receive(:guard1?).and_return false
147
159
  state.event1
148
160
  end
149
161
  end
150
162
 
151
- describe 'triggering workflow after_transition hook' do
163
+ describe "triggering workflow after_transition hook" do
152
164
  let(:state) { workflow.current_state }
165
+
153
166
  before do
154
167
  workflow_class.after_transition :workflow_hook
155
168
 
@@ -160,9 +173,9 @@ RSpec.describe FlowMachine::WorkflowState do
160
173
  workflow_class.refresh_state_methods!
161
174
  end
162
175
 
163
- it 'calls the hook on transition' do
176
+ it "calls the hook on transition" do
164
177
  # allow the transition, and make it think it's a different state
165
- expect(workflow).to receive(:current_state_name=).with('state2')
178
+ expect(workflow).to receive(:current_state_name=).with("state2")
166
179
  expect(state).to receive(:==).and_return false
167
180
 
168
181
  expect(state).to receive(:guard1?).and_return(true)
@@ -170,48 +183,77 @@ RSpec.describe FlowMachine::WorkflowState do
170
183
  workflow.event1
171
184
  end
172
185
 
173
- it 'does not call the hook on failure' do
186
+ it "does not call the hook on failure" do
174
187
  expect(state).to receive(:guard1?).and_return(false)
175
188
  expect(workflow).not_to receive(:workflow_hook)
176
189
  workflow.event1
177
190
  end
178
191
  end
179
192
 
180
- describe 'after transition hooks' do
193
+ describe "after transition hooks" do
181
194
  let(:state) { workflow.current_state }
182
- before :each do
195
+
196
+ before do
183
197
  state_class.event :event1 do
184
198
  transition to: :state2, after: :after_hook
185
199
  end
186
200
 
187
201
  workflow_class.refresh_state_methods!
188
202
 
189
- expect(workflow).to receive(:current_state_name=).with('state2')
203
+ expect(workflow).to receive(:current_state_name=).with("state2")
190
204
  end
191
205
 
192
- it 'does not call the hook before saving' do
193
- expect(state).to receive(:after_hook).never
206
+ it "does not call the hook before saving" do
207
+ expect(state).not_to receive(:after_hook)
194
208
  workflow.event1
195
209
  end
196
210
 
197
- it 'calls the hook after saving the transition' do
211
+ it "calls the hook after saving the transition" do
198
212
  expect(state).to receive(:after_hook).once
199
213
  workflow.event1
200
214
  workflow.persist
201
215
  end
202
216
  end
203
217
 
204
- describe 'after_enter' do
218
+ describe "on_enter" do
205
219
  let(:state) { state_class.new(workflow) }
206
- context 'the method is on the workflow' do
220
+
221
+ context "the method is on the workflow" do
222
+ before { state_class.on_enter :workflow_hook }
223
+
224
+ it "can call the method" do
225
+ expect(workflow).to receive(:workflow_hook)
226
+ state.fire_callbacks(:on_enter, {})
227
+ end
228
+ end
229
+ end
230
+
231
+ describe "on_exit" do
232
+ let(:state) { state_class.new(workflow) }
233
+
234
+ context "the method is on the workflow" do
235
+ before { state_class.on_exit :workflow_hook }
236
+
237
+ it "can call the method" do
238
+ expect(workflow).to receive(:workflow_hook)
239
+ state.fire_callbacks(:on_exit, {})
240
+ end
241
+ end
242
+ end
243
+
244
+ describe "after_enter" do
245
+ let(:state) { state_class.new(workflow) }
246
+
247
+ context "the method is on the workflow" do
207
248
  before { state_class.after_enter :workflow_hook }
249
+
208
250
  it "can call the method" do
209
251
  expect(workflow).to receive(:workflow_hook)
210
252
  state.fire_callbacks(:after_enter, {})
211
253
  end
212
254
  end
213
255
 
214
- context 'the method is on the object' do
256
+ context "the method is on the object" do
215
257
  before { state_class.after_enter :object_hook }
216
258
 
217
259
  it "can call the method" do
@@ -221,41 +263,41 @@ RSpec.describe FlowMachine::WorkflowState do
221
263
  end
222
264
  end
223
265
 
224
- describe '#run_workflow_method' do
266
+ describe "#run_workflow_method" do
225
267
  let(:state) { state_class.new(workflow) }
226
268
 
227
- context 'nothing in the chain has the method' do
228
- it 'raises a NoMethodError' do
269
+ context "nothing in the chain has the method" do
270
+ it "raises a NoMethodError" do
229
271
  expect { state.run_workflow_method :some_method }.to raise_error(NoMethodError)
230
272
  end
231
273
  end
232
274
 
233
- context 'the object defines the method' do
234
- before :each do
275
+ context "the object defines the method" do
276
+ before do
235
277
  allow(object).to receive(:some_method)
236
278
  end
237
279
 
238
- it 'calls the method on object' do
280
+ it "calls the method on object" do
239
281
  expect(object).to receive(:some_method)
240
282
  state.run_workflow_method :some_method
241
283
  end
242
284
 
243
- context 'and the workflow defines the method' do
244
- before :each do
285
+ context "and the workflow defines the method" do
286
+ before do
245
287
  workflow.singleton_class.send(:define_method, :some_method) {}
246
288
  end
247
289
 
248
- it 'calls the method on workflow' do
290
+ it "calls the method on workflow" do
249
291
  expect(workflow).to receive(:some_method)
250
292
  state.run_workflow_method :some_method
251
293
  end
252
294
 
253
- context 'and the state defines the method' do
254
- before :each do
295
+ context "and the state defines the method" do
296
+ before do
255
297
  state.singleton_class.send(:define_method, :some_method) {}
256
298
  end
257
299
 
258
- it 'calls the method on the state' do
300
+ it "calls the method on the state" do
259
301
  expect(state).to receive(:some_method)
260
302
  state.run_workflow_method :some_method
261
303
  end
@@ -1,8 +1,8 @@
1
- GEM_ROOT = File.expand_path('../../', __FILE__)
2
- $LOAD_PATH.unshift File.join(GEM_ROOT, 'lib')
1
+ GEM_ROOT = File.expand_path("..", __dir__)
2
+ $LOAD_PATH.unshift File.join(GEM_ROOT, "lib")
3
3
 
4
- require 'rspec/its'
5
- require 'flow_machine'
4
+ require "rspec/its"
5
+ require "flow_machine"
6
6
 
7
7
  # This file was generated by the `rspec --init` command. Conventionally, all
8
8
  # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
@@ -44,8 +44,8 @@ RSpec.configure do |config|
44
44
  mocks.verify_partial_doubles = true
45
45
  end
46
46
 
47
- # The settings below are suggested to provide a good initial experience
48
- # with RSpec, but feel free to customize to your heart's content.
47
+ # The settings below are suggested to provide a good initial experience
48
+ # with RSpec, but feel free to customize to your heart's content.
49
49
 
50
50
  # These two settings work together to allow you to limit a spec run
51
51
  # to individual examples or groups you care about by tagging them with
@@ -72,7 +72,7 @@ RSpec.configure do |config|
72
72
  # Use the documentation formatter for detailed output,
73
73
  # unless a formatter has already been configured
74
74
  # (e.g. via a command-line flag).
75
- config.default_formatter = 'doc'
75
+ config.default_formatter = "doc"
76
76
  end
77
77
 
78
78
  # Print the 10 slowest examples and example groups at the