flow_machine 0.2.2 → 0.2.3

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.
@@ -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