Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add current_page? helper for pages. #1074

Merged
merged 5 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
wout marked this conversation as resolved.
Show resolved Hide resolved

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
wout marked this conversation as resolved.
Show resolved Hide resolved
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
wout marked this conversation as resolved.
Show resolved Hide resolved

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
wout marked this conversation as resolved.
Show resolved Hide resolved
)
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