Skip to content

Commit

Permalink
Add current_page? helper for pages. (#1074)
Browse files Browse the repository at this point in the history
Closes #1073
  • Loading branch information
wout authored Apr 2, 2020
1 parent 4ce801b commit 685e0ca
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 3 deletions.
178 changes: 178 additions & 0 deletions spec/lucky/url_helpers_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
require "../spec_helper"

include ContextHelper

describe Lucky::UrlHelpers do
describe "#current_page?" do
context "given a string" do
it "tests if a path matches the request path or not" do
view_for("/").current_page?("/").should be_true
view_for("/action").current_page?("/gum").should be_false
view_for("/action").current_page?("/action").should be_true
view_for("/action").current_page?("/action/").should be_true
view_for("/action/").current_page?("/action").should be_true
view_for("/action/").current_page?("/action/").should be_true
end

it "tests if the path of a url matches request path or not" do
view_for("/")
.current_page?("https://example.com/")
.should be_true
view_for("/action")
.current_page?("https://example.com/action")
.should be_true
view_for("/action", host_with_port: "example.io")
.current_page?("https://example.com/action")
.should be_false
view_for("/action", host_with_port: "example.com:3000")
.current_page?("https://example.com/action")
.should be_false
view_for("/action", host_with_port: "example.com:3000")
.current_page?("https://example.com:3000/action")
.should be_true
view_for("/action", host_with_port: "example.com:3000")
.current_page?("http://example.com:3000/action")
.should be_true
end

it "only tests positive for get and head requests" do
view_for("/get", "GET").current_page?("/get").should be_true
view_for("/head", "HEAD").current_page?("/head").should be_true
view_for("/post", "POST").current_page?("/post").should be_false
view_for("/put", "PUT").current_page?("/put").should be_false
view_for("/patch", "PATCH").current_page?("/patch").should be_false
view_for("/delete", "DELETE").current_page?("/delete").should be_false
end

it "ignores query parameters by default" do
view_for("/action?order=desc&page=1").current_page?("/action")
.should be_true
view_for("/action").current_page?("/action?order=desc&page=1")
.should be_true
view_for("/action?order=desc&page=1").current_page?("/action/123")
.should be_false
end

it "deals with escaped characters in query params" do
view_for("/pages?description=Some%20d%C3%A9scription")
.current_page?("/pages?description=Some déscription", check_query_params: true)
.should be_true
view_for("/pages?description=Some%20d%C3%A9scription")
.current_page?("/pages?description=Some%20d%C3%A9scription", check_query_params: true)
.should be_true
end

it "checks query params if explicitly required" do
view_for("/action?order=desc&page=1")
.current_page?("/action?order=desc&page=1", check_query_params: true)
.should be_true
view_for("/action")
.current_page?("/action", check_query_params: true)
.should be_true
view_for("/action")
.current_page?("/action?order=desc&page=1", check_query_params: true)
.should be_false
view_for("/action?order=desc&page=1")
.current_page?("/action", check_query_params: true)
.should be_false
end

it "does not care about the order of query params" do
view_for("/action?order=desc&page=1")
.current_page?("/action?order=desc&page=1", check_query_params: true)
.should be_true
view_for("/action?order=desc&page=1")
.current_page?("/action?page=1&order=desc", check_query_params: true)
.should be_true
end

it "ignores anchors" do
view_for("/pages/123").current_page?("/pages/123#section")
.should be_true
view_for("/pages/123#section").current_page?("/pages/123")
.should be_true
view_for("/pages/123#section").current_page?("/pages/123#section")
.should be_true
view_for("/pages/123")
.current_page?("/pages/123#section", check_query_params: true)
.should be_true
end
end

context "given a browser action" do
it "tests if the path matches or not" do
view_for("/pages/123").current_page?(Pages::Show.with(123))
.should be_true
view_for("/pages/123").current_page?(Pages::Show.with(12))
.should be_false
view_for("/pages").current_page?(Pages::Index)
.should be_true
view_for("/pages")
.current_page?(Pages::Index.with(page: 2))
.should be_true
view_for("/pages?page=2")
.current_page?(Pages::Index)
.should be_true
end

it "checks query params if explicitly required" do
view_for("/pages")
.current_page?(Pages::Index, check_query_params: true)
.should be_true
view_for("/pages?page=2")
.current_page?(Pages::Index.with(page: 2), check_query_params: true)
.should be_true
view_for("/pages")
.current_page?(Pages::Index.with(page: 2), check_query_params: true)
.should be_false
view_for("/pages?page=2")
.current_page?(Pages::Index, check_query_params: true)
.should be_false
end

it "ignores anchors" do
view_for("/pages/123")
.current_page?(Pages::Show.with(123, anchor: "section"))
.should be_true
view_for("/pages/123#section")
.current_page?(Pages::Show.with(123))
.should be_true
view_for("/pages/123#section")
.current_page?(Pages::Show.with(123, anchor: "section"))
.should be_true
view_for("/pages/123")
.current_page?(Pages::Show.with(123, anchor: "section"), check_query_params: true)
.should be_true
end
end
end
end

private def view_for(
path : String,
method : String = "GET",
host_with_port : String = "example.com"
)
request = HTTP::Request.new(method, path)
request.headers["Host"] = host_with_port
TestPage.new(build_context(path: path, request: request))
end

private class TestPage
include Lucky::HTMLPage
include Lucky::UrlHelpers
end

class Pages::Index < TestAction
param page : Int32 = 1

get "/pages" do
plain_text "I'm just a list of pages"
end
end

class Pages::Show < TestAction
get "/pages/:id" do
plain_text "I'm just a page"
end
end
18 changes: 15 additions & 3 deletions spec/support/context_helper.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
module ContextHelper
extend self

private def build_request(method = "GET", body = "", content_type = "", fixed_length : Bool = false) : HTTP::Request
private def build_request(
method = "GET",
body = "",
content_type = "",
fixed_length : Bool = false
) : HTTP::Request
headers = HTTP::Headers.new
headers.add("Content-Type", content_type)
if fixed_length
Expand All @@ -10,7 +15,10 @@ module ContextHelper
HTTP::Request.new(method, "/", body: body, headers: headers)
end

def build_context(path = "/", request : HTTP::Request? = nil) : HTTP::Server::Context
def build_context(
path = "/",
request : HTTP::Request? = nil
) : HTTP::Server::Context
build_context_with_io(IO::Memory.new, path: path, request: request)
end

Expand All @@ -26,7 +34,11 @@ module ContextHelper
)
end

