2018-03-04 10:09:32 -05:00
|
|
|
require_relative '../../spec_helper'
|
|
|
|
require_relative 'fixtures/classes'
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
describe "Array#flatten" do
|
|
|
|
it "returns a one-dimensional flattening recursively" do
|
|
|
|
[[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []].flatten.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "takes an optional argument that determines the level of recursion" do
|
|
|
|
[ 1, 2, [3, [4, 5] ] ].flatten(1).should == [1, 2, 3, [4, 5]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns dup when the level of recursion is 0" do
|
|
|
|
a = [ 1, 2, [3, [4, 5] ] ]
|
|
|
|
a.flatten(0).should == a
|
|
|
|
a.flatten(0).should_not equal(a)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "ignores negative levels" do
|
|
|
|
[ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-1).should == [1, 2, 3, 4, 5, 6]
|
|
|
|
[ 1, 2, [ 3, 4, [5, 6] ] ].flatten(-10).should == [1, 2, 3, 4, 5, 6]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "tries to convert passed Objects to Integers using #to_int" do
|
|
|
|
obj = mock("Converted to Integer")
|
|
|
|
obj.should_receive(:to_int).and_return(1)
|
|
|
|
|
|
|
|
[ 1, 2, [3, [4, 5] ] ].flatten(obj).should == [1, 2, 3, [4, 5]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a TypeError when the passed Object can't be converted to an Integer" do
|
|
|
|
obj = mock("Not converted")
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { [ 1, 2, [3, [4, 5] ] ].flatten(obj) }.should raise_error(TypeError)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "does not call flatten on elements" do
|
|
|
|
obj = mock('[1,2]')
|
|
|
|
obj.should_not_receive(:flatten)
|
|
|
|
[obj, obj].flatten.should == [obj, obj]
|
|
|
|
|
|
|
|
obj = [5, 4]
|
|
|
|
obj.should_not_receive(:flatten)
|
|
|
|
[obj, obj].flatten.should == [5, 4, 5, 4]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises an ArgumentError on recursive arrays" do
|
|
|
|
x = []
|
|
|
|
x << x
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { x.flatten }.should raise_error(ArgumentError)
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
x = []
|
|
|
|
y = []
|
|
|
|
x << y
|
|
|
|
y << x
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { x.flatten }.should raise_error(ArgumentError)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "flattens any element which responds to #to_ary, using the return value of said method" do
|
|
|
|
x = mock("[3,4]")
|
|
|
|
x.should_receive(:to_ary).at_least(:once).and_return([3, 4])
|
|
|
|
[1, 2, x, 5].flatten.should == [1, 2, 3, 4, 5]
|
|
|
|
|
|
|
|
y = mock("MyArray[]")
|
|
|
|
y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[])
|
|
|
|
[y].flatten.should == []
|
|
|
|
|
|
|
|
z = mock("[2,x,y,5]")
|
|
|
|
z.should_receive(:to_ary).and_return([2, x, y, 5])
|
|
|
|
[1, z, 6].flatten.should == [1, 2, 3, 4, 5, 6]
|
|
|
|
end
|
|
|
|
|
2018-04-28 15:50:06 -04:00
|
|
|
it "does not call #to_ary on elements beyond the given level" do
|
|
|
|
obj = mock("1")
|
|
|
|
obj.should_not_receive(:to_ary)
|
|
|
|
[[obj]].flatten(1)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "returns subclass instance for Array subclasses" do
|
|
|
|
ArraySpecs::MyArray[].flatten.should be_an_instance_of(ArraySpecs::MyArray)
|
|
|
|
ArraySpecs::MyArray[1, 2, 3].flatten.should be_an_instance_of(ArraySpecs::MyArray)
|
|
|
|
ArraySpecs::MyArray[1, [2], 3].flatten.should be_an_instance_of(ArraySpecs::MyArray)
|
|
|
|
ArraySpecs::MyArray[1, [2, 3], 4].flatten.should == ArraySpecs::MyArray[1, 2, 3, 4]
|
|
|
|
[ArraySpecs::MyArray[1, 2, 3]].flatten.should be_an_instance_of(Array)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "is not destructive" do
|
|
|
|
ary = [1, [2, 3]]
|
|
|
|
ary.flatten
|
|
|
|
ary.should == [1, [2, 3]]
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "with a non-Array object in the Array" do
|
|
|
|
before :each do
|
|
|
|
@obj = mock("Array#flatten")
|
|
|
|
ScratchPad.record []
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not call #to_ary if the method is not defined" do
|
|
|
|
[@obj].flatten.should == [@obj]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not raise an exception if #to_ary returns nil" do
|
|
|
|
@obj.should_receive(:to_ary).and_return(nil)
|
|
|
|
[@obj].flatten.should == [@obj]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a TypeError if #to_ary does not return an Array" do
|
|
|
|
@obj.should_receive(:to_ary).and_return(1)
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { [@obj].flatten }.should raise_error(TypeError)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
2017-09-20 18:02:10 -04:00
|
|
|
ruby_version_is ""..."2.5" do
|
|
|
|
it "calls respond_to_missing?(:to_ary, false) to try coercing" do
|
|
|
|
def @obj.respond_to_missing?(*args) ScratchPad << args; false end
|
|
|
|
[@obj].flatten.should == [@obj]
|
|
|
|
ScratchPad.recorded.should == [[:to_ary, false]]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ruby_version_is "2.5" do
|
|
|
|
it "calls respond_to_missing?(:to_ary, true) to try coercing" do
|
|
|
|
def @obj.respond_to_missing?(*args) ScratchPad << args; false end
|
|
|
|
[@obj].flatten.should == [@obj]
|
|
|
|
ScratchPad.recorded.should == [[:to_ary, true]]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-07 08:04:49 -04:00
|
|
|
it "does not call #to_ary if not defined when #respond_to_missing? returns false" do
|
2017-09-20 18:02:10 -04:00
|
|
|
def @obj.respond_to_missing?(name, priv) ScratchPad << name; false end
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
[@obj].flatten.should == [@obj]
|
2017-09-20 18:02:10 -04:00
|
|
|
ScratchPad.recorded.should == [:to_ary]
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #to_ary if not defined when #respond_to_missing? returns true" do
|
2017-09-20 18:02:10 -04:00
|
|
|
def @obj.respond_to_missing?(name, priv) ScratchPad << name; true end
|
2017-05-07 08:04:49 -04:00
|
|
|
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { [@obj].flatten }.should raise_error(NoMethodError)
|
2017-09-20 18:02:10 -04:00
|
|
|
ScratchPad.recorded.should == [:to_ary]
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #method_missing if defined" do
|
|
|
|
@obj.should_receive(:method_missing).with(:to_ary).and_return([1, 2, 3])
|
|
|
|
[@obj].flatten.should == [1, 2, 3]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns a tainted array if self is tainted" do
|
|
|
|
[].taint.flatten.tainted?.should be_true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns an untrusted array if self is untrusted" do
|
|
|
|
[].untrust.flatten.untrusted?.should be_true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "performs respond_to? and method_missing-aware checks when coercing elements to array" do
|
|
|
|
bo = BasicObject.new
|
|
|
|
[bo].flatten.should == [bo]
|
|
|
|
|
|
|
|
def bo.method_missing(name, *)
|
|
|
|
[1,2]
|
|
|
|
end
|
|
|
|
|
|
|
|
[bo].flatten.should == [1,2]
|
|
|
|
|
|
|
|
def bo.respond_to?(name, *)
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
[bo].flatten.should == [bo]
|
|
|
|
|
|
|
|
def bo.respond_to?(name, *)
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
[bo].flatten.should == [1,2]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "Array#flatten!" do
|
|
|
|
it "modifies array to produce a one-dimensional flattening recursively" do
|
|
|
|
a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []]
|
|
|
|
a.flatten!
|
|
|
|
a.should == [1, 2, 3, 2, 3, 4, 4, 5, 5, 1, 2, 3, 4]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns self if made some modifications" do
|
|
|
|
a = [[[1, [2, 3]],[2, 3, [4, [4, [5, 5]], [1, 2, 3]]], [4]], []]
|
|
|
|
a.flatten!.should equal(a)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns nil if no modifications took place" do
|
|
|
|
a = [1, 2, 3]
|
|
|
|
a.flatten!.should == nil
|
|
|
|
a = [1, [2, 3]]
|
|
|
|
a.flatten!.should_not == nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "should not check modification by size" do
|
|
|
|
a = [1, 2, [3]]
|
|
|
|
a.flatten!.should_not == nil
|
|
|
|
a.should == [1, 2, 3]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "takes an optional argument that determines the level of recursion" do
|
|
|
|
[ 1, 2, [3, [4, 5] ] ].flatten!(1).should == [1, 2, 3, [4, 5]]
|
|
|
|
end
|
|
|
|
|
|
|
|
# redmine #1440
|
|
|
|
it "returns nil when the level of recursion is 0" do
|
|
|
|
a = [ 1, 2, [3, [4, 5] ] ]
|
|
|
|
a.flatten!(0).should == nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "treats negative levels as no arguments" do
|
|
|
|
[ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-1).should == [1, 2, 3, 4, 5, 6]
|
|
|
|
[ 1, 2, [ 3, 4, [5, 6] ] ].flatten!(-10).should == [1, 2, 3, 4, 5, 6]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "tries to convert passed Objects to Integers using #to_int" do
|
|
|
|
obj = mock("Converted to Integer")
|
|
|
|
obj.should_receive(:to_int).and_return(1)
|
|
|
|
|
|
|
|
[ 1, 2, [3, [4, 5] ] ].flatten!(obj).should == [1, 2, 3, [4, 5]]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises a TypeError when the passed Object can't be converted to an Integer" do
|
|
|
|
obj = mock("Not converted")
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { [ 1, 2, [3, [4, 5] ] ].flatten!(obj) }.should raise_error(TypeError)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "does not call flatten! on elements" do
|
|
|
|
obj = mock('[1,2]')
|
|
|
|
obj.should_not_receive(:flatten!)
|
|
|
|
[obj, obj].flatten!.should == nil
|
|
|
|
|
|
|
|
obj = [5, 4]
|
|
|
|
obj.should_not_receive(:flatten!)
|
|
|
|
[obj, obj].flatten!.should == [5, 4, 5, 4]
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises an ArgumentError on recursive arrays" do
|
|
|
|
x = []
|
|
|
|
x << x
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { x.flatten! }.should raise_error(ArgumentError)
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
x = []
|
|
|
|
y = []
|
|
|
|
x << y
|
|
|
|
y << x
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { x.flatten! }.should raise_error(ArgumentError)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "flattens any elements which responds to #to_ary, using the return value of said method" do
|
|
|
|
x = mock("[3,4]")
|
|
|
|
x.should_receive(:to_ary).at_least(:once).and_return([3, 4])
|
|
|
|
[1, 2, x, 5].flatten!.should == [1, 2, 3, 4, 5]
|
|
|
|
|
|
|
|
y = mock("MyArray[]")
|
|
|
|
y.should_receive(:to_ary).at_least(:once).and_return(ArraySpecs::MyArray[])
|
|
|
|
[y].flatten!.should == []
|
|
|
|
|
|
|
|
z = mock("[2,x,y,5]")
|
|
|
|
z.should_receive(:to_ary).and_return([2, x, y, 5])
|
|
|
|
[1, z, 6].flatten!.should == [1, 2, 3, 4, 5, 6]
|
|
|
|
|
|
|
|
ary = [ArraySpecs::MyArray[1, 2, 3]]
|
|
|
|
ary.flatten!
|
|
|
|
ary.should be_an_instance_of(Array)
|
|
|
|
ary.should == [1, 2, 3]
|
|
|
|
end
|
|
|
|
|
2017-12-27 11:12:47 -05:00
|
|
|
it "raises a #{frozen_error_class} on frozen arrays when the array is modified" do
|
2017-05-07 08:04:49 -04:00
|
|
|
nested_ary = [1, 2, []]
|
|
|
|
nested_ary.freeze
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { nested_ary.flatten! }.should raise_error(frozen_error_class)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# see [ruby-core:23663]
|
2017-12-27 11:12:47 -05:00
|
|
|
it "raises a #{frozen_error_class} on frozen arrays when the array would not be modified" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> { ArraySpecs.frozen_array.flatten! }.should raise_error(frozen_error_class)
|
|
|
|
-> { ArraySpecs.empty_frozen_array.flatten! }.should raise_error(frozen_error_class)
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
end
|