belajar 0.1.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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +5 -0
  6. data/README.md +5 -0
  7. data/Rakefile +5 -0
  8. data/belajar.gemspec +38 -0
  9. data/bin/belajar +12 -0
  10. data/lib/belajar.rb +24 -0
  11. data/lib/belajar/chapter.rb +23 -0
  12. data/lib/belajar/configuration.rb +89 -0
  13. data/lib/belajar/congratulator.rb +13 -0
  14. data/lib/belajar/course.rb +80 -0
  15. data/lib/belajar/exceptions.rb +19 -0
  16. data/lib/belajar/generator.rb +60 -0
  17. data/lib/belajar/github_client.rb +30 -0
  18. data/lib/belajar/loadable.rb +23 -0
  19. data/lib/belajar/loading/chapters.rb +9 -0
  20. data/lib/belajar/loading/courses.rb +9 -0
  21. data/lib/belajar/loading/units.rb +9 -0
  22. data/lib/belajar/reference_solution.rb +18 -0
  23. data/lib/belajar/solution.rb +53 -0
  24. data/lib/belajar/storeable.rb +32 -0
  25. data/lib/belajar/task.rb +10 -0
  26. data/lib/belajar/terminal.rb +12 -0
  27. data/lib/belajar/terminal/cli.rb +59 -0
  28. data/lib/belajar/terminal/courses.rb +179 -0
  29. data/lib/belajar/terminal/output.rb +78 -0
  30. data/lib/belajar/terminal/setup.rb +115 -0
  31. data/lib/belajar/terminal/solutions.rb +46 -0
  32. data/lib/belajar/terminal/texts/about.txt +19 -0
  33. data/lib/belajar/terminal/texts/congratulations.txt +12 -0
  34. data/lib/belajar/terminal/texts/courses_empty.txt +3 -0
  35. data/lib/belajar/terminal/texts/hint_course_download.txt +13 -0
  36. data/lib/belajar/terminal/texts/welcome.txt +12 -0
  37. data/lib/belajar/terminal/welcome.rb +98 -0
  38. data/lib/belajar/test.rb +46 -0
  39. data/lib/belajar/test_result.rb +86 -0
  40. data/lib/belajar/unit.rb +28 -0
  41. data/lib/belajar/version.rb +3 -0
  42. data/lib/belajar/views.rb +51 -0
  43. data/lib/belajar/views/chapters_menu.rb +60 -0
  44. data/lib/belajar/views/courses_menu.rb +52 -0
  45. data/lib/belajar/views/main_menu.rb +37 -0
  46. data/lib/belajar/views/menu.rb +89 -0
  47. data/lib/belajar/views/splash.rb +57 -0
  48. data/lib/belajar/views/task_view.rb +236 -0
  49. data/lib/belajar/views/top_bar.rb +40 -0
  50. data/lib/belajar/views/units_menu.rb +61 -0
  51. data/lib/belajar/window.rb +215 -0
  52. data/spec/belajar/chapter_spec.rb +76 -0
  53. data/spec/belajar/configuration_spec.rb +161 -0
  54. data/spec/belajar/congratulator_spec.rb +24 -0
  55. data/spec/belajar/course_spec.rb +201 -0
  56. data/spec/belajar/generator_spec.rb +82 -0
  57. data/spec/belajar/github_client_spec.rb +53 -0
  58. data/spec/belajar/loading/chapters_spec.rb +16 -0
  59. data/spec/belajar/loading/courses_spec.rb +16 -0
  60. data/spec/belajar/loading/units_spec.rb +21 -0
  61. data/spec/belajar/reference_solution_spec.rb +41 -0
  62. data/spec/belajar/solution_spec.rb +86 -0
  63. data/spec/belajar/storeable_spec.rb +35 -0
  64. data/spec/belajar/task_spec.rb +23 -0
  65. data/spec/belajar/terminal/cli_spec.rb +51 -0
  66. data/spec/belajar/terminal/courses_spec.rb +293 -0
  67. data/spec/belajar/terminal/output_spec.rb +151 -0
  68. data/spec/belajar/terminal/setup_spec.rb +10 -0
  69. data/spec/belajar/terminal/solutions_spec.rb +8 -0
  70. data/spec/belajar/terminal/welcome_spec.rb +12 -0
  71. data/spec/belajar/terminal_spec.rb +24 -0
  72. data/spec/belajar/test_example_spec.rb +54 -0
  73. data/spec/belajar/test_result_spec.rb +91 -0
  74. data/spec/belajar/test_spec.rb +48 -0
  75. data/spec/belajar/unit_spec.rb +85 -0
  76. data/spec/belajar/views/chapters_menu_spec.rb +6 -0
  77. data/spec/belajar/views/courses_menu_spec.rb +6 -0
  78. data/spec/belajar/views/menu_spec.rb +19 -0
  79. data/spec/belajar/views/task_view_spec.rb +7 -0
  80. data/spec/belajar/views/units_menu_spec.rb +6 -0
  81. data/spec/belajar/views_spec.rb +21 -0
  82. data/spec/belajar_spec.rb +51 -0
  83. data/spec/path_helpers_spec.rb +60 -0
  84. data/spec/resource_helpers_spec.rb +33 -0
  85. data/spec/spec_helper.rb +28 -0
  86. data/spec/support/macros/content_helpers.rb +129 -0
  87. data/spec/support/macros/mock_helpers.rb +25 -0
  88. data/spec/support/macros/path_helpers.rb +139 -0
  89. data/spec/support/macros/resource_helpers.rb +157 -0
  90. data/spec/support/macros/test_helpers.rb +6 -0
  91. metadata +385 -0
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::Congratulator do
4
+
5
+ it "responds to #message" do
6
+ expect(Belajar::Congratulator).to respond_to :message
7
+ end
8
+
9
+ describe "#message" do
10
+ it "returns a string" do
11
+ expect(Belajar::Congratulator.message).to be_a String
12
+ end
13
+
14
+ it "returns a random congratulation method" do
15
+ messages = 1.upto(10).map { |i| Belajar::Congratulator.message }
16
+ expect(messages.uniq.count).to be > 1
17
+ end
18
+
19
+ it "receives the congratulation texts from a Terminal text" do
20
+ expect(Belajar::Terminal).to receive(:text).with(:congratulations) { '' }
21
+ Belajar::Congratulator.message
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::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 { suppress_print_out }
17
+
18
+ before(:all) do
19
+ prepare_solutions
20
+ Belajar.config.solutions_path = solutions_basepath
21
+ end
22
+
23
+ subject { Belajar::Course.new(course_path) }
24
+
25
+ it "responds to ::unzip" do
26
+ expect(Belajar::Course).to respond_to :unzip
27
+ end
28
+
29
+ it "has the prescribed title" do
30
+ expect(subject.title).to eq course_titles.first
31
+ end
32
+
33
+ it "has the prescribed path" do
34
+ expect(subject.path).to eq course_path
35
+ end
36
+
37
+ it "is not started by default" do
38
+ expect(subject.started?).to be_falsey
39
+ end
40
+
41
+ it "is not mastered by default" do
42
+ expect(subject.mastered?).to be_falsey
43
+ end
44
+
45
+ describe "#chapters" do
46
+ it "loads the prescribed number of chapters" do
47
+ expect(subject.chapters.count).to eq available_chapters(course_path).count
48
+ end
49
+
50
+ it "lazy-loads the chapters" do
51
+ expect(subject.instance_variable_get(:@chapters)).to be_nil
52
+ subject.chapters
53
+ expect(subject.instance_variable_get(:@chapters)).not_to be_nil
54
+ end
55
+ end
56
+
57
+ describe "#started?" do
58
+ it "returns true if at least one chapter has been started" do
59
+ allow(subject.chapters.first).to receive(:started?) { true }
60
+ expect(subject.started?).to be true
61
+ end
62
+
63
+ it "returns false if no chapter has been started" do
64
+ allow_any_instance_of(Belajar::Chapter).to receive(:started?) { false }
65
+ expect(subject.started?).to be false
66
+ end
67
+ end
68
+
69
+ describe "#mastered?" do
70
+ it "returns true if all chapters have been mastered" do
71
+ allow_any_instance_of(Belajar::Chapter).to receive(:mastered?) { true }
72
+ expect(subject.mastered?).to be true
73
+ end
74
+
75
+ it "returns false unless all chapters have been mastered" do
76
+ allow_any_instance_of(Belajar::Chapter).to receive(:mastered?) { false }
77
+ allow(subject.chapters.first).to receive(:mastered?) { true }
78
+ expect(subject.mastered?).to be false
79
+ end
80
+ end
81
+
82
+ describe "#key" do
83
+ it "returns the courses store key for the given key name" do
84
+ allow(subject).to receive(:title) { '1-Course title' }
85
+ key = "courses/course_title/some_key"
86
+ expect(subject.key('1-some Key')).to eq key
87
+ end
88
+ end
89
+
90
+ describe "#author" do
91
+ it "returns the author of Github courses form the store" do
92
+ author = 'author'
93
+ QuickStore.store.set(subject.key(:author), author)
94
+
95
+ course = Belajar::Course.new(course_path)
96
+ expect(course.author).to eq author
97
+ end
98
+ end
99
+
100
+ describe "::unzip" do
101
+ before do
102
+ Belajar.config.courses_path = local_courses_path
103
+ @zip_file_name = "unzip/repo.zip"
104
+ @zip_file_path = File.join(courses_basepath, @zip_file_name)
105
+ @file_content = prepare_download(@zip_file_name)
106
+ end
107
+
108
+ after do
109
+ cleanup_download(@zip_file_name)
110
+ dir = File.dirname(@zip_file_path)
111
+ FileUtils.rm_r(dir) if Dir.exist?(dir)
112
+ end
113
+
114
+ def expect_course_dirs_exists_to_be(boolean)
115
+ unit_dirs(course_dir_names.first).each do |chapter_dirs|
116
+ chapter_dirs.each do |dir|
117
+ path = [dir.split('/')[0..-4], 'unzip', dir.split('/')[-3..-1]].join('/')
118
+ expect(Dir.exist?(path)).to be boolean
119
+ end
120
+ end
121
+ end
122
+
123
+ it "unzips a course zip file" do
124
+ expect_course_dirs_exists_to_be false
125
+ Belajar::Course.unzip(@zip_file_path)
126
+ expect_course_dirs_exists_to_be true
127
+ end
128
+
129
+ it "returns the unzipped course" do
130
+ dir = course_dirs.first
131
+ path = File.join(File.dirname(dir), 'unzip', File.basename(dir))
132
+ course = Belajar::Course.new(path)
133
+
134
+ expect(Belajar::Course.unzip(@zip_file_path).to_json).to eql course.to_json
135
+ end
136
+
137
+ it "removes the zip file" do
138
+ expect(File.exist?(@zip_file_path)).to be true
139
+ Belajar::Course.unzip(@zip_file_path)
140
+ expect(File.exist?(@zip_file_path)).to be false
141
+ end
142
+
143
+ context "with the same course already available:" do
144
+ before do
145
+ dir = course_dirs.first
146
+ @path = File.join(File.dirname(dir), 'unzip', File.basename(dir))
147
+ @old_chapter_dir = File.join(@path, 'Old_chapter')
148
+
149
+ FileUtils.mkdir_p(@old_chapter_dir)
150
+ end
151
+
152
+ it "overwrites all chapters" do
153
+ expect(Dir.exist?(@old_chapter_dir)).to be true
154
+ Belajar::Course.unzip(@zip_file_path)
155
+ expect(Dir.exist?(@old_chapter_dir)).to be false
156
+ end
157
+
158
+ context "if an errior occurs" do
159
+ before do
160
+ allow_any_instance_of(Zip::File)
161
+ .to receive(:extract) { raise Exception.new }
162
+ end
163
+
164
+ it "restores an old state if an error occurs" do
165
+ Belajar::Course.unzip(@zip_file_path)
166
+ expect(Dir.exist?(@old_chapter_dir)).to be true
167
+ expect(Dir.exist?("#{@path}_old")).to be false
168
+ end
169
+
170
+ it "keeps the zip file if an error occurs" do
171
+ expect(File.exist?(@zip_file_path)).to be true
172
+ Belajar::Course.unzip(@zip_file_path)
173
+ expect(File.exist?(@zip_file_path)).to be true
174
+ end
175
+ end
176
+ end
177
+
178
+ context "with the github_repo option:" do
179
+ before { @github_course_dir = prepare_github_course }
180
+ after { FileUtils.rm_r(@github_course_dir) }
181
+
182
+ it "removes the '-master' from the root directory" do
183
+ zip_file_name = "unzip/repo-master.zip"
184
+ zip_file_path = File.join(courses_basepath, zip_file_name)
185
+ prepare_github_download(zip_file_name)
186
+
187
+ expect(File.exist?(zip_file_path)).to be true
188
+ Belajar::Course.unzip(zip_file_path, github_repo: true)
189
+
190
+ unit_dirs("#{course_dir_names.first}-master").each do |chapter_dirs|
191
+ chapter_dirs.each do |dir|
192
+ path = [dir.split('/')[0..-4], 'unzip', dir.split('/')[-3..-1]].join('/')
193
+ expect(Dir.exist?(path)).to be false
194
+ end
195
+ end
196
+
197
+ expect_course_dirs_exists_to_be true
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::Generator do
4
+
5
+ it { is_expected.to respond_to :scaffold }
6
+ it { is_expected.to respond_to :prepare }
7
+
8
+ subject { Belajar::Generator.new }
9
+
10
+ before do
11
+ Belajar.config.instance_variable_set(:@storage_file, local_storage_file)
12
+ end
13
+
14
+ describe "#scaffold" do
15
+ it "creates blank solution files for all available units" do
16
+ subject.scaffold(courses_basepath, solutions_basepath)
17
+
18
+ all_solution_file_paths.each do |file_path|
19
+ expect(File.exist?(file_path)).to be_truthy
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "#prepare" do
25
+ context "with an existing solutions_path" do
26
+ before do
27
+ Belajar.configure do |config|
28
+ config.solutions_path = solutions_basepath
29
+ config.courses_path = local_courses_path
30
+ end
31
+
32
+ subject.prepare
33
+ end
34
+
35
+ it "generates a '<basepath>/.belajar/belajar.db.yml' file" do
36
+ expect(File.exist?(local_storage_file)).to be_truthy
37
+ end
38
+
39
+ it "generates a '<basepath>/.belajar/courses' folder" do
40
+ expect(Dir.exist?(local_courses_path)).to be_truthy
41
+ end
42
+
43
+ it "saves the current config info" do
44
+ expect(File.exist?(local_storage_file)).to be_truthy
45
+ expect(QuickStore.store.courses_path).to eq local_courses_path
46
+ expect(QuickStore.store.solutions_path).to eq solutions_basepath
47
+ end
48
+ end
49
+
50
+ context "with a missing solutions_path" do
51
+ before do
52
+ FileUtils.rm_r(solutions_basepath) if Dir.exist?(solutions_basepath)
53
+ base_path = File.dirname(Belajar.config.courses_path)
54
+ @solutions_path = File.join(base_path, 'solutions')
55
+
56
+ Belajar.config.instance_variable_set(:@solutions_path, nil)
57
+ Belajar.configure { |config| config.courses_path = local_courses_path }
58
+
59
+ subject.prepare
60
+ end
61
+
62
+ it "generates a 'solutions' path on the base directory as the courses" do
63
+ expect(Dir.exist?(@solutions_path)).to be_truthy
64
+ end
65
+
66
+ it "generates a '<basepath>/.belajar/belajar.db.yml' file" do
67
+ expect(File.exist?(local_storage_file)).to be_truthy
68
+ end
69
+
70
+ it "generates a '<basepath>/.belajar/courses' folder" do
71
+ expect(Dir.exist?(local_courses_path)).to be_truthy
72
+ end
73
+
74
+ it "saves the current config info" do
75
+ expect(File.exist?(local_storage_file)).to be_truthy
76
+ expect(QuickStore.store.courses_path).to eq local_courses_path
77
+ expect(QuickStore.store.solutions_path).to eq @solutions_path
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::GithubClient do
4
+
5
+ describe "#master_zip_url" do
6
+ it "returns the url to the master zip file for the given github repo" do
7
+ url = "https://github.com/a/b/archive/master.zip"
8
+ expect(Belajar::GithubClient.master_zip_url('a/b')).to eq url
9
+ end
10
+ end
11
+
12
+ describe "#updated_at" do
13
+ it "fetches the updated_at timestamp from the Github API" do
14
+ expected_timestamp = "2015-10-21T12:00:00Z"
15
+ response = { updated_at: expected_timestamp }.to_json
16
+ url = "https://api.github.com/repos/a/b"
17
+
18
+ stub_request(:get, url)
19
+ .with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' })
20
+ .to_return(status: 200, body: response, headers: {})
21
+
22
+ timestamp = Belajar::GithubClient.updated_at('a/b')
23
+
24
+ expect(timestamp).to eq expected_timestamp
25
+ end
26
+ end
27
+
28
+ describe "#updated?" do
29
+ before do
30
+ @received_timestamp = "2015-10-21T12:00:00Z"
31
+ response = { updated_at: @received_timestamp }.to_json
32
+ url = "https://api.github.com/repos/a/b"
33
+
34
+ stub_request(:get, url)
35
+ .with(headers: { 'Accept' => '*/*', 'User-Agent' => 'Ruby' })
36
+ .to_return(status: 200, body: response, headers: {})
37
+ end
38
+
39
+ it "returns true if content was pushed to the Github repo" do
40
+ QuickStore.store.set('courses/b/updated_at', "2015-10-21T11:59:59Z")
41
+ expect(Belajar::GithubClient.updated?('a/b')).to be_truthy
42
+ end
43
+
44
+ it "returns false if no content was pushed to the Github repo" do
45
+ QuickStore.store.set('courses/b/updated_at', @received_timestamp)
46
+ expect(Belajar::GithubClient.updated?('a/b')).to be_falsey
47
+ end
48
+
49
+ it "returns false if param is nil" do
50
+ expect(Belajar::GithubClient.updated?(nil)).to be_falsey
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::Loading::Chapters do
4
+
5
+ let(:subjects) { Belajar::Loading::Chapters.load(course_dirs.first) }
6
+
7
+ it "has the prescribed number of chapters" do
8
+ expect(subjects.count).to eq available_chapters(course_dirs.first).count
9
+ end
10
+
11
+ it "loads the available chapters" do
12
+ subjects.each_with_index do |chapter, index|
13
+ expect(chapter.title).to eq chapter_titles[index]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::Loading::Courses do
4
+
5
+ let(:subjects) { Belajar::Loading::Courses.load(courses_basepath) }
6
+
7
+ it "has the prescribed number of courses" do
8
+ expect(subjects.count).to eq available_courses.count
9
+ end
10
+
11
+ it "loads the available courses" do
12
+ subjects.each_with_index do |course, index|
13
+ expect(course.title).to eq course_titles[index]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::Loading::Units do
4
+
5
+ let(:course_name) { course_dir_names.first }
6
+ let(:chapter_path) { chapter_dirs(course_name).first }
7
+ let(:chapter_name) { File.basename(chapter_path) }
8
+
9
+ let(:subjects) { Belajar::Loading::Units.load(chapter_path) }
10
+
11
+ it "has the prescribed number of units" do
12
+ units_count = available_units(course_name, chapter_name).count
13
+ expect(subjects.count).to eq units_count
14
+ end
15
+
16
+ it "loads the available units" do
17
+ subjects.each_with_index do |unit, index|
18
+ expect(unit.title).to eq unit_titles[index]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Belajar::ReferenceSolution do
4
+
5
+ it { is_expected.to respond_to :code }
6
+ it { is_expected.to respond_to :path }
7
+ it { is_expected.to respond_to :code_lines }
8
+
9
+ let(:unit_path) do
10
+ course_name = course_dir_names.first
11
+ unit_dirs(course_name)[0].first
12
+ end
13
+
14
+ subject { Belajar::ReferenceSolution.new(unit_path) }
15
+
16
+ it "has the prescribed path" do
17
+ path = File.join(unit_path, reference_solution_name)
18
+ expect(subject.path).to eq path
19
+ end
20
+
21
+ describe "#code" do
22
+ it "has the prescribed code" do
23
+ expect(subject.code).to eq solution_content
24
+ end
25
+
26
+ it "returns an empty string if the code is not available" do
27
+ subject.instance_variable_set(:@code, nil)
28
+ expect(subject.code).to eq ""
29
+ end
30
+ end
31
+
32
+
33
+ describe "#code_lines" do
34
+ it "returns the code split into lines" do
35
+ lines = ['muffin = "sweet"', 'hamburger = "mjummy"']
36
+ allow(subject).to receive(:code) { lines.join("\n") }
37
+
38
+ expect(subject.code_lines).to eq lines
39
+ end
40
+ end
41
+ end