require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__))) describe "path plugin" do def path_app(*args, &block) app(:bare) do plugin :path path(*args, &block) route{|r| send(r.path_info)} end end def path_script_name_app(*args, &block) app(:bare) do opts[:add_script_name] = true plugin :path path(*args, &block) route{|r| send(r.path_info)} end end def path_block_app(b, *args, &block) path_app(*args, &block) app.route{|r| send(r.path_info, &b)} end it "adds path method for defining named paths" do app(:bare) do plugin :path path :foo, "/foo" path :bar do |o| "/bar/#{o}" end path :baz do |&block| "/baz/#{block.call}" end route do |r| "#{foo_path}#{bar_path('a')}#{baz_path{'b'}}" end end body.must_equal '/foo/bar/a/baz/b' end it "raises if both path and block are given" do app.plugin :path proc{app.path(:foo, '/foo'){}}.must_raise(Roda::RodaError) end it "raises if neither path nor block are given" do app.plugin :path proc{app.path(:foo)}.must_raise(Roda::RodaError) end it "raises if two options hashes are given" do app.plugin :path proc{app.path(:foo, {:name=>'a'}, :add_script_name=>true)}.must_raise(Roda::RodaError) end it "supports :name option for naming the method" do path_app(:foo, :name=>'foobar_route'){"/bar/foo"} body("foobar_route").must_equal "/bar/foo" end it "supports :add_script_name option for automatically adding the script name" do path_app(:foo, :add_script_name=>true){"/bar/foo"} body("foo_path", 'SCRIPT_NAME'=>'/baz').must_equal "/baz/bar/foo" end it "respects :add_script_name app option for automatically adding the script name" do path_script_name_app(:foo){"/bar/foo"} body("foo_path", 'SCRIPT_NAME'=>'/baz').must_equal "/baz/bar/foo" end it "supports :add_script_name=>false option for not automatically adding the script name" do path_script_name_app(:foo, :add_script_name=>false){"/bar/foo"} body("foo_path", 'SCRIPT_NAME'=>'/baz').must_equal "/bar/foo" end it "supports path method accepting a block when using :add_script_name" do path_block_app(lambda{"c"}, :foo, :add_script_name=>true){|&block| "/bar/foo/#{block.call}"} body("foo_path", 'SCRIPT_NAME'=>'/baz').must_equal "/baz/bar/foo/c" end it "supports :url option for also creating a *_url method" do path_app(:foo, :url=>true){"/bar/foo"} body("foo_path", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).must_equal "/bar/foo" body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).must_equal "http://example.org/bar/foo" end it "supports url method accepting a block when using :url" do path_block_app(lambda{"c"}, :foo, :url=>true){|&block| "/bar/foo/#{block.call}"} body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).must_equal "http://example.org/bar/foo/c" end it "supports url method name specified in :url option" do path_app(:foo, :url=>:foobar_uri){"/bar/foo"} body("foo_path", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).must_equal "/bar/foo" body("foobar_uri", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).must_equal "http://example.org/bar/foo" end it "supports :url_only option for not creating a path method" do path_app(:foo, :url_only=>true){"/bar/foo"} proc{body("foo_path")}.must_raise(NoMethodError) body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>80).must_equal "http://example.org/bar/foo" end it "handles non-default ports in url methods" do path_app(:foo, :url=>true){"/bar/foo"} body("foo_url", 'HTTP_HOST'=>'example.org', "rack.url_scheme"=>'http', 'SERVER_PORT'=>81).must_equal "http://example.org:81/bar/foo" end end describe "path plugin" do before do app(:bare) do plugin :path route{|r| path(*env['path'])} end c = Class.new{attr_accessor :a} app.path(c){|obj, *args| "/d/#{obj.a}/#{File.join(*args)}"} @obj = c.new @obj.a = 1 end it "Roda#path respects classes and symbols registered via Roda.path" do # Strings body('path'=>'/foo/bar').must_equal '/foo/bar' # Classes body('path'=>@obj).must_equal '/d/1/' body('path'=>[@obj, 'foo']).must_equal '/d/1/foo' body('path'=>[@obj, 'foo', 'bar']).must_equal '/d/1/foo/bar' end it "Roda#path raises an error for an unrecognized class" do # Strings proc{body('path'=>:foo)}.must_raise(Roda::RodaError) end it "Roda#path respects :add_script_name app option" do app.opts[:add_script_name] = true # Strings body('path'=>'/foo/bar', 'SCRIPT_NAME'=>'/baz').must_equal '/baz/foo/bar' # Classes body('path'=>@obj, 'SCRIPT_NAME'=>'/baz').must_equal '/baz/d/1/' body('path'=>[@obj, 'foo'], 'SCRIPT_NAME'=>'/baz').must_equal '/baz/d/1/foo' body('path'=>[@obj, 'foo', 'bar'], 'SCRIPT_NAME'=>'/baz').must_equal '/baz/d/1/foo/bar' end it "Roda#path works in subclasses" do old_app = @app @app = Class.new(@app) @app.route{|r| path('/a')} body.must_equal '/a' @app.path(String){|b| "/foo#{b}"} body.must_equal '/foo/a' @app = old_app body('path'=>'/a').must_equal '/a' end it "registers classes by reference by default" do c1 = Class.new def c1.name; 'C'; end c2 = Class.new def c2.name; 'C'; end @app.path(c1){'/c'} @app.route{|r| path(r.env['c'])} body('c'=>c1.new).must_equal '/c' proc{body('c'=>c2.new)}.must_raise(Roda::RodaError) end it ":by_name => option registers classes by name" do c1 = Class.new def c1.name; 'C'; end c2 = Class.new def c2.name; 'C'; end @app.plugin :path, :by_name=>true @app.path(c1){'/c'} @app.route{|r| path(r.env['c'])} body('c'=>c1.new).must_equal '/c' body('c'=>c2.new).must_equal '/c' end it "Roda.path_block returns the block used" do c = Class.new b = proc{|x| x.to_s} @app.path(c, &b) # Work around minitest bug assert_equal @app.path_block(c), b end it "Roda.path doesn't work with classes without blocks" do proc{app.path(Class.new)}.must_raise(Roda::RodaError) end it "Roda.path doesn't work with classes with paths or options" do proc{app.path(Class.new, '/a'){}}.must_raise(Roda::RodaError) proc{app.path(Class.new, nil, :a=>1){}}.must_raise(Roda::RodaError) end it "Roda.path doesn't work after freezing the app" do app.freeze proc{app.path(Class.new){|obj| ''}}.must_raise FrozenError end end