diff options
Diffstat (limited to 'spec/ruby/language/case_spec.rb')
| -rw-r--r-- | spec/ruby/language/case_spec.rb | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/spec/ruby/language/case_spec.rb b/spec/ruby/language/case_spec.rb new file mode 100644 index 0000000000..464d06e46a --- /dev/null +++ b/spec/ruby/language/case_spec.rb @@ -0,0 +1,509 @@ +require_relative '../spec_helper' + +describe "The 'case'-construct" do + it "evaluates the body of the when clause matching the case target expression" do + case 1 + when 2; false + when 1; true + end.should == true + end + + it "evaluates the body of the when clause whose array expression includes the case target expression" do + case 2 + when 3, 4; false + when 1, 2; true + end.should == true + end + + it "evaluates the body of the when clause in left-to-right order if it's an array expression" do + @calls = [] + def foo; @calls << :foo; end + def bar; @calls << :bar; end + + case true + when foo, bar; + end + + @calls.should == [:foo, :bar] + end + + it "evaluates the body of the when clause whose range expression includes the case target expression" do + case 5 + when 21..30; false + when 1..20; true + end.should == true + end + + it "returns nil when no 'then'-bodies are given" do + case "a" + when "a" + when "b" + end.should == nil + end + + it "evaluates the 'else'-body when no other expression matches" do + case "c" + when "a"; 'foo' + when "b"; 'bar' + else 'zzz' + end.should == 'zzz' + end + + it "returns nil when no expression matches and 'else'-body is empty" do + case "c" + when "a"; "a" + when "b"; "b" + else + end.should == nil + end + + it "returns 2 when a then body is empty" do + case Object.new + when Numeric then + 1 + when String then + # ok + else + 2 + end.should == 2 + end + + it "returns the statement following 'then'" do + case "a" + when "a" then 'foo' + when "b" then 'bar' + end.should == 'foo' + end + + it "tests classes with case equality" do + case "a" + when String + 'foo' + when Symbol + 'bar' + end.should == 'foo' + end + + it "tests with matching regexps" do + case "hello" + when /abc/; false + when /^hell/; true + end.should == true + end + + it "tests with matching regexps and sets $~ and captures" do + case "foo42" + when /oo(\d+)/ + $~.should be_kind_of(MatchData) + $1.should == "42" + else + flunk + end + $~.should be_kind_of(MatchData) + $1.should == "42" + end + + it "tests with a string interpolated in a regexp" do + digits = '\d+' + case "foo44" + when /oo(#{digits})/ + $~.should be_kind_of(MatchData) + $1.should == "44" + else + flunk + end + $~.should be_kind_of(MatchData) + $1.should == "44" + end + + it "tests with a regexp interpolated within another regexp" do + digits_regexp = /\d+/ + case "foo43" + when /oo(#{digits_regexp})/ + $~.should be_kind_of(MatchData) + $1.should == "43" + else + flunk + end + $~.should be_kind_of(MatchData) + $1.should == "43" + end + + it "does not test with equality when given classes" do + case :symbol.class + when Symbol + "bar" + when String + "bar" + else + "foo" + end.should == "foo" + end + + it "takes lists of values" do + case 'z' + when 'a', 'b', 'c', 'd' + "foo" + when 'x', 'y', 'z' + "bar" + end.should == "bar" + + case 'b' + when 'a', 'b', 'c', 'd' + "foo" + when 'x', 'y', 'z' + "bar" + end.should == "foo" + end + + it "tests an empty array" do + case [] + when [] + 'foo' + else + 'bar' + end.should == 'foo' + end + + it "expands arrays to lists of values" do + case 'z' + when *['a', 'b', 'c', 'd'] + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "bar" + end + + it "takes an expanded array in addition to a list of values" do + case 'f' + when 'f', *['a', 'b', 'c', 'd'] + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "foo" + + case 'b' + when 'f', *['a', 'b', 'c', 'd'] + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "foo" + end + + it "takes an expanded array before additional listed values" do + case 'f' + when *['a', 'b', 'c', 'd'], 'f' + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == 'foo' + end + + it "expands arrays from variables before additional listed values" do + a = ['a', 'b', 'c'] + case 'a' + when *a, 'd', 'e' + "foo" + when 'x' + "bar" + end.should == "foo" + end + + it "expands arrays from variables before a single additional listed value" do + a = ['a', 'b', 'c'] + case 'a' + when *a, 'd' + "foo" + when 'x' + "bar" + end.should == "foo" + end + + it "expands multiple arrays from variables before additional listed values" do + a = ['a', 'b', 'c'] + b = ['d', 'e', 'f'] + + case 'f' + when *a, *b, 'g', 'h' + "foo" + when 'x' + "bar" + end.should == "foo" + end + + # MR: critical + it "concats arrays before expanding them" do + a = ['a', 'b', 'c', 'd'] + b = ['f'] + + case 'f' + when 'f', *a|b + "foo" + when *['x', 'y', 'z'] + "bar" + end.should == "foo" + end + + it "never matches when clauses with no values" do + case nil + when *[] + "foo" + end.should == nil + end + + it "lets you define a method after the case statement" do + case (def foo; 'foo'; end; 'f') + when 'a' + 'foo' + when 'f' + 'bar' + end.should == 'bar' + end + + it "raises a SyntaxError when 'else' is used when no 'when' is given" do + -> { + eval <<-CODE + case 4 + else + true + end + CODE + }.should raise_error(SyntaxError) + end + + it "raises a SyntaxError when 'else' is used before a 'when' was given" do + -> { + eval <<-CODE + case 4 + else + true + when 4; false + end + CODE + }.should raise_error(SyntaxError) + end + + it "supports nested case statements" do + result = false + case :x + when Symbol + case :y + when Symbol + result = true + end + end + result.should == true + end + + it "supports nested case statements followed by a when with a splatted array" do + result = false + case :x + when Symbol + case :y + when Symbol + result = true + end + when *[Symbol] + result = false + end + result.should == true + end + + it "supports nested case statements followed by a when with a splatted non-array" do + result = false + case :x + when Symbol + case :y + when Symbol + result = true + end + when *Symbol + result = false + end + result.should == true + end + + it "works even if there's only one when statement" do + case 1 + when 1 + 100 + end.should == 100 + end + + it "evaluates true as only 'true' when true is the first clause" do + case 1 + when true; "bad" + when Integer; "good" + end.should == "good" + end + + it "evaluates false as only 'false' when false is the first clause" do + case nil + when false; "bad" + when nil; "good" + end.should == "good" + end + + it "treats a literal array as its own when argument, rather than a list of arguments" do + case 'foo' + when ['foo', 'foo']; 'bad' + when 'foo'; 'good' + end.should == 'good' + end + + it "takes multiple expanded arrays" do + a1 = ['f', 'o', 'o'] + a2 = ['b', 'a', 'r'] + + case 'f' + when *a1, *['x', 'y', 'z'] + "foo" + when *a2, *['x', 'y', 'z'] + "bar" + end.should == "foo" + + case 'b' + when *a1, *['x', 'y', 'z'] + "foo" + when *a2, *['x', 'y', 'z'] + "bar" + end.should == "bar" + end + + it "calls === even when private" do + klass = Class.new do + def ===(o) + true + end + private :=== + end + + case 1 + when klass.new + :called + end.should == :called + end + + it "accepts complex expressions within ()" do + case 'a' + when (raise if 2+2 == 3; /a/) + :called + end.should == :called + end + + it "only matches last value in complex expressions within ()" do + case 'a' + when ('a'; 'b') + :wrong_called + when ('b'; 'a') + :called + end.should == :called + end + + it "supports declaring variables in the case target expression" do + def test(v) + case new_variable_in_expression = v + when true + # This extra block is a test that `new_variable_in_expression` is declared outside of it and not inside + self.then { new_variable_in_expression } + else + # Same + self.then { new_variable_in_expression.casecmp?("foo") } + end + end + + self.test("bar").should == false + self.test(true).should == true + end + + ruby_version_is ""..."3.4" do + it "warns if there are identical when clauses" do + -> { + eval <<~RUBY + case 1 + when 2 + :foo + when 2 + :bar + end + RUBY + }.should complain(/warning: (duplicated .when' clause with line \d+ is ignored|'when' clause on line \d+ duplicates 'when' clause on line \d+ and is ignored)/, verbose: true) + end + end + + ruby_version_is "3.4" do + it "warns if there are identical when clauses" do + -> { + eval <<~RUBY + case 1 + when 2 + :foo + when 2 + :bar + end + RUBY + }.should complain(/warning: 'when' clause on line \d+ duplicates 'when' clause on line \d+ and is ignored/, verbose: true) + end + end +end + +describe "The 'case'-construct with no target expression" do + it "evaluates the body of the first clause when at least one of its condition expressions is true" do + case + when true, false; 'foo' + end.should == 'foo' + end + + it "evaluates the body of the first when clause that is not false/nil" do + case + when false; 'foo' + when 2; 'bar' + when 1 == 1; 'baz' + end.should == 'bar' + + case + when false; 'foo' + when nil; 'foo' + when 1 == 1; 'bar' + end.should == 'bar' + end + + it "evaluates the body of the else clause if all when clauses are false/nil" do + case + when false; 'foo' + when nil; 'foo' + when 1 == 2; 'bar' + else 'baz' + end.should == 'baz' + end + + it "evaluates multiple conditional expressions as a boolean disjunction" do + case + when true, false; 'foo' + else 'bar' + end.should == 'foo' + + case + when false, true; 'foo' + else 'bar' + end.should == 'foo' + end + + # Homogeneous cases are often optimized to avoid === using a jump table, and should be tested separately. + # See https://github.com/jruby/jruby/issues/6440 + it "handles homogeneous cases" do + case + when 1; 'foo' + when 2; 'bar' + end.should == 'foo' + end + + it "expands arrays to lists of values" do + case + when *[false] + "foo" + when *[true] + "bar" + end.should == "bar" + end +end |
