i18n-js 2.1.2 → 3.8.3

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 (127) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +24 -0
  3. data/.github/FUNDING.yml +3 -0
  4. data/.github/workflows/tests.yaml +100 -0
  5. data/.gitignore +6 -4
  6. data/.npmignore +27 -0
  7. data/Appraisals +44 -0
  8. data/CHANGELOG.md +539 -0
  9. data/Gemfile +1 -1
  10. data/README.md +1086 -0
  11. data/Rakefile +19 -7
  12. data/app/assets/javascripts/i18n.js +1095 -0
  13. data/app/assets/javascripts/i18n/filtered.js.erb +23 -0
  14. data/app/assets/javascripts/i18n/shims.js +240 -0
  15. data/app/assets/javascripts/i18n/translations.js +3 -0
  16. data/gemfiles/i18n_0_6.gemfile +7 -0
  17. data/gemfiles/i18n_0_7.gemfile +7 -0
  18. data/gemfiles/i18n_0_8.gemfile +7 -0
  19. data/gemfiles/i18n_0_9.gemfile +7 -0
  20. data/gemfiles/i18n_1_0.gemfile +7 -0
  21. data/gemfiles/i18n_1_1.gemfile +7 -0
  22. data/gemfiles/i18n_1_2.gemfile +7 -0
  23. data/gemfiles/i18n_1_3.gemfile +7 -0
  24. data/gemfiles/i18n_1_4.gemfile +7 -0
  25. data/gemfiles/i18n_1_5.gemfile +7 -0
  26. data/gemfiles/i18n_1_6.gemfile +7 -0
  27. data/gemfiles/i18n_1_7.gemfile +7 -0
  28. data/gemfiles/i18n_1_8.gemfile +7 -0
  29. data/i18n-js.gemspec +12 -9
  30. data/i18njs.png +0 -0
  31. data/lib/i18n-js.rb +1 -177
  32. data/lib/i18n/js.rb +264 -0
  33. data/lib/i18n/js/dependencies.rb +63 -0
  34. data/lib/i18n/js/engine.rb +87 -0
  35. data/lib/i18n/js/fallback_locales.rb +70 -0
  36. data/lib/i18n/js/formatters/base.rb +25 -0
  37. data/lib/i18n/js/formatters/js.rb +39 -0
  38. data/lib/i18n/js/formatters/json.rb +13 -0
  39. data/lib/{i18n-js → i18n/js}/middleware.rb +32 -9
  40. data/lib/i18n/js/private/config_store.rb +31 -0
  41. data/lib/i18n/js/private/hash_with_symbol_keys.rb +36 -0
  42. data/lib/i18n/js/segment.rb +80 -0
  43. data/lib/i18n/js/utils.rb +78 -0
  44. data/lib/i18n/js/version.rb +7 -0
  45. data/lib/rails/generators/i18n/js/config/config_generator.rb +19 -0
  46. data/{config → lib/rails/generators/i18n/js/config/templates}/i18n-js.yml +11 -6
  47. data/lib/tasks/export.rake +8 -0
  48. data/package.json +25 -0
  49. data/spec/fixtures/custom_path.yml +5 -0
  50. data/spec/fixtures/default.yml +5 -0
  51. data/spec/fixtures/erb.yml +5 -0
  52. data/spec/fixtures/except_condition.yml +7 -0
  53. data/spec/fixtures/js_export_dir_custom.yml +7 -0
  54. data/spec/fixtures/js_export_dir_none.yml +6 -0
  55. data/spec/fixtures/js_extend_parent.yml +6 -0
  56. data/spec/fixtures/js_extend_segment.yml +6 -0
  57. data/spec/fixtures/js_file_per_locale.yml +7 -0
  58. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_default_locale_symbol.yml +4 -0
  59. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_hash.yml +6 -0
  60. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale.yml +4 -0
  61. data/spec/fixtures/js_file_per_locale_with_fallbacks_as_locale_without_fallback_translations.yml +4 -0
  62. data/spec/fixtures/js_file_per_locale_with_fallbacks_enabled.yml +4 -0
  63. data/spec/fixtures/js_file_per_locale_without_fallbacks.yml +4 -0
  64. data/spec/fixtures/js_file_with_namespace_prefix_and_pretty_print.yml +9 -0
  65. data/spec/fixtures/js_sort_translation_keys_false.yml +6 -0
  66. data/spec/fixtures/js_sort_translation_keys_true.yml +6 -0
  67. data/spec/fixtures/json_only.yml +18 -0
  68. data/spec/{resources → fixtures}/locales.yml +58 -1
  69. data/spec/fixtures/merge_plurals.yml +6 -0
  70. data/spec/fixtures/merge_plurals_with_no_overrides.yml +4 -0
  71. data/spec/fixtures/merge_plurals_with_partial_overrides.yml +4 -0
  72. data/spec/fixtures/millions.yml +4 -0
  73. data/spec/fixtures/multiple_conditions.yml +7 -0
  74. data/spec/fixtures/multiple_conditions_per_locale.yml +7 -0
  75. data/spec/fixtures/multiple_files.yml +7 -0
  76. data/spec/{resources → fixtures}/no_config.yml +0 -0
  77. data/spec/fixtures/no_scope.yml +4 -0
  78. data/spec/fixtures/simple_scope.yml +5 -0
  79. data/spec/js/currency.spec.js +62 -0
  80. data/spec/js/current_locale.spec.js +19 -0
  81. data/spec/js/dates.spec.js +276 -0
  82. data/spec/js/defaults.spec.js +31 -0
  83. data/spec/js/extend.spec.js +110 -0
  84. data/spec/js/interpolation.spec.js +124 -0
  85. data/spec/js/jasmine/MIT.LICENSE +20 -0
  86. data/spec/js/jasmine/jasmine-html.js +190 -0
  87. data/spec/js/jasmine/jasmine.css +166 -0
  88. data/spec/js/jasmine/jasmine.js +2476 -0
  89. data/spec/js/jasmine/jasmine_favicon.png +0 -0
  90. data/spec/js/json_parsable.spec.js +14 -0
  91. data/spec/js/locales.spec.js +31 -0
  92. data/spec/js/localization.spec.js +78 -0
  93. data/spec/js/numbers.spec.js +174 -0
  94. data/spec/js/placeholder.spec.js +24 -0
  95. data/spec/js/pluralization.spec.js +219 -0
  96. data/spec/js/prepare_options.spec.js +41 -0
  97. data/spec/js/require.js +2083 -0
  98. data/spec/js/specs.html +49 -0
  99. data/spec/js/specs_requirejs.html +72 -0
  100. data/spec/js/translate.spec.js +304 -0
  101. data/spec/js/translations.js +188 -0
  102. data/spec/js/utility_functions.spec.js +20 -0
  103. data/spec/ruby/i18n/js/fallback_locales_spec.rb +84 -0
  104. data/spec/ruby/i18n/js/segment_spec.rb +261 -0
  105. data/spec/ruby/i18n/js/utils_spec.rb +106 -0
  106. data/spec/ruby/i18n/js_spec.rb +748 -0
  107. data/spec/spec_helper.rb +75 -14
  108. data/yarn.lock +131 -0
  109. metadata +223 -98
  110. data/.rspec +0 -1
  111. data/Gemfile.lock +0 -51
  112. data/README.rdoc +0 -305
  113. data/lib/i18n-js/engine.rb +0 -62
  114. data/lib/i18n-js/railtie.rb +0 -13
  115. data/lib/i18n-js/rake.rb +0 -16
  116. data/lib/i18n-js/version.rb +0 -10
  117. data/spec/i18n_spec.js +0 -768
  118. data/spec/i18n_spec.rb +0 -205
  119. data/spec/resources/custom_path.yml +0 -4
  120. data/spec/resources/default.yml +0 -4
  121. data/spec/resources/js_file_per_locale.yml +0 -3
  122. data/spec/resources/multiple_conditions.yml +0 -6
  123. data/spec/resources/multiple_files.yml +0 -6
  124. data/spec/resources/no_scope.yml +0 -3
  125. data/spec/resources/simple_scope.yml +0 -4
  126. data/vendor/assets/javascripts/i18n.js +0 -450
  127. data/vendor/assets/javascripts/i18n/translations.js.erb +0 -7
