kanban 0.7.0 → 0.8.0
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 +4 -4
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.md +37 -0
- data/VERSION +1 -1
- data/kanban.gemspec +6 -3
- data/lib/kanban.rb +31 -1
- data/spec/kanban_spec.rb +317 -111
- data/spec/spec_helper.rb +8 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a02deca9fb7c7a28e7e8ad7098207514dd7f7923
|
4
|
+
data.tar.gz: 86cd0848296598c34ff29804f074982335ddb7ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e3c12bdac55a092b22cf08e144195e6f42bb061d8f549dc2b482c9effb0002f6df083a7d28638deb96a36b2d7fce1865971dca8491420689763f51b6e65fd1d
|
7
|
+
data.tar.gz: bdc4dd6c4a1bf249771f9fba20b7640daac28ec3c53cb7c4e29616ef372765fad3f9fc2c21b096af52875b5b32fe44b46a602e519b6af085d8c53c58f1e8fbf7
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -4,6 +4,7 @@ GEM
|
|
4
4
|
addressable (2.3.8)
|
5
5
|
autotest-standalone (4.5.11)
|
6
6
|
builder (3.2.2)
|
7
|
+
contracts (0.9)
|
7
8
|
descendants_tracker (0.0.4)
|
8
9
|
thread_safe (~> 0.3, >= 0.3.1)
|
9
10
|
diff-lcs (1.2.5)
|
@@ -93,6 +94,7 @@ PLATFORMS
|
|
93
94
|
DEPENDENCIES
|
94
95
|
autotest-standalone
|
95
96
|
bundler (~> 1.0)
|
97
|
+
contracts
|
96
98
|
jeweler (~> 2.0.1)
|
97
99
|
rdoc (~> 3.12)
|
98
100
|
redis
|
data/README.md
CHANGED
@@ -1,2 +1,39 @@
|
|
1
1
|
# kanban
|
2
2
|
Because your code totally needed an Agile workflow.
|
3
|
+
|
4
|
+
Description
|
5
|
+
===========
|
6
|
+
Kanban provides tools to model task flow in distributed apps.
|
7
|
+
|
8
|
+
Create a Backlog
|
9
|
+
----------------
|
10
|
+
```ruby
|
11
|
+
require 'redis'
|
12
|
+
require 'kanban'
|
13
|
+
|
14
|
+
backlog = Kanban::Backlog.new backend: Redis.new
|
15
|
+
```
|
16
|
+
|
17
|
+
Add some tasks to your shiny new Backlog
|
18
|
+
----------------------------------------
|
19
|
+
```ruby
|
20
|
+
task = { 'foo' => 'bar' }
|
21
|
+
5.times { backlog.add task }
|
22
|
+
```
|
23
|
+
|
24
|
+
(Elsewhere) Stake a claim on a task from the backlog
|
25
|
+
----------------------------------------------------
|
26
|
+
```ruby
|
27
|
+
task_id = backlog.claim # Will block until there is a task, if the backlog is empty or all tasks are being worked.
|
28
|
+
details = backlog.get task_id
|
29
|
+
```
|
30
|
+
|
31
|
+
Mark a task as complete (or unworkable)
|
32
|
+
---------------------------------------
|
33
|
+
```ruby
|
34
|
+
backlog.complete task_id
|
35
|
+
# or backlog.unworkable task_id
|
36
|
+
backlog.done? task_id # => true
|
37
|
+
```
|
38
|
+
|
39
|
+
Claims expire after awhile (default 3 seconds), and become eligible to be worked by something else.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.8.0
|
data/kanban.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: kanban 0.
|
5
|
+
# stub: kanban 0.8.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "kanban"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.8.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Chris Olstrom"]
|
14
|
-
s.date = "2015-
|
14
|
+
s.date = "2015-06-02"
|
15
15
|
s.description = "Because your code totally needed an Agile Workflow"
|
16
16
|
s.email = "chris@olstrom.com"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -48,6 +48,7 @@ Gem::Specification.new do |s|
|
|
48
48
|
|
49
49
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
50
|
s.add_runtime_dependency(%q<redis>, [">= 0"])
|
51
|
+
s.add_runtime_dependency(%q<contracts>, [">= 0"])
|
51
52
|
s.add_development_dependency(%q<rspec>, ["~> 3.2"])
|
52
53
|
s.add_development_dependency(%q<yard>, ["~> 0.7"])
|
53
54
|
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
@@ -60,6 +61,7 @@ Gem::Specification.new do |s|
|
|
60
61
|
s.add_development_dependency(%q<rspec-autotest>, [">= 0"])
|
61
62
|
else
|
62
63
|
s.add_dependency(%q<redis>, [">= 0"])
|
64
|
+
s.add_dependency(%q<contracts>, [">= 0"])
|
63
65
|
s.add_dependency(%q<rspec>, ["~> 3.2"])
|
64
66
|
s.add_dependency(%q<yard>, ["~> 0.7"])
|
65
67
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
@@ -73,6 +75,7 @@ Gem::Specification.new do |s|
|
|
73
75
|
end
|
74
76
|
else
|
75
77
|
s.add_dependency(%q<redis>, [">= 0"])
|
78
|
+
s.add_dependency(%q<contracts>, [">= 0"])
|
76
79
|
s.add_dependency(%q<rspec>, ["~> 3.2"])
|
77
80
|
s.add_dependency(%q<yard>, ["~> 0.7"])
|
78
81
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
data/lib/kanban.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'redis'
|
2
|
+
require 'contracts'
|
2
3
|
require_relative 'kanban/hash_safety'
|
3
4
|
|
4
5
|
module Kanban
|
5
6
|
class Backlog
|
6
7
|
using HashSafety
|
8
|
+
include Contracts
|
7
9
|
|
8
10
|
attr_reader :namespace, :queue, :item
|
9
11
|
|
@@ -14,77 +16,105 @@ module Kanban
|
|
14
16
|
@backend = backend
|
15
17
|
end
|
16
18
|
|
19
|
+
Contract Num => Hash
|
17
20
|
def get(id)
|
18
21
|
@backend.hgetall "#{@item}:#{id}"
|
19
22
|
end
|
20
23
|
|
24
|
+
Contract None => Num
|
21
25
|
def next_id
|
22
26
|
@backend.incr "#{@queue}:id"
|
23
27
|
end
|
24
28
|
|
29
|
+
Contract None => ArrayOf[Num]
|
25
30
|
def todo
|
26
31
|
@backend.lrange("#{@queue}:todo", 0, -1).map(&:to_i)
|
27
32
|
end
|
28
33
|
|
34
|
+
Contract HashOf[String, Any] => Num
|
29
35
|
def add(task)
|
30
|
-
fail TypeError if task.keys_contain_symbols?
|
31
36
|
id = next_id
|
32
37
|
@backend.hmset "#{@item}:#{id}", *task.to_a
|
33
38
|
@backend.lpush "#{@queue}:todo", id
|
34
39
|
id
|
35
40
|
end
|
36
41
|
|
42
|
+
Contract Hash => Num
|
37
43
|
def add!(task)
|
38
44
|
safe = task.with_string_keys
|
39
45
|
add(safe)
|
40
46
|
end
|
41
47
|
|
48
|
+
Contract Maybe[({ duration: Num })] => Num
|
42
49
|
def claim(duration: 3)
|
43
50
|
id = @backend.brpoplpush("#{@queue}:todo", "#{@queue}:doing")
|
44
51
|
@backend.set "#{@item}:#{id}:claimed", true, ex: duration
|
45
52
|
id.to_i
|
46
53
|
end
|
47
54
|
|
55
|
+
Contract Num => Bool
|
48
56
|
def claimed?(id)
|
49
57
|
@backend.exists "#{@item}:#{id}:claimed"
|
50
58
|
end
|
51
59
|
|
60
|
+
Contract None => ArrayOf[Num]
|
52
61
|
def doing
|
53
62
|
@backend.lrange("#{@queue}:doing", 0, -1).map(&:to_i)
|
54
63
|
end
|
55
64
|
|
65
|
+
Contract Num => Bool
|
56
66
|
def complete(id)
|
57
67
|
@backend.setbit("#{@queue}:completed", id, 1).zero?
|
58
68
|
end
|
59
69
|
|
70
|
+
Contract Num => Bool
|
60
71
|
def completed?(id)
|
61
72
|
@backend.getbit("#{@queue}:completed", id) == 1
|
62
73
|
end
|
63
74
|
|
75
|
+
Contract Num => Bool
|
64
76
|
def unworkable(id)
|
65
77
|
@backend.setbit("#{@queue}:unworkable", id, 1).zero?
|
66
78
|
end
|
67
79
|
|
80
|
+
Contract Num => Bool
|
68
81
|
def unworkable?(id)
|
69
82
|
@backend.getbit("#{@queue}:unworkable", id) == 1
|
70
83
|
end
|
71
84
|
|
85
|
+
Contract Num => Bool
|
72
86
|
def done?(id)
|
73
87
|
completed?(id) || unworkable?(id)
|
74
88
|
end
|
75
89
|
|
90
|
+
Contract Num => Bool
|
76
91
|
def release(id)
|
77
92
|
expire_claim id
|
78
93
|
@backend.lrem("#{@queue}:doing", 0, id) > 0
|
79
94
|
end
|
80
95
|
|
96
|
+
Contract Num => Bool
|
81
97
|
def expire_claim(id)
|
82
98
|
@backend.expire "#{@item}:#{id}:claimed", 0
|
83
99
|
end
|
84
100
|
|
101
|
+
Contract Num => Bool
|
85
102
|
def requeue(id)
|
86
103
|
release id
|
87
104
|
@backend.lpush("#{@queue}:todo", id) > 0
|
88
105
|
end
|
106
|
+
|
107
|
+
Contract None => ArrayOf[Num]
|
108
|
+
def groom
|
109
|
+
doing.map do |id|
|
110
|
+
# puts "#{id} has active claim" if claimed? id
|
111
|
+
next if claimed? id
|
112
|
+
if done? id
|
113
|
+
id if release id
|
114
|
+
else
|
115
|
+
id if requeue id
|
116
|
+
end
|
117
|
+
end.compact
|
118
|
+
end
|
89
119
|
end
|
90
120
|
end
|
data/spec/kanban_spec.rb
CHANGED
@@ -2,10 +2,12 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
2
|
require 'timeout'
|
3
3
|
|
4
4
|
describe 'Backlog' do
|
5
|
+
let(:redis) { Redis.new }
|
6
|
+
let(:backlog) { Kanban::Backlog.new backend: redis, namespace: 'kanban:test' }
|
7
|
+
|
5
8
|
before do
|
6
|
-
@backlog = Kanban::Backlog.new backend: Redis.new, namespace: 'kanban:test'
|
7
9
|
task = { 'test' => 'data' }
|
8
|
-
5.times {
|
10
|
+
5.times { backlog.add(task) }
|
9
11
|
end
|
10
12
|
|
11
13
|
after(:all) do
|
@@ -15,159 +17,363 @@ describe 'Backlog' do
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
describe '#new' do
|
21
|
+
it 'should require a backend' do
|
22
|
+
expect { Kanban::Backlog.new }.to raise_error(ArgumentError)
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
+
subject { backlog }
|
26
|
+
it { is_expected.to be_an_instance_of Kanban::Backlog }
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
end
|
28
|
+
context 'when no optional parameters are given' do
|
29
|
+
let(:backlog) { Kanban::Backlog.new backend: redis }
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
describe '.namespace' do
|
32
|
+
subject { backlog.namespace }
|
33
|
+
it { is_expected.to eq 'default' }
|
34
|
+
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
describe '.queue' do
|
37
|
+
subject { backlog.queue }
|
38
|
+
it { is_expected.to eq 'default:tasks' }
|
39
|
+
end
|
37
40
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
describe '.item' do
|
42
|
+
subject { backlog.item }
|
43
|
+
it { is_expected.to eq 'default:task' }
|
44
|
+
end
|
45
|
+
end
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
end
|
47
|
+
context 'when :namespace is "testing"' do
|
48
|
+
let(:backlog) { Kanban::Backlog.new backend: redis, namespace: 'testing' }
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
describe '.namespace' do
|
51
|
+
subject { backlog.namespace }
|
52
|
+
it { is_expected.to eq 'testing' }
|
53
|
+
end
|
54
|
+
end
|
49
55
|
|
50
|
-
|
51
|
-
|
52
|
-
|
56
|
+
context 'when :queue is "tests"' do
|
57
|
+
let(:backlog) { Kanban::Backlog.new backend: redis, queue: 'tests' }
|
58
|
+
describe '.queue' do
|
59
|
+
subject { backlog.queue }
|
60
|
+
it { is_expected.to eq 'default:tests' }
|
61
|
+
end
|
62
|
+
end
|
53
63
|
|
54
|
-
|
55
|
-
|
56
|
-
|
64
|
+
context 'when :item is "test"' do
|
65
|
+
let(:backlog) { Kanban::Backlog.new backend: redis, item: 'test' }
|
66
|
+
describe '.item' do
|
67
|
+
subject { backlog.item }
|
68
|
+
it { is_expected.to eq 'default:test' }
|
69
|
+
end
|
70
|
+
end
|
57
71
|
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
73
|
+
describe '#get' do
|
74
|
+
context 'when the task does not exist' do
|
75
|
+
subject { backlog.get 0 }
|
76
|
+
it { is_expected.to be_empty }
|
77
|
+
end
|
63
78
|
|
64
|
-
|
65
|
-
|
66
|
-
|
79
|
+
context 'when the task is {"test"=>"data"}' do
|
80
|
+
let(:task) { ({ 'test' => 'data' }) }
|
81
|
+
let(:id) { backlog.add task }
|
82
|
+
subject { backlog.get id }
|
83
|
+
it { is_expected.to eq task }
|
84
|
+
end
|
67
85
|
end
|
68
86
|
|
69
|
-
|
70
|
-
|
71
|
-
|
87
|
+
describe '#next_id' do
|
88
|
+
describe 'should return incrementing values' do
|
89
|
+
let!(:last_id) { backlog.next_id }
|
90
|
+
subject { backlog.next_id }
|
91
|
+
it { is_expected.to be > last_id }
|
92
|
+
end
|
72
93
|
end
|
73
94
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
95
|
+
describe '#todo' do
|
96
|
+
context 'when there are no tasks pending' do
|
97
|
+
before { redis.del "#{backlog.queue}:todo" }
|
98
|
+
subject { backlog.todo }
|
99
|
+
it { is_expected.to be_empty }
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when there are tasks pending' do
|
103
|
+
let(:task) { ({ 'test' => 'data' }) }
|
104
|
+
let!(:id) { backlog.add task }
|
105
|
+
subject { backlog.todo }
|
106
|
+
it { is_expected.to_not be_empty }
|
107
|
+
it { is_expected.to include id }
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when a task is requeued' do
|
111
|
+
let(:id) { backlog.claim }
|
112
|
+
before { backlog.requeue id }
|
113
|
+
subject { backlog.todo }
|
114
|
+
it { is_expected.to include id }
|
115
|
+
end
|
78
116
|
end
|
79
117
|
|
80
|
-
|
81
|
-
|
118
|
+
describe '#add' do
|
119
|
+
context 'when task is a hash with symbol keys' do
|
120
|
+
let(:task) { ({ foo: 'bar' }) }
|
121
|
+
it 'should raise a ParamContractError' do
|
122
|
+
expect { backlog.add task }.to raise_error(ParamContractError)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'when task is a hash with string keys' do
|
127
|
+
let(:task) { ({ 'test' => 'data' }) }
|
128
|
+
let!(:id) { backlog.next_id + 1}
|
129
|
+
subject { backlog.add task }
|
130
|
+
it { is_expected.to eq id }
|
131
|
+
end
|
82
132
|
end
|
83
133
|
|
84
|
-
|
85
|
-
|
86
|
-
|
134
|
+
describe '#add!' do
|
135
|
+
context 'when task is a hash with symbol keys' do
|
136
|
+
let(:task) { ({ test: 'data' }) }
|
137
|
+
let!(:id) { backlog.next_id + 1 }
|
138
|
+
subject { backlog.add! task }
|
139
|
+
it { is_expected.to eq id }
|
140
|
+
end
|
87
141
|
end
|
88
142
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
143
|
+
describe '#claimed?' do
|
144
|
+
context 'when a claim does not exist' do
|
145
|
+
subject { backlog.claimed? 0 }
|
146
|
+
it { is_expected.to be false }
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when a claim exists' do
|
150
|
+
let(:id) { backlog.claim }
|
151
|
+
subject { backlog.claimed? id }
|
152
|
+
it { is_expected.to be true }
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'when a claim has expired' do
|
156
|
+
let!(:id) { backlog.claim duration: 1 }
|
157
|
+
before { sleep 1.1 }
|
158
|
+
subject { backlog.claimed? id }
|
159
|
+
it { is_expected.to be false }
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'when a claim has been forcibly expired' do
|
163
|
+
let(:id) { backlog.claim }
|
164
|
+
before { backlog.expire_claim id }
|
165
|
+
subject { backlog.claimed? id }
|
166
|
+
it { is_expected.to be false }
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'when a task has been released' do
|
170
|
+
let(:id) { backlog.claim }
|
171
|
+
before { backlog.release id }
|
172
|
+
subject { backlog.claimed? id }
|
173
|
+
it { is_expected.to be false }
|
174
|
+
end
|
93
175
|
end
|
94
176
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
177
|
+
describe '#claim' do
|
178
|
+
context 'when there are no pending tasks' do
|
179
|
+
before { redis.del "#{backlog.queue}:todo" }
|
180
|
+
it 'should block' do
|
181
|
+
expect do
|
182
|
+
Timeout.timeout(0.1) do
|
183
|
+
backlog.claim
|
184
|
+
end
|
185
|
+
end.to raise_error(Timeout::Error)
|
101
186
|
end
|
102
|
-
end
|
103
|
-
end
|
187
|
+
end
|
104
188
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
189
|
+
context 'when there are pending tasks' do
|
190
|
+
before { backlog.add ({ 'test' => 'data' }) }
|
191
|
+
subject { backlog.claim }
|
192
|
+
it { is_expected.to be_a Fixnum }
|
193
|
+
end
|
109
194
|
end
|
110
195
|
|
111
|
-
|
112
|
-
id
|
113
|
-
|
114
|
-
|
196
|
+
describe '#doing' do
|
197
|
+
let!(:id) { backlog.claim }
|
198
|
+
subject { backlog.doing }
|
199
|
+
it { is_expected.to include id }
|
115
200
|
|
116
|
-
|
117
|
-
|
118
|
-
|
201
|
+
context 'when a task is released' do
|
202
|
+
before { backlog.release id }
|
203
|
+
subject { backlog.doing }
|
204
|
+
it { is_expected.to_not include id }
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'when a task is requeued' do
|
208
|
+
before { backlog.requeue id }
|
209
|
+
subject { backlog.doing }
|
210
|
+
it { is_expected.to_not include id }
|
211
|
+
end
|
119
212
|
end
|
120
213
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
214
|
+
describe '#complete' do
|
215
|
+
context 'when task has not been marked complete' do
|
216
|
+
subject { backlog.complete 1 }
|
217
|
+
it { is_expected.to be true }
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'when task has been marked complete' do
|
221
|
+
before { backlog.complete 1 }
|
222
|
+
subject { backlog.complete 1 }
|
223
|
+
it { is_expected.to be false }
|
224
|
+
end
|
125
225
|
end
|
126
226
|
|
127
|
-
|
128
|
-
|
129
|
-
|
227
|
+
describe '#completed?' do
|
228
|
+
context 'when task has not been marked complete' do
|
229
|
+
subject { backlog.completed? 2 }
|
230
|
+
it { is_expected.to be false }
|
231
|
+
end
|
232
|
+
|
233
|
+
context 'when task has been marked complete' do
|
234
|
+
before { backlog.complete 3 }
|
235
|
+
subject { backlog.completed? 3 }
|
236
|
+
it { is_expected.to be true }
|
237
|
+
end
|
130
238
|
end
|
131
239
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
240
|
+
describe '#unworkable' do
|
241
|
+
context 'when task has not been marked unworkable' do
|
242
|
+
subject { backlog.unworkable 1 }
|
243
|
+
it { is_expected.to be true }
|
244
|
+
end
|
245
|
+
|
246
|
+
context 'when task has been marked unworkable' do
|
247
|
+
before { backlog.unworkable 1 }
|
248
|
+
subject { backlog.unworkable 1 }
|
249
|
+
it { is_expected.to be false }
|
250
|
+
end
|
136
251
|
end
|
137
252
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
253
|
+
describe '#unworkable?' do
|
254
|
+
context 'when task has not been marked unworkable' do
|
255
|
+
subject { backlog.unworkable? 2 }
|
256
|
+
it { is_expected.to be false }
|
257
|
+
end
|
258
|
+
|
259
|
+
context 'when task has been marked unworkable' do
|
260
|
+
before { backlog.unworkable 3 }
|
261
|
+
subject { backlog.unworkable? 3 }
|
262
|
+
it { is_expected.to be true }
|
263
|
+
end
|
144
264
|
end
|
145
265
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
266
|
+
describe '#done?' do
|
267
|
+
context 'when task has not been marked either complete or unworkable' do
|
268
|
+
subject { backlog.done? 0 }
|
269
|
+
it { is_expected.to be false }
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'when task has been marked complete' do
|
273
|
+
before { backlog.complete 5 }
|
274
|
+
subject { backlog.done? 5 }
|
275
|
+
it { is_expected.to be true }
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'when task has been marked unworkable' do
|
279
|
+
before { backlog.unworkable 6 }
|
280
|
+
subject { backlog.done? 6 }
|
281
|
+
it { is_expected.to be true }
|
282
|
+
end
|
151
283
|
end
|
152
284
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
285
|
+
describe '#release' do
|
286
|
+
context 'when task is claimed' do
|
287
|
+
let(:id) { backlog.claim }
|
288
|
+
subject { backlog.release id }
|
289
|
+
it { is_expected.to be true }
|
290
|
+
end
|
291
|
+
|
292
|
+
context 'when task was not claimed' do
|
293
|
+
subject { backlog.release 0 }
|
294
|
+
it { is_expected.to be false }
|
295
|
+
end
|
158
296
|
end
|
159
297
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
298
|
+
describe '#expire_claim' do
|
299
|
+
context 'when task was not claimed' do
|
300
|
+
subject { backlog.expire_claim 0 }
|
301
|
+
it { is_expected.to be false }
|
302
|
+
end
|
303
|
+
|
304
|
+
context 'when task was claimed' do
|
305
|
+
let(:id) { backlog.claim }
|
306
|
+
subject { backlog.expire_claim id }
|
307
|
+
it { is_expected.to be true }
|
308
|
+
end
|
165
309
|
end
|
166
310
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
311
|
+
describe '#groom' do
|
312
|
+
context 'when a task is claimed' do
|
313
|
+
let(:id) { backlog.claim }
|
314
|
+
|
315
|
+
context 'and the claim has expired' do
|
316
|
+
before { backlog.expire_claim id }
|
317
|
+
subject { backlog.groom }
|
318
|
+
it { is_expected.to include id }
|
319
|
+
|
320
|
+
context 'when #groom is called' do
|
321
|
+
before { backlog.groom }
|
322
|
+
|
323
|
+
describe '#todo' do
|
324
|
+
subject { backlog.todo }
|
325
|
+
it { is_expected.to include id }
|
326
|
+
end
|
327
|
+
|
328
|
+
describe '#doing' do
|
329
|
+
subject { backlog.doing }
|
330
|
+
it { is_expected.to_not include id }
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context 'and the task is done' do
|
336
|
+
before { backlog.complete id }
|
337
|
+
|
338
|
+
context 'and the claim has expired' do
|
339
|
+
before { backlog.expire_claim id }
|
340
|
+
|
341
|
+
subject { backlog.groom }
|
342
|
+
it { is_expected.to include id }
|
343
|
+
|
344
|
+
context 'when #groom is called' do
|
345
|
+
before { backlog.groom }
|
346
|
+
describe '#todo' do
|
347
|
+
subject { backlog.todo }
|
348
|
+
it { is_expected.to_not include id }
|
349
|
+
end
|
350
|
+
|
351
|
+
describe '#doing' do
|
352
|
+
subject { backlog.doing }
|
353
|
+
it { is_expected.to_not include id }
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'and the claim has not expired' do
|
359
|
+
subject { backlog.groom }
|
360
|
+
it { is_expected.to_not include id }
|
361
|
+
|
362
|
+
context 'when #groom is called' do
|
363
|
+
before { backlog.groom }
|
364
|
+
|
365
|
+
describe '#todo' do
|
366
|
+
subject { backlog.todo }
|
367
|
+
it { is_expected.to_not include id }
|
368
|
+
end
|
369
|
+
|
370
|
+
describe '#doing' do
|
371
|
+
subject { backlog.doing }
|
372
|
+
it { is_expected.to include id }
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
172
378
|
end
|
173
379
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -15,6 +15,8 @@ end
|
|
15
15
|
|
16
16
|
ENV['COVERAGE'] && SimpleCov.start do
|
17
17
|
add_filter '/.rvm/'
|
18
|
+
add_filter '/.rubies/'
|
19
|
+
add_filter '/vendor/'
|
18
20
|
end
|
19
21
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
20
22
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
@@ -25,3 +27,9 @@ require 'kanban'
|
|
25
27
|
# Requires supporting files with custom matchers and macros, etc,
|
26
28
|
# in ./support/ and its subdirectories.
|
27
29
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
30
|
+
|
31
|
+
RSpec.configure do |config|
|
32
|
+
config.expect_with :rspec do |c|
|
33
|
+
c.syntax = :expect
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kanban
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Olstrom
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: contracts
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rspec
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|