private def build_context_with_io(io : IO, path = "/", request = nil) : HTTP::Server::Context
private def build_context_with_io(
io : IO,
path = "/",
request = nil
) : HTTP::Server::Context
request = request || HTTP::Request.new("GET", path)
response = HTTP::Server::Response.new(io)
HTTP::Server::Context.new request, response
Expand Down
91 changes: 91 additions & 0 deletions src/lucky/page_helpers/url_helpers.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module Lucky::UrlHelpers
# Tests if the given path matches the current request path.
#
# ```
# # Let's say we are visiting https://example.com/shop/products?order=desc&page=1
# current_page?("/shop/checkout")
# # => false
# current_page?("/shop/products")
# # => true
# current_page?("/shop/products/")
# # => true
# current_page?("/shop/products?order=desc&page=1")
# # => true
# current_page?("/shop/products", check_query_params: true)
# # => false
# current_page?("/shop/products?order=desc&page=1", check_query_params: true)
# # => true
# current_page?("https://example.com/shop/products")
# # => true
# current_page?("https://example.io/shop/products")
# # => false
# current_page?("https://example.com/shop/products", check_query_params: true)
# # => false
# current_page?("https://example.com/shop/products?order=desc&page=1")
# # => true
# ```
def current_page?(
value : String,
check_query_params : Bool = false
)
request = @context.request

return false unless {"GET", "HEAD"}.includes?(request.method)

uri = URI.parse(value)
request_uri = URI.parse(request.resource)
path = uri.path
resource = request_uri.path

unless path == "/"
path = path.chomp("/")
resource = resource.chomp("/")
end

if check_query_params
path += comparable_query_params(uri.query_params)
resource += comparable_query_params(request_uri.query_params)
end

if value.match(/^\w+:\/\//)
host_with_port = uri.port ? "#{uri.host}:#{uri.port}" : uri.host
"#{host_with_port}#{path}" == "#{request.host_with_port}#{resource}"
else
path == resource
end
end

# Tests if the given path matches the current request path.
#
# ```
# # Visiting https://example.com/pages/123
# current_page?(Pages::Show.with(123))
# # => true
# current_page?(Posts::Show.with(123))
# # => false
# # Visiting https://example.com/pages
# current_page?(Pages::Index)
# # => true
# current_page?(Blog::Index)
# # => false
# # Visiting https://example.com/pages?page=2
# current_page?(Pages::Index.with)
# # => true
# current_page?(Pages::Index.with(page: 2))
# # => true
# current_page?(Pages::Index.with, check_query_params: true)
# # => false
# current_page?(Pages::Index.with(page: 2), check_query_params: true)
# # => true
# ```
def current_page?(
action : Lucky::Action.class | Lucky::RouteHelper,
check_query_params : Bool = false
)
current_page?(action.path, check_query_params)
end

private def comparable_query_params(query_params : HTTP::Params) : String
URI.decode(query_params.map(&.join).sort!.join)
end
end

0 comments on commit 685e0ca

Please sign in to comment.