@@ -0,0 +1,20 @@
1
+ var I18n = require("../../app/assets/javascripts/i18n");
2
+
3
+ describe("Utility Functions", function(){
4
+ beforeEach(function(){
5
+ I18n.reset();
6
+ });
7
+
8
+ describe("I18n.lookup", function() {
9
+ it("does not change locale on failed lookup", function(){
10
+ var fallback_locales = ['fallback1', 'fallback2'];
11
+
12
+ I18n.locales['lang'] = fallback_locales.slice();
13
+ expect(I18n.locales.lang).toEqual(fallback_locales);
14
+
15
+ I18n.lookup('anything', {locale: 'lang'})
16
+ expect(I18n.locales.lang).toEqual(fallback_locales);
17
+ });
18
+
19
+ })
20
+ });
@@ -0,0 +1,84 @@
1
+ require "spec_helper"
2
+
3
+ describe I18n::JS::FallbackLocales do
4
+ let(:locale) { :fr }
5
+ let(:default_locale) { :en }
6
+
7
+ describe "#locales" do
8
+ let(:fallbacks_locales) { described_class.new(fallbacks, locale) }
9
+ subject { fallbacks_locales.locales }
10
+
11
+ let(:fetching_locales) { proc do fallbacks_locales.locales end }
12
+
13
+ context "when given true as fallbacks" do
14
+ let(:fallbacks) { true }
15
+ it { should eq([default_locale]) }
16
+ end
17
+
18
+ context "when given false as fallbacks" do
19
+ let(:fallbacks) { false }
20
+ it { expect(fetching_locales).to raise_error(ArgumentError) }
21
+ end
22
+
23
+ context "when given a valid locale as fallbacks" do
24
+ let(:fallbacks) { :de }
25
+ it { should eq([:de]) }
26
+ end
27
+
28
+ context "when given a valid Array as fallbacks" do
29
+ let(:fallbacks) { [:de, :en] }
30
+ it { should eq([:de, :en]) }
31
+ end
32
+
33
+ context "when given a valid Hash with current locale as key as fallbacks" do
34
+ let(:fallbacks) do { :fr => [:de, :en] } end
35
+ it { should eq([:de, :en]) }
36
+ end
37
+
38
+ context "when given a valid Hash without current locale as key as fallbacks" do
39
+ let(:fallbacks) do { :de => [:fr, :en] } end
40
+ it { should eq([default_locale]) }
41
+ end
42
+
43
+ context "when given a invalid locale as fallbacks" do
44
+ let(:fallbacks) { :invalid_locale }
45
+ it { should eq([:invalid_locale]) }
46
+ end
47
+
48
+ context "when given a invalid type as fallbacks" do
49
+ let(:fallbacks) { 42 }
50
+ it { expect(fetching_locales).to raise_error(ArgumentError) }
51
+ end
52
+
53
+ # I18n::Backend::Fallbacks
54
+ context "when I18n::Backend::Fallbacks is used" do
55
+ let(:backend_with_fallbacks) { backend_class_with_fallbacks.new }
56
+
57
+ before do
58
+ I18n::JS.backend = backend_with_fallbacks
59
+ I18n.fallbacks[:fr] = [:de, :en]
60
+ end
61
+ after { I18n::JS.backend = I18n::Backend::Simple.new }
62
+
63
+ context "given true as fallbacks" do
64
+ let(:fallbacks) { true }
65
+ it { should eq([:de, :en]) }
66
+ end
67
+
68
+ context "given :default_locale as fallbacks" do
69
+ let(:fallbacks) { :default_locale }
70
+ it { should eq([:en]) }
71
+ end
72
+
73
+ context "given a Hash with current locale as fallbacks" do
74
+ let(:fallbacks) do { :fr => [:en] } end
75
+ it { should eq([:en]) }
76
+ end
77
+
78
+ context "given a Hash without current locale as fallbacks" do
79
+ let(:fallbacks) do { :de => [:en] } end
80
+ it { should eq([:de, :en]) }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,261 @@
1
+ require "spec_helper"
2
+
3
+ describe I18n::JS::Segment do
4
+
5
+ let(:file) { "tmp/i18n-js/segment.js" }
6
+ let(:translations){ { en: { "test" => "Test" }, fr: { "test" => "Test2" } } }
7
+ let(:namespace) { "MyNamespace" }
8
+ let(:pretty_print){ nil }
9
+ let(:json_only) { nil }
10
+ let(:js_extend) { nil }
11
+ let(:sort_translation_keys){ nil }
12
+ let(:options) { { namespace: namespace,
13
+ pretty_print: pretty_print,
14
+ json_only: json_only,
15
+ js_extend: js_extend,
16
+ sort_translation_keys: sort_translation_keys }.delete_if{|k,v| v.nil?} }
17
+ subject { I18n::JS::Segment.new(file, translations, options) }
18
+
19
+ describe ".new" do
20
+
21
+ it "should persist the file path variable" do
22
+ expect(subject.file).to eql("tmp/i18n-js/segment.js")
23
+ end
24
+
25
+ it "should persist the translations variable" do
26
+ expect(subject.translations).to eql(translations)
27
+ end
28
+
29
+ it "should persist the namespace variable" do
30
+ expect(subject.namespace).to eql("MyNamespace")
31
+ end
32
+
33
+ context "when namespace is nil" do
34
+ let(:namespace){ nil }
35
+
36
+ it "should default namespace to `I18n`" do
37
+ expect(subject.namespace).to eql("I18n")
38
+ end
39
+ end
40
+
41
+ context "when namespace is not set" do
42
+ subject { I18n::JS::Segment.new(file, translations) }
43
+
44
+ it "should default namespace to `I18n`" do
45
+ expect(subject.namespace).to eql("I18n")
46
+ end
47
+ end
48
+
49
+ context "when pretty_print is nil" do
50
+ it "should set pretty_print to false" do
51
+ expect(subject.pretty_print).to be false
52
+ end
53
+ end
54
+
55
+ context "when pretty_print is truthy" do
56
+ let(:pretty_print){ 1 }
57
+
58
+ it "should set pretty_print to true" do
59
+ expect(subject.pretty_print).to be true
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#saving!" do
65
+ before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) }
66
+
67
+ context "when json_only is true with locale" do
68
+ let(:file){ "tmp/i18n-js/%{locale}.js" }
69
+ let(:json_only){ true }
70
+
71
+ it 'should output JSON files per locale' do
72
+ subject.save!
73
+ file_should_exist "en.js"
74
+ file_should_exist "fr.js"
75
+
76
+ expect(File.read(File.join(temp_path, "en.js"))).to eql(
77
+ %Q({"en":{"test":"Test"}})
78
+ )
79
+
80
+ expect(File.read(File.join(temp_path, "fr.js"))).to eql(
81
+ %Q({"fr":{"test":"Test2"}})
82
+ )
83
+ end
84
+ end
85
+
86
+ context "when json_only is true without locale" do
87
+ let(:file){ "tmp/i18n-js/segment.js" }
88
+ let(:json_only){ true }
89
+
90
+ it 'should output one JSON file for all locales' do
91
+ subject.save!
92
+ file_should_exist "segment.js"
93
+
94
+ expect(File.read(File.join(temp_path, "segment.js"))).to eql(
95
+ %Q({"en":{"test":"Test"},"fr":{"test":"Test2"}})
96
+ )
97
+ end
98
+ end
99
+
100
+ context "when json_only and pretty print are true" do
101
+ let(:file){ "tmp/i18n-js/segment.js" }
102
+ let(:json_only){ true }
103
+ let(:pretty_print){ true }
104
+
105
+ it 'should output one JSON file for all locales' do
106
+ subject.save!
107
+ file_should_exist "segment.js"
108
+
109
+ expect(File.read(File.join(temp_path, "segment.js"))).to eql <<-EOS
110
+ {
111
+ "en": {
112
+ "test": "Test"
113
+ },
114
+ "fr": {
115
+ "test": "Test2"
116
+ }
117
+ }
118
+ EOS
119
+ .chomp
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "#save!" do
125
+ before { allow(I18n::JS).to receive(:export_i18n_js_dir_path).and_return(temp_path) }
126
+ before { subject.save! }
127
+
128
+ context "when file does not include %{locale}" do
129
+ it "should write the file" do
130
+ file_should_exist "segment.js"
131
+
132
+ expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF
133
+ MyNamespace.translations || (MyNamespace.translations = {});
134
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"test":"Test"}'));
135
+ MyNamespace.translations["fr"] = I18n.extend((MyNamespace.translations["fr"] || {}), JSON.parse('{"test":"Test2"}'));
136
+ EOF
137
+ end
138
+ end
139
+
140
+ context "when file includes %{locale}" do
141
+ let(:file){ "tmp/i18n-js/%{locale}.js" }
142
+
143
+ it "should write files" do
144
+ file_should_exist "en.js"
145
+ file_should_exist "fr.js"
146
+
147
+ expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql <<-EOF
148
+ MyNamespace.translations || (MyNamespace.translations = {});
149
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"test":"Test"}'));
150
+ EOF
151
+
152
+ expect(File.open(File.join(temp_path, "fr.js")){|f| f.read}).to eql <<-EOF
153
+ MyNamespace.translations || (MyNamespace.translations = {});
154
+ MyNamespace.translations["fr"] = I18n.extend((MyNamespace.translations["fr"] || {}), JSON.parse('{"test":"Test2"}'));
155
+ EOF
156
+ end
157
+ end
158
+
159
+ context "when file includes single quote" do
160
+ let(:file){ "tmp/i18n-js/%{locale}.js" }
161
+ let(:translations){ { en: { "a" => "Test's" } } }
162
+
163
+ it "should write files" do
164
+ file_should_exist "en.js"
165
+
166
+ expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql <<-EOF
167
+ MyNamespace.translations || (MyNamespace.translations = {});
168
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"a":"Test\\'s"}'));
169
+ EOF
170
+ end
171
+ end
172
+
173
+ context "when file includes escaped double quote" do
174
+ let(:file){ "tmp/i18n-js/%{locale}.js" }
175
+ let(:translations){ { en: { "a" => 'say "hello"' } } }
176
+
177
+ it "should escape double quote" do
178
+ file_should_exist "en.js"
179
+
180
+ expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql <<-EOF
181
+ MyNamespace.translations || (MyNamespace.translations = {});
182
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"a":"say \\\\"hello\\\\""}'));
183
+ EOF
184
+ end
185
+ end
186
+
187
+ context "when file includes backslash in double quote" do
188
+ let(:file){ "tmp/i18n-js/%{locale}.js" }
189
+ let(:translations){ { en: { "double-backslash-in-double-quote" => '"\\\\"' } } }
190
+
191
+ it "should escape backslash" do
192
+ file_should_exist "en.js"
193
+
194
+ expect(File.open(File.join(temp_path, "en.js")){|f| f.read}).to eql <<-EOF
195
+ MyNamespace.translations || (MyNamespace.translations = {});
196
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"double-backslash-in-double-quote":"\\\\"\\\\\\\\\\\\\\\\\\\\""}'));
197
+ EOF
198
+ end
199
+ end
200
+
201
+ context "when js_extend is true" do
202
+ let(:js_extend){ true }
203
+
204
+ let(:translations){ { en: { "b" => "Test", "a" => "Test" } } }
205
+
206
+ it 'should output the keys as sorted' do
207
+ file_should_exist "segment.js"
208
+
209
+ expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF
210
+ MyNamespace.translations || (MyNamespace.translations = {});
211
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"a":"Test","b":"Test"}'));
212
+ EOF
213
+ end
214
+ end
215
+
216
+ context "when js_extend is false" do
217
+ let(:js_extend){ false }
218
+
219
+ let(:translations){ { en: { "b" => "Test", "a" => "Test" } } }
220
+
221
+ it 'should output the keys as sorted' do
222
+ file_should_exist "segment.js"
223
+
224
+ expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF
225
+ MyNamespace.translations || (MyNamespace.translations = {});
226
+ MyNamespace.translations["en"] = JSON.parse('{"a":"Test","b":"Test"}');
227
+ EOF
228
+ end
229
+ end
230
+
231
+ context "when sort_translation_keys is true" do
232
+ let(:sort_translation_keys){ true }
233
+
234
+ let(:translations){ { en: { "b" => "Test", "a" => "Test" } } }
235
+
236
+ it 'should output the keys as sorted' do
237
+ file_should_exist "segment.js"
238
+
239
+ expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF
240
+ MyNamespace.translations || (MyNamespace.translations = {});
241
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"a":"Test","b":"Test"}'));
242
+ EOF
243
+ end
244
+ end
245
+
246
+ context "when sort_translation_keys is false" do
247
+ let(:sort_translation_keys){ false }
248
+
249
+ let(:translations){ { en: { "b" => "Test", "a" => "Test" } } }
250
+
251
+ it 'should output the keys as sorted' do
252
+ file_should_exist "segment.js"
253
+
254
+ expect(File.open(File.join(temp_path, "segment.js")){|f| f.read}).to eql <<-EOF
255
+ MyNamespace.translations || (MyNamespace.translations = {});
256
+ MyNamespace.translations["en"] = I18n.extend((MyNamespace.translations["en"] || {}), JSON.parse('{"b":"Test","a":"Test"}'));
257
+ EOF
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,106 @@
1
+ require "spec_helper"
2
+
3
+ describe I18n::JS::Utils do
4
+
5
+ describe ".strip_keys_with_nil_values" do
6
+ subject { described_class.strip_keys_with_nil_values(input_hash) }
7
+
8
+ context 'when input_hash does NOT contain nil value' do
9
+ let(:input_hash) { {a: 1, b: { c: 2 }} }
10
+ let(:expected_hash) { input_hash }
11
+
12
+ it 'returns the original input' do
13
+ is_expected.to eq expected_hash
14
+ end
15
+ end
16
+ context 'when input_hash does contain nil value' do
17
+ let(:input_hash) { {a: 1, b: { c: 2, d: nil }, e: { f: nil }} }
18
+ let(:expected_hash) { {a: 1, b: { c: 2 }, e: {}} }
19
+
20
+ it 'returns the original input with nil values removed' do
21
+ is_expected.to eq expected_hash
22
+ end
23
+ end
24
+ end
25
+
26
+ context "hash merging" do
27
+ it "performs a deep merge" do
28
+ target = {:a => {:b => 1}}
29
+ result = described_class.deep_merge(target, {:a => {:c => 2}})
30
+
31
+ expect(result[:a]).to eql({:b => 1, :c => 2})
32
+ end
33
+
34
+ it "performs a banged deep merge" do
35
+ target = {:a => {:b => 1}}
36
+ described_class.deep_merge!(target, {:a => {:c => 2}})
37
+
38
+ expect(target[:a]).to eql({:b => 1, :c => 2})
39
+ end
40
+ end
41
+
42
+ describe ".deep_reject" do
43
+ it "performs a deep keys rejection" do
44
+ hash = {:a => {:b => 1}}
45
+
46
+ result = described_class.deep_reject(hash) { |k, v| k == :b }
47
+
48
+ expect(result).to eql({:a => {}})
49
+ end
50
+
51
+ it "performs a deep keys rejection prunning the whole tree if necessary" do
52
+ hash = {:a => {:b => {:c => {:d => 1, :e => 2}}}}
53
+
54
+ result = described_class.deep_reject(hash) { |k, v| k == :b }
55
+
56
+ expect(result).to eql({:a => {}})
57
+ end
58
+
59
+
60
+ it "performs a deep keys rejection without changing the original hash" do
61
+ hash = {:a => {:b => 1, :c => 2}}
62
+
63
+ result = described_class.deep_reject(hash) { |k, v| k == :b }
64
+
65
+ expect(result).to eql({:a => {:c => 2}})
66
+ expect(hash).to eql({:a => {:b => 1, :c => 2}})
67
+ end
68
+ end
69
+
70
+ describe ".deep_key_sort" do
71
+ let(:unsorted_hash) { {:z => {:b => 1, :a => 2}, :y => 3} }
72
+ subject(:sorting) { described_class.deep_key_sort(unsorted_hash) }
73
+
74
+ it "performs a deep keys sort without changing the original hash" do
75
+ should eql({:y => 3, :z => {:a => 2, :b => 1}})
76
+ expect(unsorted_hash).to eql({:z => {:b => 1, :a => 2}, :y => 3})
77
+ end
78
+
79
+ # Idea from gem `rails_admin`
80
+ context "when hash contain non-Symbol as key" do
81
+ let(:unsorted_hash) { {:z => {1 => 1, true => 2}, :y => 3} }
82
+
83
+ it "performs a deep keys sort without error" do
84
+ expect{ sorting }.to_not raise_error
85
+ end
86
+ it "converts keys to symbols" do
87
+ should eql({:y => 3, :z => {1 => 1, true => 2}})
88
+ end
89
+ end
90
+ end
91
+
92
+ describe ".scopes_match?" do
93
+ it "performs a comparison of literal scopes" do
94
+ expect(described_class.scopes_match?([:a, :b], [:a, :b, :c])).to_not eql true
95
+ expect(described_class.scopes_match?([:a, :b, :c], [:a, :b, :c])).to eql true
96
+ expect(described_class.scopes_match?([:a, :b, :c], [:a, :b, :d])).to_not eql true
97
+ end
98
+
99
+ it "performs a comparison of wildcard scopes" do
100
+ expect(described_class.scopes_match?([:a, '*'], [:a, :b, :c])).to_not eql true
101
+ expect(described_class.scopes_match?([:a, '*', :c], [:a, :b, :c])).to eql true
102
+ expect(described_class.scopes_match?([:a, :b, :c], [:a, '*', :c])).to eql true
103
+ expect(described_class.scopes_match?([:a, :b, :c], [:a, '*', '*'])).to eql true
104
+ end
105
+ end
106
+ end