rosette-core 1.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.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +26 -0
  3. data/History.txt +3 -0
  4. data/README.md +94 -0
  5. data/Rakefile +18 -0
  6. data/lib/rosette/core.rb +110 -0
  7. data/lib/rosette/core/branch_utils.rb +152 -0
  8. data/lib/rosette/core/commands.rb +139 -0
  9. data/lib/rosette/core/commands/errors.rb +17 -0
  10. data/lib/rosette/core/commands/git/commit_command.rb +65 -0
  11. data/lib/rosette/core/commands/git/diff_base_command.rb +301 -0
  12. data/lib/rosette/core/commands/git/diff_command.rb +188 -0
  13. data/lib/rosette/core/commands/git/diff_entry.rb +44 -0
  14. data/lib/rosette/core/commands/git/fetch_command.rb +27 -0
  15. data/lib/rosette/core/commands/git/repo_snapshot_command.rb +40 -0
  16. data/lib/rosette/core/commands/git/show_command.rb +70 -0
  17. data/lib/rosette/core/commands/git/snapshot_command.rb +50 -0
  18. data/lib/rosette/core/commands/git/status_command.rb +128 -0
  19. data/lib/rosette/core/commands/git/with_non_merge_ref.rb +48 -0
  20. data/lib/rosette/core/commands/git/with_ref.rb +92 -0
  21. data/lib/rosette/core/commands/git/with_refs.rb +92 -0
  22. data/lib/rosette/core/commands/git/with_repo_name.rb +50 -0
  23. data/lib/rosette/core/commands/git/with_snapshots.rb +45 -0
  24. data/lib/rosette/core/commands/queuing/enqueue_commit_command.rb +37 -0
  25. data/lib/rosette/core/commands/queuing/requeue_commit_command.rb +46 -0
  26. data/lib/rosette/core/commands/translations/export_command.rb +257 -0
  27. data/lib/rosette/core/commands/translations/translation_lookup_command.rb +66 -0
  28. data/lib/rosette/core/commands/translations/with_locale.rb +47 -0
  29. data/lib/rosette/core/configurator.rb +160 -0
  30. data/lib/rosette/core/error_reporters/buffered_error_reporter.rb +96 -0
  31. data/lib/rosette/core/error_reporters/error_reporter.rb +31 -0
  32. data/lib/rosette/core/error_reporters/nil_error_reporter.rb +25 -0
  33. data/lib/rosette/core/error_reporters/printing_error_reporter.rb +58 -0
  34. data/lib/rosette/core/error_reporters/raising_error_reporter.rb +27 -0
  35. data/lib/rosette/core/errors.rb +93 -0
  36. data/lib/rosette/core/extractor/commit_log.rb +33 -0
  37. data/lib/rosette/core/extractor/commit_log_status.rb +57 -0
  38. data/lib/rosette/core/extractor/commit_processor.rb +109 -0
  39. data/lib/rosette/core/extractor/extractor.rb +72 -0
  40. data/lib/rosette/core/extractor/extractor_config.rb +74 -0
  41. data/lib/rosette/core/extractor/locale.rb +118 -0
  42. data/lib/rosette/core/extractor/phrase.rb +76 -0
  43. data/lib/rosette/core/extractor/phrase/phrase_index_policy.rb +108 -0
  44. data/lib/rosette/core/extractor/phrase/phrase_to_hash.rb +33 -0
  45. data/lib/rosette/core/extractor/repo_config.rb +339 -0
  46. data/lib/rosette/core/extractor/serializer_config.rb +55 -0
  47. data/lib/rosette/core/extractor/static_extractor.rb +44 -0
  48. data/lib/rosette/core/extractor/translation.rb +44 -0
  49. data/lib/rosette/core/extractor/translation/translation_to_hash.rb +28 -0
  50. data/lib/rosette/core/git/diff_finder.rb +131 -0
  51. data/lib/rosette/core/git/ref.rb +116 -0
  52. data/lib/rosette/core/git/repo.rb +378 -0
  53. data/lib/rosette/core/path_matcher_factory.rb +330 -0
  54. data/lib/rosette/core/resolvers/extractor_id.rb +37 -0
  55. data/lib/rosette/core/resolvers/integration_id.rb +37 -0
  56. data/lib/rosette/core/resolvers/preprocessor_id.rb +38 -0
  57. data/lib/rosette/core/resolvers/resolver.rb +115 -0
  58. data/lib/rosette/core/resolvers/serializer_id.rb +37 -0
  59. data/lib/rosette/core/snapshots/cached_head_snapshot_factory.rb +51 -0
  60. data/lib/rosette/core/snapshots/cached_snapshot_factory.rb +67 -0
  61. data/lib/rosette/core/snapshots/head_snapshot_factory.rb +58 -0
  62. data/lib/rosette/core/snapshots/repo_config_path_filter.rb +83 -0
  63. data/lib/rosette/core/snapshots/snapshot_factory.rb +184 -0
  64. data/lib/rosette/core/string_utils.rb +23 -0
  65. data/lib/rosette/core/translation_status.rb +81 -0
  66. data/lib/rosette/core/validators.rb +18 -0
  67. data/lib/rosette/core/validators/commit_validator.rb +62 -0
  68. data/lib/rosette/core/validators/commits_validator.rb +32 -0
  69. data/lib/rosette/core/validators/encoding_validator.rb +32 -0
  70. data/lib/rosette/core/validators/locale_validator.rb +37 -0
  71. data/lib/rosette/core/validators/repo_validator.rb +33 -0
  72. data/lib/rosette/core/validators/serializer_validator.rb +37 -0
  73. data/lib/rosette/core/validators/validator.rb +31 -0
  74. data/lib/rosette/core/version.rb +8 -0
  75. data/lib/rosette/data_stores.rb +11 -0
  76. data/lib/rosette/data_stores/errors.rb +26 -0
  77. data/lib/rosette/data_stores/phrase_status.rb +59 -0
  78. data/lib/rosette/integrations.rb +12 -0
  79. data/lib/rosette/integrations/errors.rb +15 -0
  80. data/lib/rosette/integrations/integratable.rb +58 -0
  81. data/lib/rosette/integrations/integration.rb +23 -0
  82. data/lib/rosette/preprocessors.rb +11 -0
  83. data/lib/rosette/preprocessors/errors.rb +14 -0
  84. data/lib/rosette/preprocessors/preprocessor.rb +48 -0
  85. data/lib/rosette/queuing.rb +14 -0
  86. data/lib/rosette/queuing/commits.rb +19 -0
  87. data/lib/rosette/queuing/commits/commit_conductor.rb +90 -0
  88. data/lib/rosette/queuing/commits/commit_job.rb +93 -0
  89. data/lib/rosette/queuing/commits/commits_queue_configurator.rb +60 -0
  90. data/lib/rosette/queuing/commits/extract_stage.rb +46 -0
  91. data/lib/rosette/queuing/commits/fetch_stage.rb +51 -0
  92. data/lib/rosette/queuing/commits/finalize_stage.rb +76 -0
  93. data/lib/rosette/queuing/commits/phrase_storage_granularity.rb +20 -0
  94. data/lib/rosette/queuing/commits/push_stage.rb +91 -0
  95. data/lib/rosette/queuing/commits/stage.rb +96 -0
  96. data/lib/rosette/queuing/job.rb +74 -0
  97. data/lib/rosette/queuing/queue.rb +28 -0
  98. data/lib/rosette/queuing/queue_configurator.rb +76 -0
  99. data/lib/rosette/queuing/worker.rb +30 -0
  100. data/lib/rosette/serializers.rb +10 -0
  101. data/lib/rosette/serializers/serializer.rb +98 -0
  102. data/lib/rosette/tms.rb +9 -0
  103. data/lib/rosette/tms/repository.rb +95 -0
  104. data/rosette-core.gemspec +24 -0
  105. data/spec/core/branch_utils_spec.rb +110 -0
  106. data/spec/core/commands/git/commit_command_spec.rb +60 -0
  107. data/spec/core/commands/git/diff_command_spec.rb +263 -0
  108. data/spec/core/commands/git/fetch_command_spec.rb +61 -0
  109. data/spec/core/commands/git/repo_snapshot_command_spec.rb +72 -0
  110. data/spec/core/commands/git/show_command_spec.rb +128 -0
  111. data/spec/core/commands/git/snapshot_command_spec.rb +86 -0
  112. data/spec/core/commands/git/status_command_spec.rb +154 -0
  113. data/spec/core/commands/queuing/enqueue_commit_command_spec.rb +34 -0
  114. data/spec/core/commands/queuing/requeue_commit_command_spec.rb +46 -0
  115. data/spec/core/commands/translations/export_command_spec.rb +113 -0
  116. data/spec/core/commands/translations/translation_lookup_command_spec.rb +58 -0
  117. data/spec/core/configurator_spec.rb +47 -0
  118. data/spec/core/error_reporters/buffered_error_reporter_spec.rb +61 -0
  119. data/spec/core/error_reporters/nil_error_reporter_spec.rb +16 -0
  120. data/spec/core/error_reporters/printing_error_reporter_spec.rb +60 -0
  121. data/spec/core/extractor/commit_log_status_spec.rb +216 -0
  122. data/spec/core/extractor/commit_processor_spec.rb +68 -0
  123. data/spec/core/extractor/extractor_config_spec.rb +47 -0
  124. data/spec/core/extractor/extractor_spec.rb +26 -0
  125. data/spec/core/extractor/locale_spec.rb +92 -0
  126. data/spec/core/extractor/phrase/phrase_index_policy_spec.rb +116 -0
  127. data/spec/core/extractor/phrase/phrase_to_hash_spec.rb +18 -0
  128. data/spec/core/extractor/repo_config_spec.rb +147 -0
  129. data/spec/core/extractor/translation/translation_to_hash_spec.rb +25 -0
  130. data/spec/core/git/diff_finder_spec.rb +74 -0
  131. data/spec/core/git/ref_spec.rb +118 -0
  132. data/spec/core/git/repo_spec.rb +216 -0
  133. data/spec/core/path_matcher_factory_spec.rb +139 -0
  134. data/spec/core/resolvers/extractor_id_spec.rb +47 -0
  135. data/spec/core/resolvers/integration_id_spec.rb +47 -0
  136. data/spec/core/resolvers/preprocessor_id_spec.rb +47 -0
  137. data/spec/core/resolvers/serializer_id_spec.rb +47 -0
  138. data/spec/core/snapshots/snapshot_factory_spec.rb +145 -0
  139. data/spec/core/string_utils_spec.rb +19 -0
  140. data/spec/core/translation_status_spec.rb +91 -0
  141. data/spec/core/validators/commit_validator_spec.rb +40 -0
  142. data/spec/core/validators/encoding_validator_spec.rb +30 -0
  143. data/spec/core/validators/locale_validator_spec.rb +31 -0
  144. data/spec/core/validators/repo_validator_spec.rb +30 -0
  145. data/spec/core/validators/serializer_validator_spec.rb +31 -0
  146. data/spec/integrations/integratable_spec.rb +58 -0
  147. data/spec/queuing/commits/commit_conductor_spec.rb +71 -0
  148. data/spec/queuing/commits/commit_job_spec.rb +87 -0
  149. data/spec/queuing/commits/extract_stage_spec.rb +68 -0
  150. data/spec/queuing/commits/fetch_stage_spec.rb +101 -0
  151. data/spec/queuing/commits/finalize_stage_spec.rb +88 -0
  152. data/spec/queuing/commits/push_stage_spec.rb +145 -0
  153. data/spec/queuing/commits/stage_spec.rb +80 -0
  154. data/spec/queuing/job_spec.rb +33 -0
  155. data/spec/queuing/queue_configurator_spec.rb +44 -0
  156. data/spec/spec_helper.rb +90 -0
  157. data/spec/test_helpers/fake_commit_stage.rb +17 -0
  158. metadata +257 -0
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe ExtractorId do
8
+ module ExtractorsNoNaming
9
+ module Foo
10
+ class Bar; end
11
+ end
12
+ end
13
+
14
+ module ExtractorsOneLevel
15
+ module Foo
16
+ class BarExtractor; end
17
+ end
18
+ end
19
+
20
+ module ExtractorsTwoLevels
21
+ module FooExtractor
22
+ class BarExtractor; end
23
+ end
24
+ end
25
+
26
+ let(:id) { ExtractorId }
27
+
28
+ describe '#resolve' do
29
+ it 'resolves constants with no modified naming' do
30
+ expect(id.resolve('foo/bar', ExtractorsNoNaming)).to(
31
+ be(ExtractorsNoNaming::Foo::Bar)
32
+ )
33
+ end
34
+
35
+ it 'resolves constants with one level of naming' do
36
+ expect(id.resolve('foo/bar', ExtractorsOneLevel)).to(
37
+ be(ExtractorsOneLevel::Foo::BarExtractor)
38
+ )
39
+ end
40
+
41
+ it 'resolves constants with two levels of naming' do
42
+ expect(id.resolve('foo/bar', ExtractorsTwoLevels)).to(
43
+ be(ExtractorsTwoLevels::FooExtractor::BarExtractor)
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe IntegrationId do
8
+ module IntegrationsNoNaming
9
+ module Foo
10
+ class Bar; end
11
+ end
12
+ end
13
+
14
+ module IntegrationsOneLevel
15
+ module Foo
16
+ class BarIntegration; end
17
+ end
18
+ end
19
+
20
+ module IntegrationsTwoLevels
21
+ module FooIntegration
22
+ class BarIntegration; end
23
+ end
24
+ end
25
+
26
+ let(:id) { IntegrationId }
27
+
28
+ describe '#resolve' do
29
+ it 'resolves constants with no modified naming' do
30
+ expect(id.resolve('foo/bar', IntegrationsNoNaming)).to(
31
+ be(IntegrationsNoNaming::Foo::Bar)
32
+ )
33
+ end
34
+
35
+ it 'resolves constants with one level of naming' do
36
+ expect(id.resolve('foo/bar', IntegrationsOneLevel)).to(
37
+ be(IntegrationsOneLevel::Foo::BarIntegration)
38
+ )
39
+ end
40
+
41
+ it 'resolves constants with two levels of naming' do
42
+ expect(id.resolve('foo/bar', IntegrationsTwoLevels)).to(
43
+ be(IntegrationsTwoLevels::FooIntegration::BarIntegration)
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe PreprocessorId do
8
+ module PreprocessorsNoNaming
9
+ module Foo
10
+ class Bar; end
11
+ end
12
+ end
13
+
14
+ module PreprocessorsOneLevel
15
+ module Foo
16
+ class BarPreprocessor; end
17
+ end
18
+ end
19
+
20
+ module PreprocessorsTwoLevels
21
+ module FooPreprocessor
22
+ class BarPreprocessor; end
23
+ end
24
+ end
25
+
26
+ let(:id) { PreprocessorId }
27
+
28
+ describe '#resolve' do
29
+ it 'resolves constants with no modified naming' do
30
+ expect(id.resolve('foo/bar', PreprocessorsNoNaming)).to(
31
+ be(PreprocessorsNoNaming::Foo::Bar)
32
+ )
33
+ end
34
+
35
+ it 'resolves constants with one level of naming' do
36
+ expect(id.resolve('foo/bar', PreprocessorsOneLevel)).to(
37
+ be(PreprocessorsOneLevel::Foo::BarPreprocessor)
38
+ )
39
+ end
40
+
41
+ it 'resolves constants with two levels of naming' do
42
+ expect(id.resolve('foo/bar', PreprocessorsTwoLevels)).to(
43
+ be(PreprocessorsTwoLevels::FooPreprocessor::BarPreprocessor)
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe SerializerId do
8
+ module SerializersNoNaming
9
+ module Foo
10
+ class Bar; end
11
+ end
12
+ end
13
+
14
+ module SerializersOneLevel
15
+ module Foo
16
+ class BarSerializer; end
17
+ end
18
+ end
19
+
20
+ module SerializersTwoLevels
21
+ module FooSerializer
22
+ class BarSerializer; end
23
+ end
24
+ end
25
+
26
+ let(:id) { SerializerId }
27
+
28
+ describe '#resolve' do
29
+ it 'resolves constants with no modified naming' do
30
+ expect(id.resolve('foo/bar', SerializersNoNaming)).to(
31
+ be(SerializersNoNaming::Foo::Bar)
32
+ )
33
+ end
34
+
35
+ it 'resolves constants with one level of naming' do
36
+ expect(id.resolve('foo/bar', SerializersOneLevel)).to(
37
+ be(SerializersOneLevel::Foo::BarSerializer)
38
+ )
39
+ end
40
+
41
+ it 'resolves constants with two levels of naming' do
42
+ expect(id.resolve('foo/bar', SerializersTwoLevels)).to(
43
+ be(SerializersTwoLevels::FooSerializer::BarSerializer)
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,145 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe SnapshotFactory do
8
+ let(:factory_class) { SnapshotFactory }
9
+ let(:repo_name) { 'fake_app' }
10
+ let(:fixture) { load_repo_fixture(repo_name) }
11
+ let(:commits) do
12
+ fixture.git('rev-list --all').split("\n").reverse
13
+ end
14
+
15
+ let(:rosette_config) do
16
+ Rosette.build_config do |config|
17
+ config.add_repo(repo_name) do |repo_config|
18
+ repo_config.set_path(fixture.working_dir.join('.git').to_s)
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:repo_config) do
24
+ rosette_config.get_repo(repo_name)
25
+ end
26
+
27
+ describe '#take_snapshot' do
28
+ it 'returns the correct snapshot for the first commit' do
29
+ factory = factory_class.new
30
+ .set_repo_config(repo_config)
31
+ .set_start_commit_id(commits.first)
32
+
33
+ factory.take_snapshot.tap do |snapshot|
34
+ expect(snapshot).to eq(
35
+ 'app/controllers/product_controller.txt' => commits.first,
36
+ 'app/models/product.txt' => commits.first,
37
+ 'app/views/product/edit.txt' => commits.first,
38
+ 'app/controllers/file.other' => commits.first
39
+ )
40
+ end
41
+ end
42
+
43
+ it 'raises an error if passed a non-staged progress reporter' do
44
+ reporter = ::ProgressReporters::ProgressReporter.new
45
+
46
+ factory = factory_class.new
47
+ .set_repo_config(repo_config)
48
+ .set_start_commit_id(commits.first)
49
+
50
+ expect(lambda { factory.take_snapshot(reporter) }).to(
51
+ raise_error(ArgumentError)
52
+ )
53
+ end
54
+
55
+ context 'with a factory pointed at the last commit' do
56
+ let(:factory) do
57
+ factory_class.new
58
+ .set_repo_config(repo_config)
59
+ .set_start_commit_id(commits.last)
60
+ end
61
+
62
+ it 'returns the correct snapshot for the second commit' do
63
+ factory.take_snapshot.tap do |snapshot|
64
+ expect(snapshot).to eq(
65
+ 'app/controllers/product_controller.txt' => commits.first,
66
+ 'app/models/product.txt' => commits.first,
67
+ 'app/views/product/edit.txt' => commits.first,
68
+ 'app/controllers/order_controller.txt' => commits.last,
69
+ 'app/models/order.txt' => commits.last,
70
+ 'app/models/line_item.txt' => commits.last,
71
+ 'app/views/order/index.txt' => commits.last,
72
+ 'app/controllers/file.other' => commits.first,
73
+ 'app/models/another_file.other' => commits.last
74
+ )
75
+ end
76
+ end
77
+
78
+ it 'only includes files with matching file extensions when asked' do
79
+ repo_config.add_extractor('test/test') do |extractor_config|
80
+ extractor_config.set_conditions do |conditions|
81
+ conditions.match_file_extension('.other')
82
+ end
83
+ end
84
+
85
+ factory.take_snapshot.tap do |snapshot|
86
+ expect(snapshot).to eq(
87
+ 'app/controllers/file.other' => commits.first,
88
+ 'app/models/another_file.other' => commits.last
89
+ )
90
+ end
91
+ end
92
+
93
+ it 'only includes files with matching paths when asked' do
94
+ repo_config.add_extractor('test/test') do |extractor_config|
95
+ extractor_config.set_conditions do |conditions|
96
+ conditions.match_path('app/controllers')
97
+ end
98
+ end
99
+
100
+ factory.take_snapshot.tap do |snapshot|
101
+ expect(snapshot).to eq(
102
+ 'app/controllers/product_controller.txt' => commits.first,
103
+ 'app/controllers/order_controller.txt' => commits.last,
104
+ 'app/controllers/file.other' => commits.first
105
+ )
106
+ end
107
+ end
108
+
109
+ it 'supports combining multiple filters with a logical OR' do
110
+ repo_config.add_extractor('test/test') do |extractor_config|
111
+ extractor_config.set_conditions do |conditions|
112
+ conditions.match_path('app/controllers').or(
113
+ conditions.match_file_extension('.other')
114
+ )
115
+ end
116
+ end
117
+
118
+ factory.take_snapshot.tap do |snapshot|
119
+ expect(snapshot).to eq(
120
+ 'app/controllers/product_controller.txt' => commits.first,
121
+ 'app/controllers/order_controller.txt' => commits.last,
122
+ 'app/controllers/file.other' => commits.first,
123
+ 'app/models/another_file.other' => commits.last
124
+ )
125
+ end
126
+ end
127
+
128
+ it 'supports combining multiple filters with a logical AND' do
129
+ repo_config.add_extractor('test/test') do |extractor_config|
130
+ extractor_config.set_conditions do |conditions|
131
+ conditions.match_path('app/controllers').and(
132
+ conditions.match_file_extension('.other')
133
+ )
134
+ end
135
+ end
136
+
137
+ factory.take_snapshot.tap do |snapshot|
138
+ expect(snapshot).to eq(
139
+ 'app/controllers/file.other' => commits.first
140
+ )
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe StringUtils do
8
+ let(:utils) { Rosette::Core::StringUtils }
9
+
10
+ describe '#camelize' do
11
+ it 'treats underscores as capitalization boundaries' do
12
+ expect(utils.camelize('foo_bar')).to eq('FooBar')
13
+ end
14
+
15
+ it 'treats dashes as capitalization boundaries' do
16
+ expect(utils.camelize('foo-bar')).to eq('FooBar')
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,91 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ include Rosette::Core
6
+
7
+ describe TranslationStatus do
8
+ let(:phrase_count) { 10 }
9
+ let(:german) { 'de-DE' }
10
+ let(:korean) { 'ko-KR' }
11
+ let(:status) { TranslationStatus.new(phrase_count) }
12
+
13
+ describe '#add_locale_count' do
14
+ it 'adds a locale and corresponding count' do
15
+ status.add_locale_count(german, 5)
16
+ expect(status.locale_counts[german]).to eq(5)
17
+ end
18
+ end
19
+
20
+ describe '#locale_count' do
21
+ it 'retrieves the translation count for the given locale' do
22
+ status.add_locale_count(german, 5)
23
+ expect(status.locale_count(german)).to eq(5)
24
+ end
25
+ end
26
+
27
+ describe '#locales' do
28
+ it 'returns a list of all added locales' do
29
+ status.add_locale_count(german, 1)
30
+ status.add_locale_count(korean, 2)
31
+ expect(status.locales.sort).to eq([german, korean].sort)
32
+ end
33
+ end
34
+
35
+ describe '#fully_translated_in?' do
36
+ it 'returns true if the locale is fully translated' do
37
+ status.add_locale_count(german, phrase_count)
38
+ expect(status).to be_fully_translated_in(german)
39
+ end
40
+
41
+ it 'returns true if the locale contains more translations than phrases' do
42
+ status.add_locale_count(german, phrase_count + 1)
43
+ expect(status).to be_fully_translated_in(german)
44
+ end
45
+
46
+ it 'returns false if the locale is not fully translated' do
47
+ status.add_locale_count(german, phrase_count - 1)
48
+ expect(status).to_not be_fully_translated_in(german)
49
+ end
50
+ end
51
+
52
+ describe '#fully_translated?' do
53
+ it 'returns true if all locales are fully translated' do
54
+ status.add_locale_count(german, phrase_count)
55
+ status.add_locale_count(korean, phrase_count)
56
+ expect(status).to be_fully_translated
57
+ end
58
+
59
+ it 'returns false if at least one locale is not fully translated' do
60
+ status.add_locale_count(german, phrase_count)
61
+ status.add_locale_count(korean, phrase_count - 1)
62
+ expect(status).to_not be_fully_translated
63
+ end
64
+ end
65
+
66
+ describe '#percent_translated' do
67
+ it 'calculates the percent translated for the given locale' do
68
+ status.add_locale_count(german, phrase_count / 2)
69
+ expect(status.percent_translated(german)).to eq(0.5)
70
+ end
71
+
72
+ it 'returns 1.0 if translations outnumber phrases' do
73
+ status.add_locale_count(german, phrase_count + 1)
74
+ expect(status.percent_translated(german)).to eq(1.0)
75
+ end
76
+
77
+ context 'with an odd number of phrases' do
78
+ let(:phrase_count) { 7 }
79
+
80
+ it 'defaults to a precision of two decimal places' do
81
+ status.add_locale_count(german, 3)
82
+ expect(status.percent_translated(german)).to eq(0.43)
83
+ end
84
+
85
+ it 'rounds to the given number of decimal places' do
86
+ status.add_locale_count(german, 3)
87
+ expect(status.percent_translated(german, 3)).to eq(0.429)
88
+ end
89
+ end
90
+ end
91
+ end