shrimple 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +125 -0
- data/Rakefile +7 -0
- data/lib/render.js +68 -0
- data/lib/shrimple.rb +120 -0
- data/lib/shrimple/default_config.rb +114 -0
- data/lib/shrimple/phantom.rb +108 -0
- data/lib/shrimple/process.rb +131 -0
- data/lib/shrimple/process_monitor.rb +84 -0
- data/shrimple.gemspec +21 -0
- data/spec/parse_and_print_stdin.js +10 -0
- data/spec/shrimple/phantom_spec.rb +93 -0
- data/spec/shrimple/process_monitor_spec.rb +69 -0
- data/spec/shrimple/process_spec.rb +85 -0
- data/spec/shrimple_long_spec.rb +205 -0
- data/spec/shrimple_spec.rb +119 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/syntax_error.js +3 -0
- data/spec/test_file.html +6 -0
- metadata +131 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
# the other Shrimple specs exercise this class pretty well
|
5
|
+
# it would take a lot of mocking and stubbing to do it here too.
|
6
|
+
|
7
|
+
|
8
|
+
describe Shrimple::ProcessMonitor do
|
9
|
+
it "will add a process" do
|
10
|
+
processes = Shrimple::ProcessMonitor.new(1)
|
11
|
+
# this should not raise an exception
|
12
|
+
# not using expect(...).not_to raise_exception since that eats all raised expections.
|
13
|
+
processes._add(Object.new)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "won't launch too many processes" do
|
17
|
+
processes = Shrimple::ProcessMonitor.new(0)
|
18
|
+
expect { processes._add(Object.new) }.to raise_exception(Shrimple::TooManyProcessesError)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can disable the process counter" do
|
22
|
+
processes = Shrimple::ProcessMonitor.new(-1)
|
23
|
+
processes._add(Object.new)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "counts and kills multiple processes" do
|
27
|
+
expect(Shrimple.processes.count).to eq 0
|
28
|
+
process = Shrimple::Process.new(['sleep', '20'], StringIO.new, StringIO.new, StringIO.new)
|
29
|
+
process = Shrimple::Process.new(['sleep', '20'], StringIO.new, StringIO.new, StringIO.new)
|
30
|
+
process = Shrimple::Process.new(['sleep', '20'], StringIO.new, StringIO.new, StringIO.new)
|
31
|
+
process = Shrimple::Process.new(['sleep', '20'], StringIO.new, StringIO.new, StringIO.new)
|
32
|
+
expect(Shrimple.processes.count).to eq 4
|
33
|
+
Shrimple.processes.first.kill
|
34
|
+
expect(Shrimple.processes.count).to eq 3
|
35
|
+
# can't use Array#each since calling delete in the block causes it to screw up
|
36
|
+
Shrimple.processes.kill_all
|
37
|
+
expect(Shrimple.processes.count).to eq 0
|
38
|
+
end
|
39
|
+
|
40
|
+
it "waits for multiple processes" do
|
41
|
+
expect(Shrimple.processes.count).to eq 0
|
42
|
+
# these sleep durations might be too small, depends on machine load and scheduling.
|
43
|
+
# if you're seeing threads finishing in the wrong order, try increasing them 10X.
|
44
|
+
process1 = Shrimple::Process.new(['sleep', '.3'], StringIO.new, StringIO.new, StringIO.new)
|
45
|
+
process2 = Shrimple::Process.new(['sleep', '.1'], StringIO.new, StringIO.new, StringIO.new)
|
46
|
+
process3 = Shrimple::Process.new(['sleep', '.2'], StringIO.new, StringIO.new, StringIO.new)
|
47
|
+
expect(Shrimple.processes.count).to eq 3
|
48
|
+
|
49
|
+
child = Shrimple.processes.wait_next
|
50
|
+
expect(child).to eq process2
|
51
|
+
expect(child.finished?).to eq true
|
52
|
+
expect(child.success?).to eq true
|
53
|
+
expect(Shrimple.processes.count).to eq 2
|
54
|
+
|
55
|
+
child = Shrimple.processes.wait_next
|
56
|
+
expect(child).to eq process3
|
57
|
+
expect(Shrimple.processes.count).to eq 1
|
58
|
+
|
59
|
+
child = Shrimple.processes.wait_next
|
60
|
+
expect(child).to eq process1
|
61
|
+
expect(Shrimple.processes.count).to eq 0
|
62
|
+
end
|
63
|
+
|
64
|
+
it "handles waiting for zero processes" do
|
65
|
+
expect {
|
66
|
+
child = Shrimple.processes.wait_next
|
67
|
+
}.to raise_exception(ThreadsWait::ErrNoWaitingThread)
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# run this to ensure there are no deadlock / process synchronization problems:
|
4
|
+
# while rspec spec/shrimple_process_spec.rb ; do echo -n ; done
|
5
|
+
|
6
|
+
describe Shrimple::Process do
|
7
|
+
let(:chin) { StringIO.new('small instring') }
|
8
|
+
let(:chout) { StringIO.new }
|
9
|
+
let(:cherr) { StringIO.new }
|
10
|
+
|
11
|
+
it "has a working drain method" do
|
12
|
+
bigin = StringIO.new('x' * 1024 * 1024) # at least 1 MB of data to test drain loop
|
13
|
+
process = Shrimple::Process.new('cat', bigin, chout, cherr)
|
14
|
+
process.stop
|
15
|
+
expect(chout.string).to eq bigin.string
|
16
|
+
expect(process.finished?).to eq true
|
17
|
+
end
|
18
|
+
|
19
|
+
it "waits until a sleeping command is finished" do
|
20
|
+
# pile a bunch of checks into this test so we only have to sleep once
|
21
|
+
expect(Shrimple.processes.count).to eq 0
|
22
|
+
claimed = nil
|
23
|
+
|
24
|
+
elapsed = time do
|
25
|
+
# echo -n doesn't work here because of platform variations
|
26
|
+
# and for some reason jruby requires the explicit subshell; mri launches it automatically
|
27
|
+
process = Shrimple::Process.new('/bin/sh -c "sleep 0.1 && printf done."', chin, chout, cherr)
|
28
|
+
expect(Shrimple.processes.count).to eq 1
|
29
|
+
process.stop
|
30
|
+
expect(process.start_time).not_to eq nil
|
31
|
+
expect(process.stop_time).not_to eq nil
|
32
|
+
claimed = process.stop_time - process.start_time
|
33
|
+
expect(chout.string).to eq 'done.'
|
34
|
+
expect(process.finished?).to eq true
|
35
|
+
expect(process.success?).to eq true
|
36
|
+
end
|
37
|
+
|
38
|
+
# ensure process elapsed time is in the ballpark
|
39
|
+
expect(elapsed).to be >= 0.1
|
40
|
+
expect(claimed).to be >= 0.1
|
41
|
+
expect(claimed).to be <= elapsed
|
42
|
+
|
43
|
+
expect(Shrimple.processes.count).to eq 0
|
44
|
+
expect(chout.closed_read?).to eq true
|
45
|
+
expect(cherr.closed_read?).to eq true
|
46
|
+
end
|
47
|
+
|
48
|
+
it "has a working kill method" do
|
49
|
+
elapsed = time do
|
50
|
+
process = Shrimple::Process.new(['sleep', '0.5'], chin, chout, cherr)
|
51
|
+
|
52
|
+
expect(process.finished?).to eq false
|
53
|
+
expect(process.killed?).to eq false
|
54
|
+
expect(process.success?).to eq false
|
55
|
+
expect(process.timed_out?).to eq false
|
56
|
+
|
57
|
+
process.kill
|
58
|
+
|
59
|
+
expect(process.finished?).to eq true
|
60
|
+
expect(process.killed?).to eq true
|
61
|
+
expect(process.success?).to eq false
|
62
|
+
expect(process.timed_out?).to eq false
|
63
|
+
end
|
64
|
+
|
65
|
+
expect(elapsed).to be < 0.5
|
66
|
+
expect(chout.closed_read?).to eq true
|
67
|
+
expect(cherr.closed_read?).to eq true
|
68
|
+
end
|
69
|
+
|
70
|
+
it "handles invalid commands" do
|
71
|
+
expect {
|
72
|
+
expect(Shrimple.processes.count).to eq 0
|
73
|
+
process = Shrimple::Process.new(['ThisCmdDoes.Not.Exist.'], chin, chout, cherr)
|
74
|
+
raise "we shouldn't get here"
|
75
|
+
}.to raise_error(/[Nn]o such file/)
|
76
|
+
expect(Shrimple.processes.count).to eq 0
|
77
|
+
end
|
78
|
+
|
79
|
+
it "has a working timeout" do
|
80
|
+
elapsed = time do
|
81
|
+
process = Shrimple::Process.new(['sleep', '10'], chin, chout, cherr, 0)
|
82
|
+
end
|
83
|
+
expect(elapsed).to be < 0.5
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dimensions'
|
3
|
+
|
4
|
+
# this file contains the time-consuming tests that shell out to phantomjs
|
5
|
+
|
6
|
+
|
7
|
+
def pdf_valid?(io)
|
8
|
+
# quick & dirty check
|
9
|
+
case io
|
10
|
+
when File
|
11
|
+
io.read[0...4] == "%PDF"
|
12
|
+
when String
|
13
|
+
io[0...4] == "%PDF" || File.open(io).read[0...4] == "%PDF"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def prepare_file outfile
|
19
|
+
# TODO: there MUST be a better way of handling file output in rspec
|
20
|
+
# (can't mock file ops because the output is coming from phantomjs)
|
21
|
+
File.delete(outfile) if File.exists?(outfile)
|
22
|
+
return '/tmp/' + outfile
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe Shrimple do
|
27
|
+
it "echoes its arguments" do
|
28
|
+
s = Shrimple.new(renderer: 'spec/parse_and_print_stdin.js')
|
29
|
+
output = s.render
|
30
|
+
result = JSON.parse(output.stdout)
|
31
|
+
expect(result['renderer']).to eq 'spec/parse_and_print_stdin.js'
|
32
|
+
expect(result['processed']).to eq true # added by the phantom script
|
33
|
+
expect(output.stderr).to eq ""
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# well I give up. can't find an item settable by --config that I can read in js. :(
|
38
|
+
# https://github.com/ariya/phantomjs/issues/12265
|
39
|
+
#
|
40
|
+
# it "sets a command-line arg" do
|
41
|
+
# s = Shrimple.new
|
42
|
+
# s.config.loadImages = false
|
43
|
+
# s.config.autoLoadImages = false
|
44
|
+
# s.renderer = 'render_max_disk_cache.js'
|
45
|
+
# s.render
|
46
|
+
# end
|
47
|
+
|
48
|
+
|
49
|
+
it "renders text to a string" do
|
50
|
+
callback_param = nil
|
51
|
+
s = Shrimple.new
|
52
|
+
s.onSuccess = Proc.new do |result|
|
53
|
+
# make sure this process isn't removed from the process table
|
54
|
+
# until after this callback returns.
|
55
|
+
sleep(0.2)
|
56
|
+
expect(Shrimple.processes.count).to eq 1
|
57
|
+
callback_param = result
|
58
|
+
end
|
59
|
+
s.onError = Proc.new { fail }
|
60
|
+
result = s.render_text("file://#{example_html}")
|
61
|
+
output = result.stdout # TODO: get rid of this line
|
62
|
+
expect(output).to eq "Hello World!\n"
|
63
|
+
expect(callback_param).to eq result
|
64
|
+
end
|
65
|
+
|
66
|
+
it "renders text to a file" do
|
67
|
+
outfile = prepare_file('shrimple-test-output.txt')
|
68
|
+
s = Shrimple.new
|
69
|
+
s.render_text("file://#{example_html}", to: outfile)
|
70
|
+
output = File.read(outfile)
|
71
|
+
expect(output).to eq "Hello World!\n"
|
72
|
+
File.delete(outfile)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "renders html to a string" do
|
76
|
+
s = Shrimple.new
|
77
|
+
result = s.render_html("file://#{example_html}")
|
78
|
+
output = result.stdout # TODO: get rid of this line
|
79
|
+
expect(output).to include "<h1>Hello World!</h1>"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "handles a missing file" do
|
83
|
+
# also ensures failures's stderr appears in the exception
|
84
|
+
callback_param = nil
|
85
|
+
s = Shrimple.new
|
86
|
+
s.onSuccess = Proc.new { fail }
|
87
|
+
s.onError = Proc.new { |result| callback_param = result }
|
88
|
+
expect {
|
89
|
+
s.render_text("file://this-does-not-exist")
|
90
|
+
}.to raise_exception(Shrimple::PhantomError, /Unable to load.*this-does-not-exist/)
|
91
|
+
expect(callback_param).to be_a Shrimple::Phantom
|
92
|
+
end
|
93
|
+
|
94
|
+
it "handles a missing file in background mode" do
|
95
|
+
callback_param = nil
|
96
|
+
s = Shrimple.new(background: true)
|
97
|
+
s.onSuccess = Proc.new { fail }
|
98
|
+
s.onError = Proc.new { |result| callback_param = result }
|
99
|
+
result = s.render_text("file://this-does-not-exist")
|
100
|
+
child = Shrimple.processes.wait_next
|
101
|
+
|
102
|
+
expect(child).to eq result
|
103
|
+
expect(result.success?).to eq false
|
104
|
+
expect(result.stderr).to match(/Unable to load.*this-does-not-exist/)
|
105
|
+
expect(callback_param).to eq result
|
106
|
+
end
|
107
|
+
|
108
|
+
it "handles phantomjs complaining about a missing render script" do
|
109
|
+
s = Shrimple.new(renderer: 'this-does-not-exist')
|
110
|
+
expect {
|
111
|
+
s.render_text("file://#{example_html}")
|
112
|
+
}.to raise_exception(Shrimple::PhantomError, /Can't open 'this-does-not-exist'/)
|
113
|
+
end
|
114
|
+
|
115
|
+
it "handles phantomjs complaining about a missing render script in background mode" do
|
116
|
+
s = Shrimple.new(renderer: 'this-does-not-exist', background: true)
|
117
|
+
result = s.render_text("file://#{example_html}")
|
118
|
+
child = Shrimple.processes.wait_next
|
119
|
+
|
120
|
+
expect(child).to eq result
|
121
|
+
expect(result.success?).to eq false
|
122
|
+
expect(result.stderr).to match(/Can't open 'this-does-not-exist'/)
|
123
|
+
end
|
124
|
+
|
125
|
+
# # it's hopeless: https://github.com/ariya/phantomjs/issues/10687
|
126
|
+
#
|
127
|
+
# it "handles a syntax error in a render script" do
|
128
|
+
# s = Shrimple.new(renderer: 'spec/syntax_error.js')
|
129
|
+
# expect {
|
130
|
+
# s.render_text("file://#{example_html}")
|
131
|
+
# }.to raise_exception(/Can't open 'this-does-not-exist'/)
|
132
|
+
# end
|
133
|
+
|
134
|
+
it "supports a debugging mode" do
|
135
|
+
# isn't there a better way of resetting global variables in rspec?
|
136
|
+
olderr = $stderr
|
137
|
+
begin
|
138
|
+
$stderr = StringIO.new
|
139
|
+
s = Shrimple.new(debug: true)
|
140
|
+
s.render_text("file://#{example_html}")
|
141
|
+
|
142
|
+
expect($stderr.string).to match /^COMMAND: \[.*phantomjs.*render.js"\]/
|
143
|
+
expect($stderr.string).to match /^STDIN: {.*"debug":true.*}/
|
144
|
+
ensure
|
145
|
+
$stderr = olderr
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "renders a pdf to a file" do
|
150
|
+
outfile = prepare_file('shrimple-test-output.pdf')
|
151
|
+
s = Shrimple.new(to: outfile)
|
152
|
+
s.render_pdf "file://#{example_html}"
|
153
|
+
expect(File.exists? outfile).to eq true
|
154
|
+
expect(pdf_valid?(File.new(outfile))).to eq true
|
155
|
+
end
|
156
|
+
|
157
|
+
it "renders a png to a file" do
|
158
|
+
outfile = prepare_file('shrimple-test-output.png')
|
159
|
+
s = Shrimple.new
|
160
|
+
p = s.render_png "file://#{example_html}", output: outfile
|
161
|
+
|
162
|
+
expect(File.exists? outfile).to eq true
|
163
|
+
dimensions = Dimensions.dimensions(outfile)
|
164
|
+
expect(dimensions[0]).to eq 400 # phantomjs default width
|
165
|
+
expect(dimensions[1]).to eq 300 # phantomjs default height
|
166
|
+
|
167
|
+
# when dimensions allows reading the filetype, add that check here
|
168
|
+
# https://github.com/cleanio/dimensions/commit/c61ad05c354feb1063bfbdc97c1ec5456c9ad43a
|
169
|
+
end
|
170
|
+
|
171
|
+
it "renders a png to a stream" do
|
172
|
+
s = Shrimple.new(page: {viewportSize: { width: 555, height: 555 }} )
|
173
|
+
s.page.zoomFactor = 0.75
|
174
|
+
output = s.render_png "file://#{example_html}"
|
175
|
+
|
176
|
+
# todo: would be great if we could attach Dimensions straight to the io object reading the results
|
177
|
+
# instead of needing to flush the result to a memory buffer and wrapping that in a new stringio
|
178
|
+
dimensions = Dimensions(StringIO.new(output.stdout))
|
179
|
+
expect(dimensions.width).to eq 555
|
180
|
+
expect(dimensions.height).to eq 555
|
181
|
+
end
|
182
|
+
|
183
|
+
it "renders a jpeg to a file" do
|
184
|
+
outfile = prepare_file('shrimple-test-output.jpg')
|
185
|
+
s = Shrimple.new
|
186
|
+
s.page.viewportSize = { width: 320, height: 240 }
|
187
|
+
s.output = outfile
|
188
|
+
output = s.render_jpeg "file://#{example_html}"
|
189
|
+
|
190
|
+
expect(File.exists? outfile).to eq true
|
191
|
+
dimensions = Dimensions.dimensions(outfile)
|
192
|
+
expect(dimensions[0]).to eq 320
|
193
|
+
expect(dimensions[1]).to eq 240
|
194
|
+
end
|
195
|
+
|
196
|
+
it "renders a gif to memory" do
|
197
|
+
s = Shrimple.new
|
198
|
+
s.page.viewportSize = { width: 213, height: 214 }
|
199
|
+
output = s.render_gif "file://#{example_html}"
|
200
|
+
|
201
|
+
dimensions = Dimensions(StringIO.new(output.stdout))
|
202
|
+
expect(dimensions.width).to eq 213
|
203
|
+
expect(dimensions.height).to eq 214
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Mostly tests the Shrimple API. Other specs test the internals.
|
4
|
+
|
5
|
+
|
6
|
+
describe Shrimple do
|
7
|
+
# we send this in every request until Phantom fixes its bug, see default_config.rb
|
8
|
+
let(:custom_headers) { {"page" => {"customHeaders"=>{"Accept-Encoding"=>"identity"}}} }
|
9
|
+
|
10
|
+
it "automatically finds the executable and renderer" do
|
11
|
+
s = Shrimple.new
|
12
|
+
expect(File.executable? s.executable).to be true
|
13
|
+
expect(File.exists? s.renderer).to be true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can be told the executable and renderer" do
|
17
|
+
# these don't need to be real executables since they're never called
|
18
|
+
s = Shrimple.new(executable: '/bin/sh', renderer: example_html)
|
19
|
+
expect(s.executable).to eq '/bin/sh'
|
20
|
+
expect(s.renderer).to eq example_html
|
21
|
+
end
|
22
|
+
|
23
|
+
it "dies if specified executable can't be found" do
|
24
|
+
s = Shrimple.new(executable: '/bin/THIS_FILE_DOES.not.Exyst')
|
25
|
+
expect { s.render 'http://be.com' }.to raise_exception(/[Nn]o such file/)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "dies if default executable can't be found" do
|
29
|
+
expect { Shrimple.new.render('http://be.com', executable: nil) }.to raise_exception(/PhantomJS not found/)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "allows a bunch of different ways to set options" do
|
33
|
+
s = Shrimple.new(executable: '/bin/sh', renderer: example_html, render: {quality: 50})
|
34
|
+
|
35
|
+
s.executable = '/bin/cat'
|
36
|
+
s.page.paperSize.orientation = 'landscape'
|
37
|
+
s[:page][:settings][:userAgent] = 'webkitalike'
|
38
|
+
s.options.page.zoomFactor = 0.25
|
39
|
+
|
40
|
+
mock_phantom = Object.new
|
41
|
+
expect(mock_phantom).to receive(:wait).once
|
42
|
+
|
43
|
+
allow(Shrimple::Phantom).to receive(:new).once do |opts|
|
44
|
+
expect(opts.to_hash).to eq(Hashie::Mash.new({
|
45
|
+
input: 'infile',
|
46
|
+
output: 'outfile',
|
47
|
+
executable: '/bin/cat',
|
48
|
+
renderer: example_html,
|
49
|
+
render: { quality: 50 },
|
50
|
+
page: {
|
51
|
+
paperSize: { orientation: 'landscape' },
|
52
|
+
settings: { userAgent: 'webkitalike' },
|
53
|
+
zoomFactor: 0.25
|
54
|
+
}
|
55
|
+
}).merge(custom_headers).to_hash)
|
56
|
+
mock_phantom
|
57
|
+
end
|
58
|
+
|
59
|
+
s.render 'infile', to: 'outfile'
|
60
|
+
end
|
61
|
+
|
62
|
+
it "runs in the background" do
|
63
|
+
s = Shrimple.new(executable: '/bin/cat', renderer: 'tt.js', background: true)
|
64
|
+
|
65
|
+
mock_phantom = Object.new
|
66
|
+
expect(mock_phantom).not_to receive(:wait)
|
67
|
+
allow(Shrimple::Phantom).to receive(:new).once.and_return(mock_phantom)
|
68
|
+
|
69
|
+
p = s.render 'infile'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "special-cases input as the first argument" do
|
73
|
+
s = Shrimple.new
|
74
|
+
s.merge!(executable: nil, renderer: nil)
|
75
|
+
# can either start with a value for input
|
76
|
+
expect(s.get_full_options("input", to: "output")).
|
77
|
+
to eq({'input' => 'input', 'output' => 'output'}.merge(custom_headers))
|
78
|
+
# or just use hashes all the way through
|
79
|
+
expect(s.get_full_options(input: "eenput", output: "ootput")).
|
80
|
+
to eq({'input' => 'eenput', 'output' => 'ootput'}.merge(custom_headers))
|
81
|
+
end
|
82
|
+
|
83
|
+
it "has options with indifferent access" do
|
84
|
+
s = Shrimple.new
|
85
|
+
s.merge!('executable' => nil, renderer: nil)
|
86
|
+
expect(s.get_full_options(executable: 'symbol', 'executable' => 'string')).to eq({'executable' => 'string'}.merge(custom_headers))
|
87
|
+
s.merge!(executable: 'symbol')
|
88
|
+
expect(s.get_full_options(executable: 'symbol')).to eq({'executable' => 'symbol'}.merge(custom_headers))
|
89
|
+
expect(Shrimple.compact!(s.to_hash)).to eq({'executable' => 'symbol'}.merge(custom_headers))
|
90
|
+
end
|
91
|
+
|
92
|
+
it "has a working compact" do
|
93
|
+
expect(Shrimple.compact!({
|
94
|
+
a: nil,
|
95
|
+
b: { c: nil },
|
96
|
+
d: { e: { f: "", g: 1 } },
|
97
|
+
h: false
|
98
|
+
})).to eq({
|
99
|
+
d: { e: { g: 1 }},
|
100
|
+
h: false
|
101
|
+
})
|
102
|
+
|
103
|
+
expect(Shrimple.compact!({})).to eq({})
|
104
|
+
end
|
105
|
+
|
106
|
+
it "has a working deep_dup" do
|
107
|
+
x = { a: 1, b: { c: 2, d: false, e:[1,2,3] }}
|
108
|
+
y = Shrimple.deep_dup(x)
|
109
|
+
|
110
|
+
x[:a] = 2
|
111
|
+
x[:b].delete(:e)
|
112
|
+
x[:b][:d] = true
|
113
|
+
x.delete(:b)
|
114
|
+
|
115
|
+
# y should be unchanged since we dup'd it
|
116
|
+
expect(x).to eq({a: 2})
|
117
|
+
expect(y).to eq({a: 1, b: { c: 2, d: false, e: [1, 2, 3] }})
|
118
|
+
end
|
119
|
+
end
|