invoca-utils 0.4.0 → 0.5.0
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 +4 -4
- data/.github/workflows/pipeline.yml +20 -0
- data/.gitignore +1 -3
- data/.rspec +3 -0
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Appraisals +13 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +9 -16
- data/Gemfile.lock +39 -48
- data/Rakefile +9 -6
- data/gemfiles/activesupport_5.gemfile +12 -0
- data/gemfiles/activesupport_5.gemfile.lock +58 -0
- data/gemfiles/activesupport_6.gemfile +12 -0
- data/gemfiles/activesupport_6.gemfile.lock +59 -0
- data/gemfiles/activesupport_7.gemfile +12 -0
- data/gemfiles/activesupport_7.gemfile.lock +57 -0
- data/invoca-utils.gemspec +18 -6
- data/lib/invoca/utils/exceptions.rb +5 -3
- data/lib/invoca/utils/version.rb +1 -1
- data/{test → spec}/helpers/constant_overrides.rb +0 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/unit/array_spec.rb +20 -0
- data/spec/unit/enumerable_spec.rb +80 -0
- data/{test/unit/exceptions_test.rb → spec/unit/exceptions_spec.rb} +17 -17
- data/spec/unit/guaranteed_utf8_string_spec.rb +260 -0
- data/spec/unit/hash_spec.rb +81 -0
- data/spec/unit/hash_with_indifferent_access_spec.rb +100 -0
- data/spec/unit/map_compact_spec.rb +25 -0
- data/{test/unit/module_test.rb → spec/unit/module_spec.rb} +4 -4
- data/spec/unit/multi_sender_spec.rb +54 -0
- data/{test/unit/stable_sort_test.rb → spec/unit/stable_sort_spec.rb} +14 -14
- data/spec/unit/time_calculations_spec.rb +39 -0
- data/{test/unit/utils_test.rb → spec/unit/utils_spec.rb} +14 -14
- metadata +59 -37
- data/.jenkins/Jenkinsfile +0 -50
- data/.jenkins/ruby_build_pod.yml +0 -19
- data/test/test_helper.rb +0 -14
- data/test/unit/array_test.rb +0 -20
- data/test/unit/enumerable_test.rb +0 -80
- data/test/unit/guaranteed_utf8_string_test.rb +0 -263
- data/test/unit/hash_test.rb +0 -81
- data/test/unit/hash_with_indifferent_access_test.rb +0 -100
- data/test/unit/map_compact_test.rb +0 -25
- data/test/unit/multi_sender_test.rb +0 -56
- data/test/unit/time_calculations_test.rb +0 -39
@@ -1,23 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../
|
3
|
+
require_relative '../spec_helper'
|
4
4
|
|
5
|
-
|
5
|
+
describe Invoca::Utils do
|
6
6
|
context "exceptions" do
|
7
7
|
context ".retry_on_exception" do
|
8
|
-
|
8
|
+
it "default retries: to 1" do
|
9
9
|
times = 0
|
10
10
|
tries = []
|
11
11
|
result = Invoca::Utils.retry_on_exception(ArgumentError) do |try|
|
12
12
|
tries << try
|
13
13
|
times += 1
|
14
14
|
end
|
15
|
-
|
16
|
-
|
15
|
+
expect(result).to eq(1)
|
16
|
+
expect(tries).to eq([0])
|
17
17
|
end
|
18
18
|
|
19
19
|
context "when never raising an exception" do
|
20
|
-
|
20
|
+
it "return result" do
|
21
21
|
times = 0
|
22
22
|
tries = []
|
23
23
|
result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 2) do |try|
|
@@ -26,26 +26,26 @@ class ExceptionsTest < Minitest::Test
|
|
26
26
|
try == 0 and raise ArgumentError, '!!!'
|
27
27
|
times
|
28
28
|
end
|
29
|
-
|
30
|
-
|
29
|
+
expect(result).to eq(2)
|
30
|
+
expect(tries).to eq([0,1])
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
context "when always raising an exception" do
|
35
|
-
|
35
|
+
it "retry and finally raise" do
|
36
36
|
tries = []
|
37
|
-
|
37
|
+
expect do
|
38
38
|
Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do |try|
|
39
39
|
tries << try
|
40
40
|
raise ArgumentError, "!!! #{try + 1}"
|
41
41
|
end
|
42
|
-
end
|
43
|
-
|
42
|
+
end.to raise_exception(ArgumentError, /!!! 2/)
|
43
|
+
expect(tries).to eq([0,1])
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
context "when raising but then succeeding" do
|
48
|
-
|
48
|
+
it "retry and finally return result" do
|
49
49
|
times = 0
|
50
50
|
result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do
|
51
51
|
times += 1
|
@@ -55,12 +55,12 @@ class ExceptionsTest < Minitest::Test
|
|
55
55
|
times
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
58
|
+
expect(result).to eq(2)
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
context "when raising different exceptions (array notation) but then succeeding" do
|
63
|
-
|
63
|
+
it "retry and finally return result" do
|
64
64
|
times = 0
|
65
65
|
tries = []
|
66
66
|
result = Invoca::Utils.retry_on_exception([ArgumentError, RuntimeError], retries: 2) do |try|
|
@@ -75,8 +75,8 @@ class ExceptionsTest < Minitest::Test
|
|
75
75
|
times
|
76
76
|
end
|
77
77
|
end
|
78
|
-
|
79
|
-
|
78
|
+
expect(result).to eq(3)
|
79
|
+
expect(tries).to eq([0, 1, 2])
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/invoca/utils/guaranteed_utf8_string'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
describe Invoca::Utils::GuaranteedUTF8String do
|
7
|
+
class HasNoTo_sMethod
|
8
|
+
undef :to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
class BasicObjectWithKernelMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
class ConvertibleToString
|
15
|
+
attr_reader :to_s
|
16
|
+
|
17
|
+
def initialize(string)
|
18
|
+
@to_s = string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context '.normalize_string' do
|
23
|
+
it 'raise an error if called with an object with no to_s method' do
|
24
|
+
expect do
|
25
|
+
Invoca::Utils::GuaranteedUTF8String.normalize_string(HasNoTo_sMethod.new)
|
26
|
+
end.to raise_exception(ArgumentError, /must be passed a string or an object with a non-Kernel \.to_s method but instead was HasNoTo_sMethod/)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'raise an error if called with a basic Ruby object' do
|
30
|
+
expect do
|
31
|
+
Invoca::Utils::GuaranteedUTF8String.normalize_string(BasicObjectWithKernelMethods.new)
|
32
|
+
end.to raise_exception(ArgumentError,
|
33
|
+
/must be passed a string or an object with a non-Kernel \.to_s method but instead was BasicObjectWithKernelMethods/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'not mutate the original string' do
|
37
|
+
ascii_string = 'new string'.encode('ASCII')
|
38
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(ascii_string)
|
39
|
+
|
40
|
+
expect(encoded_string).to eq(ascii_string)
|
41
|
+
expect(ascii_string.encoding).to eq(Encoding::ASCII)
|
42
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'return UTF-8 encoded string' do
|
46
|
+
original_string = "this,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n"
|
47
|
+
|
48
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string)
|
49
|
+
|
50
|
+
expect(encoded_string).to eq(original_string)
|
51
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
52
|
+
end
|
53
|
+
|
54
|
+
context "normalize_utf16" do
|
55
|
+
UTF16_LE_BOM = "\xFF\xFE"
|
56
|
+
UTF16_BE_BOM = "\xFE\xFF"
|
57
|
+
UTF16_LE_TEST_STRING = (UTF16_LE_BOM + "v\x00a\x00l\x00i\x00d\x00,\x00u\x00t\x00f\x00-\x001\x006\x00\n\x00s\x00e\x00c\x00o\x00n\x00d\x00").force_encoding('BINARY').freeze
|
58
|
+
UTF16_BE_TEST_STRING = (UTF16_BE_BOM + "\x00v\x00a\x00l\x00i\x00d\x00,\x00u\x00t\x00f\x00-\x001\x006\x00\n\x00s\x00e\x00c\x00o\x00n\x00d").force_encoding('BINARY').freeze
|
59
|
+
|
60
|
+
it 'accept UTF-16LE in BINARY and return UTF-8 encoded string when true' do
|
61
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_LE_TEST_STRING, normalize_utf16: true)
|
62
|
+
|
63
|
+
expect(encoded_string).to eq("valid,utf-16\nsecond")
|
64
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'not check for UTF-16LE in BINARY and return UTF-8 encoded string when false' do
|
68
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_LE_TEST_STRING, normalize_utf16: false)
|
69
|
+
expected = "ÿþv\u0000a\u0000l\u0000i\u0000d\u0000,\u0000u\u0000t\u0000f\u0000-\u00001\u00006\u0000\n\u0000s\u0000e\u0000c\u0000o\u0000n\u0000d\u0000"
|
70
|
+
|
71
|
+
expect(encoded_string).to eq(expected)
|
72
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'accept UTF-16BE in BINARY and return UTF-8 encoded string when true' do
|
76
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_BE_TEST_STRING, normalize_utf16: true)
|
77
|
+
|
78
|
+
expect(encoded_string).to eq("valid,utf-16\nsecond")
|
79
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'not check for UTF-16BE in BINARY and return UTF-8 encoded string when false' do
|
83
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(UTF16_BE_TEST_STRING, normalize_utf16: false)
|
84
|
+
expected = "þÿ\u0000v\u0000a\u0000l\u0000i\u0000d\u0000,\u0000u\u0000t\u0000f\u0000-\u00001\u00006\u0000\n\u0000s\u0000e\u0000c\u0000o\u0000n\u0000d"
|
85
|
+
|
86
|
+
expect(encoded_string).to eq(expected)
|
87
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
88
|
+
end
|
89
|
+
|
90
|
+
context "containing embedded CP1252" do
|
91
|
+
it 'accept UTF-16LE in BINARY and return UTF-8 encoded string with "private" CP1252 when normalize_utf16: true, normalize_cp1252: false' do
|
92
|
+
original_string = (UTF16_LE_BOM + "\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d\x00").force_encoding('BINARY')
|
93
|
+
|
94
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true, normalize_cp1252: false)
|
95
|
+
|
96
|
+
expect(encoded_string).to eq("\u0091smart quotes\u0092\nsecond")
|
97
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'accept UTF-16LE in BINARY and return UTF-8 encoded string with normalized CP1252 when normalize_utf16: true, normalize_cp1252: true' do
|
101
|
+
original_string = (UTF16_LE_BOM + "\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d\x00").force_encoding('BINARY')
|
102
|
+
|
103
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true)
|
104
|
+
|
105
|
+
expect(encoded_string).to eq("‘smart quotes’\nsecond")
|
106
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'accept UTF-16BE in BINARY and return UTF-8 encoded string when normalize_utf16: true, normalize_cp1252: false' do
|
110
|
+
original_string = (UTF16_BE_BOM + "\x00\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d").force_encoding('BINARY')
|
111
|
+
|
112
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true, normalize_cp1252: false)
|
113
|
+
|
114
|
+
expect(encoded_string).to eq("\u0091smart quotes\u0092\nsecond")
|
115
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'accept UTF-16BE in BINARY and return UTF-8 encoded string when normalize_utf16: true, normalize_cp1252: true' do
|
119
|
+
original_string = (UTF16_BE_BOM + "\x00\x91\x00s\x00m\x00a\x00r\x00t\x00 \x00q\x00u\x00o\x00t\x00e\x00s\x00\x92\x00\n\x00s\x00e\x00c\x00o\x00n\x00d").force_encoding('BINARY')
|
120
|
+
|
121
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(original_string, normalize_utf16: true, normalize_cp1252: true)
|
122
|
+
|
123
|
+
expect(encoded_string).to eq("‘smart quotes’\nsecond")
|
124
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'normalize_cp1252' do
|
130
|
+
before do
|
131
|
+
@string = "This,is,NOT,a,valid,utf-8,csv,string\r\none,two,three,four,\x81five,\x91smart quotes\x92,\x93suck!\x94\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'raise ArgumentError when false' do
|
135
|
+
expect do
|
136
|
+
Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, normalize_cp1252: false)
|
137
|
+
end.to raise_exception(ArgumentError)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'return UTF-8 encoded string after falling back to CP1252 encoding when true' do
|
141
|
+
expected_string = "This,is,NOT,a,valid,utf-8,csv,string\none,two,three,four,~five,‘smart quotes’,“suck!”\n"
|
142
|
+
|
143
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string)
|
144
|
+
|
145
|
+
expect(encoded_string).to eq(expected_string)
|
146
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
147
|
+
end
|
148
|
+
|
149
|
+
it "encode all 255 UTF-8 characters, returning ~ when the character isn't mapped in CP1252" do
|
150
|
+
all_8_bit_characters = (1..255).map(&:chr).join
|
151
|
+
|
152
|
+
final_utf_8_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(all_8_bit_characters)
|
153
|
+
expected_string = "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000A\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007F€~‚ƒ„…†‡ˆ‰Š‹Œ~Ž~~‘’“”•–—˜™š›œ~žŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
|
154
|
+
|
155
|
+
expect(final_utf_8_string).to eq(expected_string)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'normalize_newlines' do
|
160
|
+
before do
|
161
|
+
@string = "This string\n\n\n has line feeds\ncarriage\r\r returns\rand Windows\r\n\r\n new line chars\r\nend of \n\r\r\r\nstring"
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'return UTF-8 encoded string without normalized return chars when false' do
|
165
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, normalize_newlines: false)
|
166
|
+
|
167
|
+
expect(encoded_string).to eq(@string)
|
168
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'return UTF-8 encoded string with normalized return chars when true' do
|
172
|
+
expected_string = "This string\n\n\n has line feeds\ncarriage\n\n returns\nand Windows\n\n new line chars\nend of \n\n\n\nstring"
|
173
|
+
|
174
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, normalize_newlines: true)
|
175
|
+
|
176
|
+
expect(encoded_string).to eq(expected_string)
|
177
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'remove_utf8_bom' do
|
182
|
+
before do
|
183
|
+
@original_string = "\xEF\xBB\xBFthis,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n"
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'return UTF-8 encoded string with BOM intact when false' do
|
187
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@original_string, remove_utf8_bom: false)
|
188
|
+
|
189
|
+
expect(encoded_string).to eq("\xEF\xBB\xBFthis,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n")
|
190
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'return UTF-8 encoded string without BOM when true' do
|
194
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@original_string, remove_utf8_bom: true)
|
195
|
+
|
196
|
+
expect(encoded_string).to eq("this,is,a,valid,utf-8,csv,string\none,two,three,four,five,six,seven\n")
|
197
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'replace_unicode_beyond_ffff' do
|
202
|
+
before do
|
203
|
+
@string = "This string has some ✓ valid UTF-8 but also some 😹 emoji \xf0\x9f\x98\xb9 that are > U+FFFF"
|
204
|
+
end
|
205
|
+
|
206
|
+
it "consider UTF-8 code points that take > 3 bytes (above U+FFFF) to be invalid (since MySQL can't store them unless column is declared mb4) and encode them as ~ when false" do
|
207
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, replace_unicode_beyond_ffff: false)
|
208
|
+
|
209
|
+
expect(encoded_string).to eq(@string)
|
210
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "consider UTF-8 code points that take > 3 bytes (above U+FFFF) to be invalid (since MySQL can't store them unless column is declared mb4) and encode them as ~ when true" do
|
214
|
+
expected_string = 'This string has some ✓ valid UTF-8 but also some ~ emoji ~ that are > U+FFFF'
|
215
|
+
|
216
|
+
encoded_string = Invoca::Utils::GuaranteedUTF8String.normalize_string(@string, replace_unicode_beyond_ffff: true)
|
217
|
+
|
218
|
+
expect(encoded_string).to eq(expected_string)
|
219
|
+
expect(encoded_string.encoding).to eq(Encoding::UTF_8)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context ".normalize_strings" do
|
224
|
+
it "walk json doc, replacing strings in: values, inside array elements, and hash keys and values" do
|
225
|
+
json_doc = {
|
226
|
+
'😹' => "\xE2\x9C\x93 laughing cat",
|
227
|
+
['😹'] => ["\xE2", "\xf0\x9f\x98\xb9", { "newline" => "\r\n" }],
|
228
|
+
'cp1252' => "\x91smart quotes\x92"
|
229
|
+
}
|
230
|
+
|
231
|
+
normalized_json = Invoca::Utils::GuaranteedUTF8String.normalize_all_strings(json_doc,
|
232
|
+
normalize_utf16: true,
|
233
|
+
normalize_cp1252: true,
|
234
|
+
normalize_newlines: true,
|
235
|
+
remove_utf8_bom: true,
|
236
|
+
replace_unicode_beyond_ffff: true)
|
237
|
+
|
238
|
+
expect({
|
239
|
+
'~' => "✓ laughing cat",
|
240
|
+
['~'] => ["â", "~", { "newline" => "\n" }],
|
241
|
+
'cp1252' => "‘smart quotes’"
|
242
|
+
}).to eq(normalized_json)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'constructor' do
|
248
|
+
it 'call normalize_string with the default conversions' do
|
249
|
+
expect(Invoca::Utils::GuaranteedUTF8String).to receive(:normalize_string).with('')
|
250
|
+
|
251
|
+
Invoca::Utils::GuaranteedUTF8String.new('').to_string
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'do the same when using to_s alias' do
|
255
|
+
expect(Invoca::Utils::GuaranteedUTF8String).to receive(:normalize_string).with('')
|
256
|
+
|
257
|
+
Invoca::Utils::GuaranteedUTF8String.new('').to_s
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/invoca/utils/hash.rb'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
describe Hash do
|
7
|
+
|
8
|
+
context 'select_hash' do
|
9
|
+
it 'return a hash containing key/values identified by the block' do
|
10
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.select_hash { |key, value| key < value }).to eq({ 1 => 2, 3 => 4 })
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'handle blocks that only check values' do
|
14
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.select_hash { |value| value != 2 }).to eq({ 3 => 4, 6 => 5 })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'map_hash' do
|
19
|
+
it 'return a hash containing values updated by the block' do
|
20
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.map_hash { |key, value| key < value }).to eq({ 1 => true, 3 => true, 6 => false })
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handle blocks that only receive values' do
|
24
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 }.map_hash { |value| value * 2 }).to eq({ 1 => 4, 3 => 8, 6 => 10 })
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'partition_hash' do
|
29
|
+
it 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
|
30
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([1, 3])).to eq([{ 1 => 2, 3 => 4 }, { 6 => 5 }])
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
|
34
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |key, value| key < value }).to eq([{ 1 => 2, 3 => 4 }, { 6 => 5 }])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'handle no matches' do
|
38
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([100])).to eq([{}, { 1 => 2, 3 => 4, 6 => 5 }])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'handle all matches' do
|
42
|
+
expect( { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |_key, _value| true }).to eq([{ 1 => 2, 3 => 4, 6 => 5 }, {}])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '- operator' do
|
47
|
+
it 'return a hash with pairs removed that match the keys in rhs array' do
|
48
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - [1, 6]).to eq({ 3 => 4 })
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'handle empty rhs array' do
|
52
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - []).to eq({ 1 => 2, 3 => 4, 6 => 5 })
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'handle no matches in rhs array' do
|
56
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - [100, 600]).to eq({ 1 => 2, 3 => 4, 6 => 5 })
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'handle all matches in rhs array' do
|
60
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } - [1, 3, 6]).to eq({})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context '& operator' do
|
65
|
+
it 'return a hash with pairs removed that do NOT match the keys in rhs array' do
|
66
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & [1, 6]).to eq({ 1 => 2, 6 => 5 })
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'handle empty rhs array' do
|
70
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & []).to eq({})
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'handle no matches in rhs array' do
|
74
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & [100, 600]).to eq({})
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'handle all matches in rhs array' do
|
78
|
+
expect({ 1 => 2, 3 => 4, 6 => 5 } & [1, 3, 6]).to eq({ 1 => 2, 3 => 4, 6 => 5 })
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/hash_with_indifferent_access'
|
4
|
+
require_relative '../../lib/invoca/utils/hash_with_indifferent_access.rb'
|
5
|
+
require_relative '../spec_helper'
|
6
|
+
|
7
|
+
describe HashWithIndifferentAccess do
|
8
|
+
|
9
|
+
context 'partition_hash' do
|
10
|
+
before do
|
11
|
+
@hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
|
15
|
+
expect(@hash_to_test.partition_hash(['one', 'three'])).to eq([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }])
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
|
19
|
+
expect(@hash_to_test.partition_hash { |key, _value| ['one', 'three'].include?(key) }).to eq([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }])
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'handle no matches' do
|
23
|
+
expect(@hash_to_test.partition_hash([:not_found])).to eq([{}, @hash_to_test])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'handle all matches' do
|
27
|
+
expect(@hash_to_test.partition_hash { |_key, _value| true }).to eq([@hash_to_test, {}])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'handle symbols for key matching' do
|
31
|
+
expect(@hash_to_test.partition_hash([:one, :three])).to eq([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }])
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'return HashWithIndifferentAccess objects' do
|
35
|
+
matched, unmatched = @hash_to_test.partition_hash([:one, :three])
|
36
|
+
expect(matched.is_a?(HashWithIndifferentAccess)).to be_truthy
|
37
|
+
expect(unmatched.is_a?(HashWithIndifferentAccess)).to be_truthy
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context '- operator' do
|
42
|
+
before do
|
43
|
+
@hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'return a hash with pairs removed that match the keys in rhs array' do
|
47
|
+
expect(@hash_to_test - ['one', 'six']).to eq({ 'three' => 4 })
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'handle empty rhs array' do
|
51
|
+
expect(@hash_to_test - []).to eq(@hash_to_test)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'handle no matches in rhs array' do
|
55
|
+
expect(@hash_to_test - ['100', '600']).to eq(@hash_to_test)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'handle all matches in rhs array' do
|
59
|
+
expect(@hash_to_test - ['one', 'three', 'six']).to eq({})
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'handle symbols for key matching' do
|
63
|
+
expect(@hash_to_test - [:one, :three]).to eq({ 'six' => 5 })
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'return HashWithIndifferentAccess object' do
|
67
|
+
expect((@hash_to_test - [:one, :three]).is_a?(HashWithIndifferentAccess)).to be_truthy
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context '& operator' do
|
72
|
+
before do
|
73
|
+
@hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'return a hash with pairs removed that do NOT match the keys in rhs array' do
|
77
|
+
expect(@hash_to_test & ['one', 'six']).to eq({ 'one' => 2, 'six' => 5 })
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'handle empty rhs array' do
|
81
|
+
expect(@hash_to_test & []).to eq({})
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'handle no matches in rhs array' do
|
85
|
+
expect(@hash_to_test & ['100', '600']).to eq({})
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'handle all matches in rhs array' do
|
89
|
+
expect(@hash_to_test & ['one', 'three', 'six']).to eq(@hash_to_test)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'handle symbols for key matching' do
|
93
|
+
expect(@hash_to_test & [:one, :three]).to eq({ 'one' => 2, 'three' => 4 })
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'return HashWithIndifferentAccess object' do
|
97
|
+
expect((@hash_to_test & [:one, :three]).is_a?(HashWithIndifferentAccess)).to be_truthy
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
describe Enumerable do
|
6
|
+
it "map_compact" do
|
7
|
+
expect([1, 2, nil, 3, 4].map_compact { |item| item**2 if (nil == item ? nil : item.odd?) }).to eq([1, 9])
|
8
|
+
end
|
9
|
+
|
10
|
+
it "map_compact to empty if nothing matches" do
|
11
|
+
expect({:a => 'aaa', :b => 'bbb'}.map_compact { |key, value| value if key == :c }).to eq([])
|
12
|
+
end
|
13
|
+
|
14
|
+
it "map_compact a hash" do
|
15
|
+
expect({:a => 'aaa', :b => 'bbb'}.map_compact { |key, value| value if key == :b }).to eq(['bbb'])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "map_compact empty collection" do
|
19
|
+
expect([].map_compact { |item| true }).to eq([])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "not map_compact false" do
|
23
|
+
expect([nil, false].map_compact { |a| a }).to eq([false])
|
24
|
+
end
|
25
|
+
end
|
@@ -3,9 +3,9 @@
|
|
3
3
|
# must require active_support's alias_method_chain first, to ensure that our module monkey patches it
|
4
4
|
require 'active_support/core_ext/module/aliasing'
|
5
5
|
require_relative '../../lib/invoca/utils/module.rb'
|
6
|
-
require_relative '../
|
6
|
+
require_relative '../spec_helper'
|
7
7
|
|
8
|
-
|
8
|
+
describe Module do
|
9
9
|
class NumberFun
|
10
10
|
def self.around_filter(around_method, method_names)
|
11
11
|
method_names.each do |meth|
|
@@ -32,8 +32,8 @@ class ModuleTest < Minitest::Test
|
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'alias_method_chain' do
|
35
|
-
|
36
|
-
|
35
|
+
it 'not cause infinite recursion when double aliasing the same method' do
|
36
|
+
expect(NumberFun.new.number_printer(3)).to eq(4)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../lib/invoca/utils/multi_sender.rb'
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
describe Invoca::Utils::MultiSender do
|
7
|
+
# create enumerable class for testing
|
8
|
+
class LinkedList
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def initialize(head, tail = nil)
|
12
|
+
@head, @tail = head, tail
|
13
|
+
end
|
14
|
+
|
15
|
+
def <<(item)
|
16
|
+
LinkedList.new(item, self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
[@head, @tail].inspect
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
if block_given?
|
25
|
+
block.call(@head)
|
26
|
+
@tail&.each(&block)
|
27
|
+
else
|
28
|
+
to_enum(:each)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'with custom Enumerable' do
|
34
|
+
before do
|
35
|
+
linked_list = LinkedList.new('some') << 'short' << 'words'
|
36
|
+
@multi_sender = Invoca::Utils::MultiSender.new(linked_list, :map)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'call the same method on each item in an Enumerable and return the results as an array' do
|
40
|
+
expect(@multi_sender.length).to eq([5, 5, 4])
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'handle methods with arguments' do
|
44
|
+
expect(@multi_sender.slice(1, 2)).to eq(['or', 'ho', 'om'])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with built-in Array' do
|
49
|
+
it 'call the same method on each item in an Array and return the results as an array' do
|
50
|
+
multi_sender = Invoca::Utils::MultiSender.new(['some', 'short', 'words'], :map)
|
51
|
+
expect(multi_sender.length).to eq([4, 5, 5])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|