daigaku 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/bin/daigaku +12 -0
- data/daigaku.gemspec +37 -0
- data/lib/daigaku.rb +27 -0
- data/lib/daigaku/chapter.rb +23 -0
- data/lib/daigaku/configuration.rb +86 -0
- data/lib/daigaku/course.rb +23 -0
- data/lib/daigaku/database.rb +64 -0
- data/lib/daigaku/exceptions.rb +19 -0
- data/lib/daigaku/generator.rb +53 -0
- data/lib/daigaku/loadable.rb +23 -0
- data/lib/daigaku/loading/chapters.rb +9 -0
- data/lib/daigaku/loading/courses.rb +9 -0
- data/lib/daigaku/loading/units.rb +9 -0
- data/lib/daigaku/reference_solution.rb +10 -0
- data/lib/daigaku/solution.rb +42 -0
- data/lib/daigaku/task.rb +10 -0
- data/lib/daigaku/terminal.rb +11 -0
- data/lib/daigaku/terminal/cli.rb +59 -0
- data/lib/daigaku/terminal/courses.rb +114 -0
- data/lib/daigaku/terminal/output.rb +72 -0
- data/lib/daigaku/terminal/setup.rb +115 -0
- data/lib/daigaku/terminal/solutions.rb +46 -0
- data/lib/daigaku/terminal/texts/about.txt +19 -0
- data/lib/daigaku/terminal/texts/courses_empty.txt +3 -0
- data/lib/daigaku/terminal/texts/hint_course_download.txt +13 -0
- data/lib/daigaku/terminal/texts/welcome.txt +12 -0
- data/lib/daigaku/terminal/welcome.rb +98 -0
- data/lib/daigaku/test.rb +46 -0
- data/lib/daigaku/test_result.rb +69 -0
- data/lib/daigaku/unit.rb +28 -0
- data/lib/daigaku/version.rb +3 -0
- data/lib/daigaku/views.rb +59 -0
- data/lib/daigaku/views/chapters_menu.rb +91 -0
- data/lib/daigaku/views/courses_menu.rb +87 -0
- data/lib/daigaku/views/main_menu.rb +37 -0
- data/lib/daigaku/views/splash.rb +57 -0
- data/lib/daigaku/views/task_view.rb +206 -0
- data/lib/daigaku/views/top_bar.rb +48 -0
- data/lib/daigaku/views/units_menu.rb +92 -0
- data/lib/daigaku/window.rb +160 -0
- data/spec/daigaku/chapter_spec.rb +76 -0
- data/spec/daigaku/configuration_spec.rb +161 -0
- data/spec/daigaku/course_spec.rb +75 -0
- data/spec/daigaku/database_spec.rb +79 -0
- data/spec/daigaku/generator_spec.rb +82 -0
- data/spec/daigaku/loading/chapters_spec.rb +16 -0
- data/spec/daigaku/loading/courses_spec.rb +16 -0
- data/spec/daigaku/loading/units_spec.rb +21 -0
- data/spec/daigaku/reference_solution_spec.rb +23 -0
- data/spec/daigaku/solution_spec.rb +79 -0
- data/spec/daigaku/task_spec.rb +23 -0
- data/spec/daigaku/terminal/cli_spec.rb +51 -0
- data/spec/daigaku/terminal/courses_spec.rb +60 -0
- data/spec/daigaku/terminal/output_spec.rb +123 -0
- data/spec/daigaku/terminal/setup_spec.rb +10 -0
- data/spec/daigaku/terminal/solutions_spec.rb +8 -0
- data/spec/daigaku/terminal/welcome_spec.rb +12 -0
- data/spec/daigaku/terminal_spec.rb +14 -0
- data/spec/daigaku/test_example_spec.rb +54 -0
- data/spec/daigaku/test_result_spec.rb +81 -0
- data/spec/daigaku/test_spec.rb +48 -0
- data/spec/daigaku/unit_spec.rb +85 -0
- data/spec/daigaku/views/chapters_menu_spec.rb +8 -0
- data/spec/daigaku/views/courses_menu_spec.rb +8 -0
- data/spec/daigaku/views/task_view_spec.rb +7 -0
- data/spec/daigaku/views/units_menu_spec.rb +8 -0
- data/spec/daigaku/views_spec.rb +23 -0
- data/spec/daigaku_spec.rb +57 -0
- data/spec/path_helpers_spec.rb +60 -0
- data/spec/resource_helpers_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/macros/content_helpers.rb +129 -0
- data/spec/support/macros/mock_helpers.rb +20 -0
- data/spec/support/macros/path_helpers.rb +133 -0
- data/spec/support/macros/resource_helpers.rb +119 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- metadata +361 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Daigaku
|
4
|
+
class Window < Curses::Window
|
5
|
+
|
6
|
+
COLOR_TEXT = Curses::COLOR_YELLOW unless defined? COLOR_TEXT
|
7
|
+
COLOR_TEXT_EMPHASIZE = Curses::COLOR_CYAN unless defined? COLOR_TEXT_EMPHASIZE
|
8
|
+
COLOR_HEADING = Curses::COLOR_WHITE unless defined? COLOR_HEADING
|
9
|
+
COLOR_RED = Curses::COLOR_BLUE unless defined? COLOR_RED
|
10
|
+
COLOR_GREEN = Curses::COLOR_MAGENTA unless defined? COLOR_GREEN
|
11
|
+
COLOR_YELLOW = Curses::COLOR_RED unless defined? COLOR_YELLOW
|
12
|
+
|
13
|
+
BACKGROUND = Curses::COLOR_WHITE unless defined? BACKGROUND
|
14
|
+
FONT = Curses::COLOR_BLACK unless defined? FONT
|
15
|
+
FONT_HEADING = Curses::COLOR_MAGENTA unless defined? FONT_HEADING
|
16
|
+
FONT_EMPHASIZE = Curses::COLOR_BLUE unless defined? FONT_EMPHASIZE
|
17
|
+
RED = Curses::COLOR_RED unless defined? RED
|
18
|
+
GREEN = Curses::COLOR_GREEN unless defined? GREEN
|
19
|
+
YELLOW = Curses::COLOR_YELLOW unless defined? YELLOW
|
20
|
+
|
21
|
+
def initialize(height = Curses.lines, width = Curses.cols, top = 0, left = 0)
|
22
|
+
super(height, width, top, left)
|
23
|
+
init_colors
|
24
|
+
end
|
25
|
+
|
26
|
+
def write(text, color = COLOR_TEXT, text_decoration = Curses::A_NORMAL )
|
27
|
+
self.attron(Curses.color_pair(color) | text_decoration) { self << text.to_s }
|
28
|
+
end
|
29
|
+
|
30
|
+
def emphasize(text, text_decoration = Curses::A_NORMAL)
|
31
|
+
write(text, Window::COLOR_TEXT_EMPHASIZE, text_decoration)
|
32
|
+
end
|
33
|
+
|
34
|
+
def heading(text, text_decoration = Curses::A_UNDERLINE)
|
35
|
+
write(text, Window::COLOR_HEADING, text_decoration)
|
36
|
+
end
|
37
|
+
|
38
|
+
def red(text, text_decoration = Curses::A_NORMAL, options = {})
|
39
|
+
colored(text, Window::COLOR_RED, text_decoration, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def yellow(text, text_decoration = Curses::A_NORMAL, options = {})
|
43
|
+
colored(text, Window::COLOR_YELLOW, text_decoration, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def green(text, text_decoration = Curses::A_NORMAL, options = {})
|
47
|
+
colored(text, Window::COLOR_GREEN, text_decoration, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def colored(text, color, text_decoration = Curses::A_NORMAL, options = {})
|
51
|
+
if options[:full_line]
|
52
|
+
clear_line(
|
53
|
+
color: color,
|
54
|
+
text_decoration: Curses::A_STANDOUT,
|
55
|
+
start_pos: 1,
|
56
|
+
end_pos: maxx - 2
|
57
|
+
)
|
58
|
+
|
59
|
+
prefix = ' '
|
60
|
+
end
|
61
|
+
|
62
|
+
write("#{prefix}#{text}", color, text_decoration)
|
63
|
+
end
|
64
|
+
|
65
|
+
# clear_line(options = {})
|
66
|
+
# options: [:color, :text_decoration, :start_pos, :end_pos]
|
67
|
+
def clear_line(options = {})
|
68
|
+
color = options[:color] || COLOR_TEXT
|
69
|
+
text_decoration = options[:text_decoration] || Curses::A_NORMAL
|
70
|
+
start = options[:start_pos] || 0
|
71
|
+
stop = options[:end_pos] || maxx
|
72
|
+
|
73
|
+
x = curx
|
74
|
+
setpos(cury, start)
|
75
|
+
write(' ' * (stop - 1), color, text_decoration)
|
76
|
+
setpos(cury, x)
|
77
|
+
refresh
|
78
|
+
end
|
79
|
+
|
80
|
+
def print_indicator(object, text = ' ', text_decoration = Curses::A_STANDOUT)
|
81
|
+
if object.respond_to?(:mastered?) && object.respond_to?(:started?)
|
82
|
+
if object.mastered?
|
83
|
+
green(text, text_decoration)
|
84
|
+
write ' '
|
85
|
+
elsif object.started?
|
86
|
+
yellow(text, text_decoration)
|
87
|
+
write ' '
|
88
|
+
else
|
89
|
+
red(text, text_decoration)
|
90
|
+
write ' '
|
91
|
+
end
|
92
|
+
elsif object.respond_to?(:mastered?)
|
93
|
+
if object.mastered?
|
94
|
+
green(text, text_decoration)
|
95
|
+
write ' '
|
96
|
+
else
|
97
|
+
red(text, text_decoration)
|
98
|
+
write ' '
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def print_markdown(text)
|
104
|
+
clear_line
|
105
|
+
|
106
|
+
h1 = /^\#{1}[^#]+/ # '# heading'
|
107
|
+
h2 = /^\#{2}[^#]+/ # '## sub heading'
|
108
|
+
bold = /(\*[^*]*\*)/ # '*text*'
|
109
|
+
line = /^-{3,}/ # '---' vertical line
|
110
|
+
code = /(\`*\`)/ # '`code line`'
|
111
|
+
|
112
|
+
case text
|
113
|
+
when h1
|
114
|
+
heading(text.gsub(/^#\s?/, ''))
|
115
|
+
when h2
|
116
|
+
text_decoration = Curses::A_UNDERLINE | Curses::A_NORMAL
|
117
|
+
emphasize(text.gsub(/^##\s?/, ''), text_decoration)
|
118
|
+
when bold || code
|
119
|
+
emphasized = false
|
120
|
+
highlighted = false
|
121
|
+
|
122
|
+
text.chars.each do |char|
|
123
|
+
if char == '*'
|
124
|
+
emphasized = !emphasized
|
125
|
+
next
|
126
|
+
end
|
127
|
+
|
128
|
+
if char == '`'
|
129
|
+
highlighted = !highlighted
|
130
|
+
next
|
131
|
+
end
|
132
|
+
|
133
|
+
if emphasized
|
134
|
+
emphasize(char)
|
135
|
+
elsif highlighted
|
136
|
+
red(char)
|
137
|
+
else
|
138
|
+
write(char)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
when line
|
142
|
+
write('-' * (Curses.cols - 2))
|
143
|
+
else
|
144
|
+
write(text)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
protected
|
149
|
+
|
150
|
+
def init_colors
|
151
|
+
Curses.start_color
|
152
|
+
Curses.init_pair(COLOR_TEXT, FONT, BACKGROUND)
|
153
|
+
Curses.init_pair(COLOR_TEXT_EMPHASIZE, FONT_EMPHASIZE, BACKGROUND)
|
154
|
+
Curses.init_pair(COLOR_HEADING, FONT_HEADING, BACKGROUND)
|
155
|
+
Curses.init_pair(COLOR_RED, RED, BACKGROUND)
|
156
|
+
Curses.init_pair(COLOR_GREEN, GREEN, BACKGROUND)
|
157
|
+
Curses.init_pair(COLOR_YELLOW, YELLOW, BACKGROUND)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Daigaku::Chapter do
|
4
|
+
|
5
|
+
it { is_expected.to respond_to :title }
|
6
|
+
it { is_expected.to respond_to :units }
|
7
|
+
it { is_expected.to respond_to :path }
|
8
|
+
|
9
|
+
it { is_expected.to respond_to :mastered? }
|
10
|
+
it { is_expected.to respond_to :started? }
|
11
|
+
|
12
|
+
let(:chapter_path) { chapter_dirs(course_dir_names.first).first }
|
13
|
+
|
14
|
+
before(:all) do
|
15
|
+
prepare_solutions
|
16
|
+
Daigaku.config.solutions_path = solutions_basepath
|
17
|
+
end
|
18
|
+
|
19
|
+
subject { Daigaku::Chapter.new(chapter_path) }
|
20
|
+
|
21
|
+
it "has the prescribed title" do
|
22
|
+
expect(subject.title).to eq chapter_titles.first
|
23
|
+
end
|
24
|
+
|
25
|
+
it "has the prescribed path" do
|
26
|
+
expect(subject.path).to eq chapter_path
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is not started by default" do
|
30
|
+
expect(subject.started?).to be_falsey
|
31
|
+
end
|
32
|
+
|
33
|
+
it "is not mastered by default" do
|
34
|
+
expect(subject.mastered?).to be_falsey
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#units" do
|
38
|
+
it "loads the prescribed number of units" do
|
39
|
+
course_name = course_dir_names.first
|
40
|
+
chapter_name = File.basename(chapter_path)
|
41
|
+
units_count = available_units(course_name, chapter_name).count
|
42
|
+
expect(subject.units.count).to eq units_count
|
43
|
+
end
|
44
|
+
|
45
|
+
it "lazy-loads the units" do
|
46
|
+
expect(subject.instance_variable_get(:@units)).to be_nil
|
47
|
+
subject.units
|
48
|
+
expect(subject.instance_variable_get(:@units)).not_to be_nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#started?" do
|
53
|
+
it "returns true if at least one unit has been verified" do
|
54
|
+
allow(subject.units.first).to receive(:mastered?) { true }
|
55
|
+
expect(subject.started?).to be true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns false if no unit has been verified" do
|
59
|
+
allow_any_instance_of(Daigaku::Unit).to receive(:mastered?) { false }
|
60
|
+
expect(subject.started?).to be false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#mastered?" do
|
65
|
+
it "returns true if all units have been verified" do
|
66
|
+
subject.units.each { |unit| unit.solution.verify! }
|
67
|
+
expect(subject.mastered?).to be true
|
68
|
+
end
|
69
|
+
|
70
|
+
it "returns false unless all units have been verified" do
|
71
|
+
allow_any_instance_of(Daigaku::Unit).to receive(:mastered?) { false }
|
72
|
+
allow(subject.units.first).to receive(:mastered?) { true }
|
73
|
+
expect(subject.mastered?).to be false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Daigaku::Configuration do
|
4
|
+
|
5
|
+
subject { Daigaku::Configuration.send(:new) }
|
6
|
+
|
7
|
+
it { is_expected.to respond_to :solutions_path }
|
8
|
+
it { is_expected.to respond_to :solutions_path= }
|
9
|
+
it { is_expected.to respond_to :courses_path }
|
10
|
+
it { is_expected.to respond_to :courses_path= }
|
11
|
+
it { is_expected.to respond_to :storage_file }
|
12
|
+
it { is_expected.to respond_to :save }
|
13
|
+
it { is_expected.to respond_to :import! }
|
14
|
+
it { is_expected.to respond_to :summary }
|
15
|
+
|
16
|
+
before do
|
17
|
+
subject.instance_variable_set(:@storage_file, local_storage_file)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#courses_path" do
|
21
|
+
it "returns the appropriate initial local courses path" do
|
22
|
+
courses_path = File.expand_path('~/.daigaku/courses', __FILE__)
|
23
|
+
expect(subject.courses_path).to eq courses_path
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns the appropriate set courses path" do
|
27
|
+
subject.courses_path = local_courses_path
|
28
|
+
expect(subject.courses_path).to eq local_courses_path
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#solutions_path=" do
|
33
|
+
it "raises an error if the given path is no existent dir" do
|
34
|
+
expect { subject.solutions_path = "no/existent/dir" }
|
35
|
+
.to raise_error(Daigaku::ConfigurationError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#solutions_path" do
|
40
|
+
it "raises an error if not set" do
|
41
|
+
expect { subject.solutions_path }
|
42
|
+
.to raise_error(Daigaku::ConfigurationError)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "returns the solutions path if set" do
|
46
|
+
subject.solutions_path = test_basepath
|
47
|
+
expect { subject.solutions_path }.not_to raise_error
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#storage_path" do
|
52
|
+
it "returns the appropriate path to the storge file" do
|
53
|
+
expect(subject.storage_file).to eq local_storage_file
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#save" do
|
58
|
+
it "saves the configured courses path to the daigaku database" do
|
59
|
+
subject.courses_path = local_courses_path
|
60
|
+
subject.save
|
61
|
+
|
62
|
+
expect(Daigaku::Database.courses_path).to eq local_courses_path
|
63
|
+
end
|
64
|
+
|
65
|
+
it "saves the configured solution_path to the daigaku database" do
|
66
|
+
path = File.join(test_basepath, 'test_solutions')
|
67
|
+
FileUtils.makedirs(path)
|
68
|
+
subject.solutions_path = path
|
69
|
+
subject.save
|
70
|
+
|
71
|
+
expect(Daigaku::Database.solutions_path).to eq path
|
72
|
+
end
|
73
|
+
|
74
|
+
it "does not save the storage file path" do
|
75
|
+
subject.save
|
76
|
+
expect(Daigaku::Database.storage_file).to be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#import!" do
|
81
|
+
context "with non-existent daigaku database entries:" do
|
82
|
+
before do
|
83
|
+
FileUtils.rm(local_storage_file) if File.exist?(local_storage_file)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "uses the default configuration" do
|
87
|
+
Daigaku::Database.courses_path = nil
|
88
|
+
Daigaku::Database.solutions_path = nil
|
89
|
+
subject.instance_variable_set(:@courses_path, local_courses_path)
|
90
|
+
|
91
|
+
loaded_config = subject.import!
|
92
|
+
|
93
|
+
expect(loaded_config.courses_path).to eq local_courses_path
|
94
|
+
expect { loaded_config.solutions_path }
|
95
|
+
.to raise_error Daigaku::ConfigurationError
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with existing daigaku database file:" do
|
100
|
+
it "returns a Daigaku::Configuration" do
|
101
|
+
expect(subject.import!).to be_a Daigaku::Configuration
|
102
|
+
end
|
103
|
+
|
104
|
+
it "loads the daigaku database entries into the configuration" do
|
105
|
+
wanted_courses_path = "/courses/path"
|
106
|
+
wanted_solutions_path = solutions_basepath
|
107
|
+
temp_solutions_path = File.join(solutions_basepath, 'temp')
|
108
|
+
FileUtils.makedirs(wanted_solutions_path)
|
109
|
+
FileUtils.makedirs(temp_solutions_path)
|
110
|
+
|
111
|
+
# save wanted settings
|
112
|
+
subject.courses_path = wanted_courses_path
|
113
|
+
subject.solutions_path = wanted_solutions_path
|
114
|
+
subject.save
|
115
|
+
|
116
|
+
# overwrite in memory settings
|
117
|
+
subject.courses_path = '/some/other/path/'
|
118
|
+
subject.solutions_path = temp_solutions_path
|
119
|
+
|
120
|
+
# fetch stored settings
|
121
|
+
subject.import!
|
122
|
+
expect(File.exist?(local_storage_file)).to be_truthy
|
123
|
+
expect(subject.courses_path).to eq wanted_courses_path
|
124
|
+
expect(subject.solutions_path).to eq wanted_solutions_path
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#summary' do
|
130
|
+
before do
|
131
|
+
subject.courses_path = "wanted/courses/path"
|
132
|
+
subject.solutions_path = solutions_basepath
|
133
|
+
@summary = subject.summary
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns a string" do
|
137
|
+
expect(@summary).to be_a String
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns a string with all properties but @configuration_file" do
|
141
|
+
expect(@summary.lines.count).to eq subject.instance_variables.count - 1
|
142
|
+
end
|
143
|
+
|
144
|
+
it "returns a string including the courses path" do
|
145
|
+
expect(@summary =~ /courses path/).to be_truthy
|
146
|
+
end
|
147
|
+
|
148
|
+
it "returns a string including the courses path" do
|
149
|
+
expect(@summary =~ /solutions path/).to be_truthy
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe '#initial_course' do
|
154
|
+
it { is_expected.to respond_to :initial_course }
|
155
|
+
|
156
|
+
it "returns the initial course github repo partial path" do
|
157
|
+
expect(subject.initial_course).to eq 'daigaku-ruby/Get_started_with_Ruby'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Daigaku::Course do
|
4
|
+
|
5
|
+
it { is_expected.to respond_to :title }
|
6
|
+
it { is_expected.to respond_to :chapters }
|
7
|
+
it { is_expected.to respond_to :path }
|
8
|
+
it { is_expected.to respond_to :author }
|
9
|
+
it { is_expected.to respond_to :link }
|
10
|
+
|
11
|
+
it { is_expected.to respond_to :mastered? }
|
12
|
+
it { is_expected.to respond_to :started? }
|
13
|
+
|
14
|
+
let(:course_path) { course_dirs.first }
|
15
|
+
|
16
|
+
before(:all) do
|
17
|
+
prepare_solutions
|
18
|
+
Daigaku.config.solutions_path = solutions_basepath
|
19
|
+
end
|
20
|
+
|
21
|
+
subject { Daigaku::Course.new(course_path) }
|
22
|
+
|
23
|
+
it "has the prescribed title" do
|
24
|
+
expect(subject.title).to eq course_titles.first
|
25
|
+
end
|
26
|
+
|
27
|
+
it "has the prescribed path" do
|
28
|
+
expect(subject.path).to eq course_path
|
29
|
+
end
|
30
|
+
|
31
|
+
it "is not started by default" do
|
32
|
+
expect(subject.started?).to be_falsey
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is not mastered by default" do
|
36
|
+
expect(subject.mastered?).to be_falsey
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#chapters" do
|
40
|
+
it "loads the prescribed number of chapters" do
|
41
|
+
expect(subject.chapters.count).to eq available_chapters(course_path).count
|
42
|
+
end
|
43
|
+
|
44
|
+
it "lazy-loads the chapters" do
|
45
|
+
expect(subject.instance_variable_get(:@chapters)).to be_nil
|
46
|
+
subject.chapters
|
47
|
+
expect(subject.instance_variable_get(:@chapters)).not_to be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#started?" do
|
52
|
+
it "returns true if at least one chapter has been started" do
|
53
|
+
allow(subject.chapters.first).to receive(:started?) { true }
|
54
|
+
expect(subject.started?).to be true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns false if no chapter has been started" do
|
58
|
+
allow_any_instance_of(Daigaku::Chapter).to receive(:started?) { false }
|
59
|
+
expect(subject.started?).to be false
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#mastered?" do
|
64
|
+
it "returns true if all chapters have been mastered" do
|
65
|
+
allow_any_instance_of(Daigaku::Chapter).to receive(:mastered?) { true }
|
66
|
+
expect(subject.mastered?).to be true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns false unless all chapters have been mastered" do
|
70
|
+
allow_any_instance_of(Daigaku::Chapter).to receive(:mastered?) { false }
|
71
|
+
allow(subject.chapters.first).to receive(:mastered?) { true }
|
72
|
+
expect(subject.mastered?).to be false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|