minder 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.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/assets/done.wav +0 -0
- data/assets/start.wav +0 -0
- data/bin/minder +7 -0
- data/lib/minder/application.rb +128 -0
- data/lib/minder/break_period.rb +13 -0
- data/lib/minder/config.rb +35 -0
- data/lib/minder/frame.rb +172 -0
- data/lib/minder/idle_period.rb +25 -0
- data/lib/minder/message_frame.rb +124 -0
- data/lib/minder/period.rb +35 -0
- data/lib/minder/pomodoro_frame.rb +54 -0
- data/lib/minder/pomodoro_period.rb +13 -0
- data/lib/minder/pomodoro_runner.rb +69 -0
- data/lib/minder/quick_add_frame.rb +47 -0
- data/lib/minder/scene.rb +85 -0
- data/lib/minder/task.rb +28 -0
- data/lib/minder/task_recorder.rb +120 -0
- data/lib/minder/timer.rb +32 -0
- data/lib/minder/version.rb +3 -0
- data/lib/minder.rb +30 -0
- data/minder.gemspec +31 -0
- data/spec/minder/application_spec.rb +55 -0
- data/spec/minder/config_spec.rb +44 -0
- data/spec/minder/pomodoro_break_spec.rb +25 -0
- data/spec/minder/pomodoro_runner_spec.rb +3 -0
- data/spec/minder/pomodoro_spec.rb +25 -0
- data/spec/minder/timer_spec.rb +83 -0
- data/spec/spec_helper.rb +91 -0
- metadata +235 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minder/timer'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class Period
|
5
|
+
attr_accessor :minutes,
|
6
|
+
:timer
|
7
|
+
|
8
|
+
def initialize(minutes: nil)
|
9
|
+
self.minutes = minutes
|
10
|
+
self.timer = Minder::Timer.new(seconds: minutes.to_i * 60)
|
11
|
+
end
|
12
|
+
|
13
|
+
def start!
|
14
|
+
Minder.play_sound('start.wav')
|
15
|
+
timer.start!
|
16
|
+
end
|
17
|
+
|
18
|
+
def complete!
|
19
|
+
Minder.play_sound('done.wav')
|
20
|
+
@status = :completed
|
21
|
+
end
|
22
|
+
|
23
|
+
def completed?
|
24
|
+
@status == :completed
|
25
|
+
end
|
26
|
+
|
27
|
+
def elapsed?
|
28
|
+
timer.completed?
|
29
|
+
end
|
30
|
+
|
31
|
+
def message
|
32
|
+
timer.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'minder/frame'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class PomodoroFrame < Frame
|
5
|
+
def template
|
6
|
+
text = <<-TEXT
|
7
|
+
<%= period.title %>
|
8
|
+
TEXT
|
9
|
+
|
10
|
+
if period.message
|
11
|
+
text += <<-TEXT
|
12
|
+
|
13
|
+
<%= period.message %>
|
14
|
+
TEXT
|
15
|
+
end
|
16
|
+
|
17
|
+
if task_manager.started_task
|
18
|
+
text += <<-TEXT
|
19
|
+
|
20
|
+
Working on: #{task_manager.started_task}
|
21
|
+
TEXT
|
22
|
+
end
|
23
|
+
|
24
|
+
text
|
25
|
+
end
|
26
|
+
|
27
|
+
def period
|
28
|
+
pomodoro_runner.current_action
|
29
|
+
end
|
30
|
+
|
31
|
+
def handle_char_keypress(key)
|
32
|
+
event = case key
|
33
|
+
when ' ' then :continue
|
34
|
+
when 'e' then :editor
|
35
|
+
end
|
36
|
+
|
37
|
+
changed
|
38
|
+
notify_observers(event)
|
39
|
+
end
|
40
|
+
|
41
|
+
def handle_non_char_keypress(key)
|
42
|
+
event = case key
|
43
|
+
when 3 then :exit
|
44
|
+
end
|
45
|
+
|
46
|
+
changed
|
47
|
+
notify_observers(event)
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_cursor_position
|
51
|
+
window.setpos(1, lines[0].strip.length + 2)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
require 'minder/pomodoro_period'
|
4
|
+
require 'minder/break_period'
|
5
|
+
require 'minder/idle_period'
|
6
|
+
|
7
|
+
module Minder
|
8
|
+
class PomodoroRunner
|
9
|
+
include Observable
|
10
|
+
|
11
|
+
attr_accessor :work_duration,
|
12
|
+
:short_break_duration,
|
13
|
+
:long_break_duration
|
14
|
+
|
15
|
+
attr_reader :action_count,
|
16
|
+
:current_action
|
17
|
+
|
18
|
+
def initialize(**options)
|
19
|
+
self.work_duration = options.fetch(:work_duration)
|
20
|
+
self.short_break_duration = options.fetch(:short_break_duration)
|
21
|
+
self.long_break_duration = options.fetch(:long_break_duration)
|
22
|
+
@action_count = 0
|
23
|
+
@current_action = IdlePeriod.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def tick
|
27
|
+
return if !current_action.elapsed? || current_action.completed?
|
28
|
+
|
29
|
+
old_action = current_action
|
30
|
+
current_action.complete!
|
31
|
+
@current_action = IdlePeriod.new
|
32
|
+
|
33
|
+
changed
|
34
|
+
if old_action.is_a?(PomodoroPeriod)
|
35
|
+
notify_observers(:completed_work)
|
36
|
+
elsif old_action.is_a?(BreakPeriod)
|
37
|
+
notify_observers(:completed_break)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def continue
|
42
|
+
return unless current_action.elapsed?
|
43
|
+
|
44
|
+
advance_action
|
45
|
+
current_action.start!
|
46
|
+
end
|
47
|
+
|
48
|
+
def advance_action
|
49
|
+
@action_count += 1
|
50
|
+
changed
|
51
|
+
|
52
|
+
if action_count.odd?
|
53
|
+
notify_observers(:started_work)
|
54
|
+
@current_action = PomodoroPeriod.new(minutes: work_duration)
|
55
|
+
else
|
56
|
+
notify_observers(:started_break)
|
57
|
+
@current_action = BreakPeriod.new(minutes: break_duration)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def break_duration
|
62
|
+
if action_count % 8 == 0
|
63
|
+
long_break_duration
|
64
|
+
else
|
65
|
+
short_break_duration
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'minder/frame'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class QuickAddFrame < Frame
|
5
|
+
attr_accessor :input
|
6
|
+
|
7
|
+
def initialize(*)
|
8
|
+
super
|
9
|
+
self.input = ''
|
10
|
+
end
|
11
|
+
|
12
|
+
def template
|
13
|
+
<<-TEXT
|
14
|
+
Quick add task:
|
15
|
+
TEXT
|
16
|
+
end
|
17
|
+
|
18
|
+
def set_text
|
19
|
+
self.lines[0] += ' ' + input
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_cursor_position
|
24
|
+
window.setpos(1, template.strip.length + 2 + input.length)
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_char_keypress(key)
|
28
|
+
self.input += key
|
29
|
+
refresh
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle_non_char_keypress(key)
|
33
|
+
case key
|
34
|
+
when 127
|
35
|
+
self.input.chop!
|
36
|
+
refresh
|
37
|
+
when 10
|
38
|
+
changed
|
39
|
+
notify_observers(:add_task, { task: input })
|
40
|
+
self.input = ''
|
41
|
+
refresh
|
42
|
+
else
|
43
|
+
#Minder.pry_open(binding)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/minder/scene.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'minder/pomodoro_frame'
|
3
|
+
require 'minder/message_frame'
|
4
|
+
require 'minder/quick_add_frame'
|
5
|
+
|
6
|
+
module Minder
|
7
|
+
class Scene
|
8
|
+
attr_accessor :frames
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
self.frames = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
Curses.noecho
|
16
|
+
Curses.init_screen
|
17
|
+
Curses.timeout = 0
|
18
|
+
clear
|
19
|
+
Curses.refresh
|
20
|
+
end
|
21
|
+
|
22
|
+
def focused_frame
|
23
|
+
frames.find(&:focused?)
|
24
|
+
end
|
25
|
+
|
26
|
+
def switch_focus
|
27
|
+
current_index = frames.find_index(focused_frame)
|
28
|
+
focused_frame.unfocus
|
29
|
+
next_frame = frames[current_index + 1..-1].find { |frame| !frame.hidden? }
|
30
|
+
if next_frame
|
31
|
+
next_frame.focus
|
32
|
+
else
|
33
|
+
frames[0].focus
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def refresh
|
38
|
+
frames.map(&:refresh)
|
39
|
+
end
|
40
|
+
|
41
|
+
def resize_frames
|
42
|
+
frames.map(&:resize)
|
43
|
+
|
44
|
+
available_height = Curses.lines - frames.last.height
|
45
|
+
|
46
|
+
frames.first.move(0, 0)
|
47
|
+
next_height = frames.first.height
|
48
|
+
second_frame = frames[1]
|
49
|
+
|
50
|
+
unless second_frame.hidden?
|
51
|
+
proposed_height = available_height - frames.first.height
|
52
|
+
if proposed_height < second_frame.desired_height
|
53
|
+
second_frame.height = proposed_height
|
54
|
+
else
|
55
|
+
second_frame.height = second_frame.desired_height
|
56
|
+
end
|
57
|
+
second_frame.move(next_height, 0)
|
58
|
+
next_height += second_frame.height
|
59
|
+
end
|
60
|
+
|
61
|
+
if next_height <= available_height
|
62
|
+
frames.last.move(next_height, 0)
|
63
|
+
else
|
64
|
+
frames.last.move(available_height, 0)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def redraw
|
69
|
+
refresh
|
70
|
+
resize_frames
|
71
|
+
refresh
|
72
|
+
Curses.curs_set(1)
|
73
|
+
focused_frame.set_cursor_position
|
74
|
+
focused_frame.window_refresh
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear
|
78
|
+
Curses.clear
|
79
|
+
end
|
80
|
+
|
81
|
+
def close
|
82
|
+
Curses.close_screen
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/minder/task.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class Task
|
5
|
+
include Virtus.model
|
6
|
+
|
7
|
+
attribute :description, String
|
8
|
+
attribute :selected, Boolean, default: false
|
9
|
+
attribute :started, Boolean, default: false
|
10
|
+
|
11
|
+
def start
|
12
|
+
self.started = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def unstart
|
16
|
+
self.description.gsub!(/\* /, '')
|
17
|
+
self.started = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def started?
|
21
|
+
super || description =~ /\* /
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
description.gsub(/\A\* /, '')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'minder/task'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Minder
|
5
|
+
class TaskRecorder
|
6
|
+
attr_accessor :tasks,
|
7
|
+
:lines
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@selected_task_index = 0
|
11
|
+
reload
|
12
|
+
end
|
13
|
+
|
14
|
+
def tasks
|
15
|
+
@tasks ||= lines.map { |task| Task.new(description: task) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def tasks?
|
19
|
+
!tasks.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_task(task)
|
23
|
+
File.open(DOING_FILE, 'a') do |file|
|
24
|
+
file.write("#{task}\n")
|
25
|
+
end
|
26
|
+
reload
|
27
|
+
end
|
28
|
+
|
29
|
+
def select_next_task
|
30
|
+
if @selected_task_index + 1 <= tasks.length - 1
|
31
|
+
@selected_task_index += 1
|
32
|
+
else
|
33
|
+
@selected_task_index = 0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def select_previous_task
|
38
|
+
if @selected_task_index == 0
|
39
|
+
@selected_task_index = tasks.length - 1
|
40
|
+
else
|
41
|
+
@selected_task_index -= 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def selected_task_index
|
46
|
+
@selected_task_index
|
47
|
+
end
|
48
|
+
|
49
|
+
def selected_task
|
50
|
+
tasks[selected_task_index]
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete_task
|
54
|
+
lines.delete_at(selected_task_index)
|
55
|
+
@tasks = nil
|
56
|
+
write_file_with_backup
|
57
|
+
reload
|
58
|
+
|
59
|
+
select_previous_task
|
60
|
+
end
|
61
|
+
|
62
|
+
def reload
|
63
|
+
self.lines = File.read(DOING_FILE).strip.split("\n")
|
64
|
+
@tasks = nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def complete_task
|
68
|
+
task = selected_task
|
69
|
+
delete_task
|
70
|
+
add_to_done_file("Finished: #{task.description}")
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_to_done_file(text)
|
74
|
+
File.open(DONE_FILE, 'a') do |file|
|
75
|
+
file.write("[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] #{text}\n")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def write_file_with_backup
|
80
|
+
FileUtils.cp(DOING_FILE, DOING_FILE + '.old')
|
81
|
+
write_file(DOING_FILE)
|
82
|
+
end
|
83
|
+
|
84
|
+
def write_file(path)
|
85
|
+
File.open(path, 'w') do |file|
|
86
|
+
tasks.each do |task|
|
87
|
+
line = task.to_s
|
88
|
+
line = "* #{line}" if task.started?
|
89
|
+
file.write("#{line}\n")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def start_task
|
95
|
+
selected_task.start
|
96
|
+
write_file_with_backup
|
97
|
+
add_to_done_file("Started: #{selected_task.description}")
|
98
|
+
reload
|
99
|
+
end
|
100
|
+
|
101
|
+
def unstart_task
|
102
|
+
selected_task.unstart
|
103
|
+
write_file_with_backup
|
104
|
+
add_to_done_file("Un-started: #{selected_task.description}")
|
105
|
+
reload
|
106
|
+
end
|
107
|
+
|
108
|
+
def started_task
|
109
|
+
tasks.find(&:started?)
|
110
|
+
end
|
111
|
+
|
112
|
+
def select_last_task
|
113
|
+
@selected_task_index = tasks.length - 1
|
114
|
+
end
|
115
|
+
|
116
|
+
def select_first_task
|
117
|
+
@selected_task_index = 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/minder/timer.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minder'
|
2
|
+
|
3
|
+
module Minder
|
4
|
+
class Timer
|
5
|
+
attr_accessor :seconds,
|
6
|
+
:start_time
|
7
|
+
|
8
|
+
def initialize(seconds: DEFAULT_WORK_PERIOD)
|
9
|
+
self.seconds = seconds
|
10
|
+
end
|
11
|
+
|
12
|
+
def start!
|
13
|
+
self.start_time = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def completed?
|
17
|
+
elapsed_time.to_i >= seconds
|
18
|
+
end
|
19
|
+
|
20
|
+
def elapsed_time
|
21
|
+
return 0 unless start_time
|
22
|
+
|
23
|
+
(Time.now - start_time)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"#{Minder.formatted_time(elapsed_time)} " \
|
28
|
+
"(out of #{Minder.formatted_time(seconds)})"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/lib/minder.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Minder
|
2
|
+
DEFAULT_WORK_PERIOD = 25
|
3
|
+
DEFAULT_BREAK_PERIOD = 5
|
4
|
+
CONFIG_LOCATION = ENV['HOME'] + '/.minder.json'
|
5
|
+
ASSETS_LOCATION = File.expand_path(File.dirname(__FILE__) + '/../assets')
|
6
|
+
DOING_FILE = File.join(ENV["HOME"], '.minder', 'doing.txt')
|
7
|
+
DONE_FILE = File.join(ENV["HOME"], '.minder', 'done.txt')
|
8
|
+
def self.formatted_time(seconds)
|
9
|
+
minutes = (seconds / 60).to_i
|
10
|
+
seconds = (seconds % 60).round
|
11
|
+
"#{'%02d' % minutes}:#{'%02d' % seconds}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.play_sound(name)
|
15
|
+
command = is_linux? ? 'aplay' : 'afplay'
|
16
|
+
spawn("#{command} #{ASSETS_LOCATION}/#{name}",
|
17
|
+
out: '/dev/null',
|
18
|
+
err: '/dev/null')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.is_linux?
|
22
|
+
RUBY_PLATFORM =~ /linux/
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.pry_open(b)
|
26
|
+
Curses.close_screen
|
27
|
+
require 'pry'
|
28
|
+
b.pry
|
29
|
+
end
|
30
|
+
end
|
data/minder.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'minder/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "minder"
|
7
|
+
spec.version = Minder::VERSION
|
8
|
+
spec.authors = ["Joseph Method"]
|
9
|
+
spec.email = ["tristil@gmail.com"]
|
10
|
+
spec.description = %q{Productivity tool borrowing a little from everything.}
|
11
|
+
spec.summary = %q{Combines a Pomodoro Technique runner with GTD-style task backlogs and Day One-style prompts."}
|
12
|
+
spec.homepage = "http://github.com/tristil/minder"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_runtime_dependency 'curses', '~> 1.0', '>= 1.0.1'
|
20
|
+
spec.add_runtime_dependency 'virtus', '~> 1.0', '>= 1.0.5'
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", '~> 0'
|
23
|
+
spec.add_development_dependency "rspec", '~> 3.2', '>= 3.2'
|
24
|
+
spec.add_development_dependency "timecop", '~> 0.7', '>= 0.7.3'
|
25
|
+
spec.add_development_dependency "pry", '~> 0.10', '>= 0.10'
|
26
|
+
spec.add_development_dependency "pry-byebug", '~> 3.1', '>= 3.1.0'
|
27
|
+
spec.add_development_dependency 'pry-stack_explorer', '~> 0.4.9'
|
28
|
+
|
29
|
+
spec.licenses = ['MIT']
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'minder/application'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Minder::Application do
|
5
|
+
let(:config_file) do
|
6
|
+
Tempfile.new('minder config')
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
it 'sets the config' do
|
11
|
+
config = instance_spy('Minder::Config')
|
12
|
+
application = Minder::Application.new(config: config)
|
13
|
+
expect(application.config).to eq(config)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
specify '#config_location is delegated to the config' do
|
18
|
+
config = instance_spy('Minder::Config', location: 'location')
|
19
|
+
application = Minder::Application.new(config: config)
|
20
|
+
expect(application.config_location).to eq('location')
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#run' do
|
24
|
+
let(:config) do
|
25
|
+
instance_spy(
|
26
|
+
'Minder::Config',
|
27
|
+
work_duration: 'work_duration',
|
28
|
+
short_break_duration: 'short_break_duration',
|
29
|
+
long_break_duration: 'long_break_duration')
|
30
|
+
end
|
31
|
+
let(:application) { Minder::Application.new(config: config) }
|
32
|
+
|
33
|
+
it 'runs the application' do
|
34
|
+
pomodoro_runner = instance_double(Minder::PomodoroRunner)
|
35
|
+
allow(Minder::PomodoroRunner).to receive(:new)
|
36
|
+
.with(work_duration: 'work_duration',
|
37
|
+
short_break_duration: 'short_break_duration',
|
38
|
+
long_break_duration: 'long_break_duration')
|
39
|
+
.and_return(pomodoro_runner)
|
40
|
+
allow(application).to receive(:system)
|
41
|
+
allow(application).to receive(:puts)
|
42
|
+
allow(STDIN).to receive(:getc).and_return(' ')
|
43
|
+
interval = instance_double(Minder::Interval)
|
44
|
+
allow(pomodoro_runner).to receive(:next_action).and_return(interval)
|
45
|
+
|
46
|
+
application.run
|
47
|
+
|
48
|
+
expect(application).to have_received(:system).with('stty raw -echo')
|
49
|
+
expect(application).to have_received(:system).with('stty -raw echo')
|
50
|
+
expect(STDIN).to have_received(:getc).with('stty -raw echo')
|
51
|
+
expect(pomodoro_runner).to have_received(:next_action)
|
52
|
+
expect(interval).to receive(:)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'minder/config'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
describe Minder::Config do
|
5
|
+
specify 'it sets location' do
|
6
|
+
config = Minder::Config.new('location')
|
7
|
+
expect(config.location).to eq('location')
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#load' do
|
11
|
+
it 'loads values from a json file if it exists' do
|
12
|
+
data = {
|
13
|
+
work_duration: 15,
|
14
|
+
short_break_duration: 10
|
15
|
+
}
|
16
|
+
json_file = Tempfile.new('minder json file')
|
17
|
+
json_file.write(JSON.dump(data))
|
18
|
+
json_file.seek(0)
|
19
|
+
config = Minder::Config.new(json_file.path)
|
20
|
+
config.load
|
21
|
+
expect(config.data).to include(
|
22
|
+
work_duration: 15,
|
23
|
+
short_break_duration: 10,
|
24
|
+
long_break_duration: 15)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'loads default values if no location is passed' do
|
28
|
+
config = Minder::Config.new
|
29
|
+
config.load
|
30
|
+
expect(config.data).to include(
|
31
|
+
work_duration: 25,
|
32
|
+
short_break_duration: 5,
|
33
|
+
long_break_duration: 15)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'auto-generated methods' do
|
38
|
+
it 'creates a reader method for each default value key' do
|
39
|
+
config = Minder::Config.new
|
40
|
+
config.load
|
41
|
+
expect(config.work_duration).to eq(25)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'minder/pomodoro_break'
|
2
|
+
|
3
|
+
describe Minder::PomodoroBreak do
|
4
|
+
describe '#run' do
|
5
|
+
let(:timer) { instance_spy(Minder::Timer) }
|
6
|
+
|
7
|
+
it 'runs the pomodoro' do
|
8
|
+
pomodoro = described_class.new(minutes: 5)
|
9
|
+
|
10
|
+
allow(Minder::Timer).to receive(:new)
|
11
|
+
.with(seconds: 300)
|
12
|
+
.and_return(timer)
|
13
|
+
allow(timer).to receive(:completed?).and_return(false, true)
|
14
|
+
allow($stdout).to receive(:flush)
|
15
|
+
allow(pomodoro).to receive(:puts)
|
16
|
+
allow(pomodoro).to receive(:print)
|
17
|
+
|
18
|
+
pomodoro.run
|
19
|
+
|
20
|
+
expect(pomodoro).to have_received(:puts).with('Break period')
|
21
|
+
expect(timer).to have_received(:start!)
|
22
|
+
expect(timer).to have_received(:tick).at_least(:once)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|