Commit 83846957 authored by Ben Kelly's avatar Ben Kelly Committed by Chromium LUCI CQ

URLPattern: Implement exec().

This CL provides an initial implementation of the URLPattern.exec()
method.  It mainly differs from test() in that it returns a full
URLPatternResult object instead of just a boolean.  The result object
looks something like:

  {
    input: <input string or object passed to exec()>,
    protocol: <component result>,
    username: <component result>,
    password: <component result>,
    hostname: <component result>,
    port: <component result>,
    pathname: <component result>,
    search: <component result>,
    hash: <component result>
  }

Each URLPatternComponentResult looks like:

  {
    input: <input component string>,
    groups: {
      <group name or numeric number string>: <matched group value>
    }
  }

To support extracting matched group values this CL enhances ScriptRegexp
to populate an optional vector of strings.

In addition, this CL enhances the WPT tests to process expected result
objects.  In general it is only necessary to provide component results
for components that match non-empty inputs.  The test runner will
automatically add expected results for empty components.

This CL also contains a fix in ApplyInit()'s handling of baseURL.  The
intent was to treat non-specified components as the explicit empty
string.  The code now does this.  This also happens to handle the
unexpected behavior of KURL::GetPass() which always returns a null
string in place of an empty string.

Finally, this CL removes the toRegex() stub as well.  That API is
unlikely to land in the current form, so lets just remove it until
it can be properly designed.

Bug: 1141510
Change-Id: Iedd0598a3d5e493c3e7ed0eb5dce4b09019abca3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2575343
Commit-Queue: Ben Kelly <wanderview@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#834967}
parent ebcb0ff3
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h" #include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h"
#include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
#include "third_party/blink/renderer/platform/bindings/string_resource.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h" #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
namespace blink { namespace blink {
...@@ -70,9 +71,10 @@ ScriptRegexp::ScriptRegexp(const String& pattern, ...@@ -70,9 +71,10 @@ ScriptRegexp::ScriptRegexp(const String& pattern,
ToCoreStringWithUndefinedOrNullCheck(try_catch.Message()->Get()); ToCoreStringWithUndefinedOrNullCheck(try_catch.Message()->Get());
} }
int ScriptRegexp::Match(const String& string, int ScriptRegexp::Match(StringView string,
int start_from, int start_from,
int* match_length) const { int* match_length,
WTF::Vector<String>* group_list) const {
if (match_length) if (match_length)
*match_length = 0; *match_length = 0;
...@@ -94,7 +96,7 @@ int ScriptRegexp::Match(const String& string, ...@@ -94,7 +96,7 @@ int ScriptRegexp::Match(const String& string,
v8::Local<v8::RegExp> regex = regex_.NewLocal(isolate); v8::Local<v8::RegExp> regex = regex_.NewLocal(isolate);
v8::Local<v8::String> subject = v8::Local<v8::String> subject =
V8String(isolate, string.Substring(start_from)); V8String(isolate, StringView(string, start_from));
v8::Local<v8::Value> return_value; v8::Local<v8::Value> return_value;
if (!regex->Exec(context, subject).ToLocal(&return_value)) if (!regex->Exec(context, subject).ToLocal(&return_value))
return -1; return -1;
...@@ -122,6 +124,21 @@ int ScriptRegexp::Match(const String& string, ...@@ -122,6 +124,21 @@ int ScriptRegexp::Match(const String& string,
*match_length = match.As<v8::String>()->Length(); *match_length = match.As<v8::String>()->Length();
} }
if (group_list) {
DCHECK(group_list->IsEmpty());
for (uint32_t i = 1; i < result->Length(); ++i) {
v8::Local<v8::Value> group;
if (!result->Get(context, i).ToLocal(&group))
return -1;
String group_string;
if (group->IsString()) {
group_string =
ToBlinkString<String>(group.As<v8::String>(), kExternalize);
}
group_list->push_back(group_string);
}
}
return match_offset.As<v8::Int32>()->Value() + start_from; return match_offset.As<v8::Int32>()->Value() + start_from;
} }
......
...@@ -52,9 +52,17 @@ class CORE_EXPORT ScriptRegexp final : public GarbageCollected<ScriptRegexp> { ...@@ -52,9 +52,17 @@ class CORE_EXPORT ScriptRegexp final : public GarbageCollected<ScriptRegexp> {
MultilineMode = kMultilineDisabled, MultilineMode = kMultilineDisabled,
CharacterMode = BMP); CharacterMode = BMP);
int Match(const String&, // Attempt to match the given input string against the regexp. Returns the
// index of the match within the input string on success and -1 otherwise.
// If |match_length| is provided, then its populated with the length of the
// match on success. If |group_list| is provided its populated with the
// matched groups within the regexp. These are the values normally starting
// at index 1 within the array returned from Regexp.exec(). |group_list|
// must be empty if it is provided.
int Match(StringView,
int start_from = 0, int start_from = 0,
int* match_length = nullptr) const; int* match_length = nullptr,
WTF::Vector<String>* group_list = nullptr) const;
bool IsValid() const { return !regex_.IsEmpty(); } bool IsValid() const { return !regex_.IsEmpty(); }
// exceptionMessage is available only if !isValid(). // exceptionMessage is available only if !isValid().
......
...@@ -7,7 +7,8 @@ ...@@ -7,7 +7,8 @@
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "third_party/blink/renderer/bindings/core/v8/script_regexp.h" #include "third_party/blink/renderer/bindings/core/v8/script_regexp.h"
#include "third_party/blink/renderer/bindings/modules/v8/usv_string_or_url_pattern_init.h" #include "third_party/blink/renderer/bindings/modules/v8/usv_string_or_url_pattern_init.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_url_pattern_init.h" #include "third_party/blink/renderer/modules/url_pattern/url_pattern_component_result.h"
#include "third_party/blink/renderer/modules/url_pattern/url_pattern_result.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
...@@ -21,6 +22,11 @@ namespace blink { ...@@ -21,6 +22,11 @@ namespace blink {
class URLPattern::Component final class URLPattern::Component final
: public GarbageCollected<URLPattern::Component> { : public GarbageCollected<URLPattern::Component> {
public: public:
bool Match(StringView input, Vector<String>* group_list) const {
return regexp->Match(input, /*start_from=*/0, /*match_length=*/nullptr,
group_list) == 0;
}
void Trace(Visitor* visitor) const { visitor->Trace(regexp); } void Trace(Visitor* visitor) const { visitor->Trace(regexp); }
// The pattern compiled down to a js regular expression. // The pattern compiled down to a js regular expression.
...@@ -28,9 +34,9 @@ class URLPattern::Component final ...@@ -28,9 +34,9 @@ class URLPattern::Component final
// The names to be applied to the regular expression capture groups. Note, // The names to be applied to the regular expression capture groups. Note,
// liburlpattern regular expressions do not use named capture groups directly. // liburlpattern regular expressions do not use named capture groups directly.
WTF::Vector<String> name_list; Vector<String> name_list;
Component(ScriptRegexp* r, WTF::Vector<String> n) Component(ScriptRegexp* r, Vector<String> n)
: regexp(r), name_list(std::move(n)) {} : regexp(r), name_list(std::move(n)) {}
}; };
...@@ -97,7 +103,10 @@ void ApplyInit(const URLPatternInit* init, ...@@ -97,7 +103,10 @@ void ApplyInit(const URLPatternInit* init,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// If there is a baseURL we need to apply its component values first. The // If there is a baseURL we need to apply its component values first. The
// rest of the URLPatternInit structure will then later override these // rest of the URLPatternInit structure will then later override these
// values. // values. Note, the baseURL will always set either an empty string or
// longer value for each considered component. We do not allow null strings
// to persist for these components past this phase since they should no
// longer be treated as wildcards.
if (init->hasBaseURL()) { if (init->hasBaseURL()) {
KURL baseURL(init->baseURL()); KURL baseURL(init->baseURL());
if (!baseURL.IsValid() || baseURL.IsEmpty()) { if (!baseURL.IsValid() || baseURL.IsEmpty()) {
...@@ -108,16 +117,33 @@ void ApplyInit(const URLPatternInit* init, ...@@ -108,16 +117,33 @@ void ApplyInit(const URLPatternInit* init,
if (baseURL.Protocol()) if (baseURL.Protocol())
protocol = baseURL.Protocol(); protocol = baseURL.Protocol();
else
protocol = g_empty_string;
if (baseURL.User()) if (baseURL.User())
username = baseURL.User(); username = baseURL.User();
else
username = g_empty_string;
if (baseURL.Pass()) if (baseURL.Pass())
password = baseURL.Pass(); password = baseURL.Pass();
else
password = g_empty_string;
if (baseURL.Host()) if (baseURL.Host())
hostname = baseURL.Host(); hostname = baseURL.Host();
if (baseURL.HasPort() && baseURL.Port() > 0) else
hostname = g_empty_string;
if (baseURL.Port() > 0)
port = String::Number(baseURL.Port()); port = String::Number(baseURL.Port());
else
port = g_empty_string;
if (baseURL.GetPath()) if (baseURL.GetPath())
pathname = baseURL.GetPath(); pathname = baseURL.GetPath();
else
pathname = "/";
// Do no propagate search or hash from the base URL. This matches the // Do no propagate search or hash from the base URL. This matches the
// behavior when resolving a relative URL against a base URL. // behavior when resolving a relative URL against a base URL.
...@@ -138,6 +164,11 @@ void ApplyInit(const URLPatternInit* init, ...@@ -138,6 +164,11 @@ void ApplyInit(const URLPatternInit* init,
if (init->hasPathname()) { if (init->hasPathname()) {
// TODO: handle relative pathnames // TODO: handle relative pathnames
pathname = init->pathname(); pathname = init->pathname();
if (pathname.IsEmpty() || pathname[0] != '/') {
exception_state.ThrowTypeError(
"Could not resolve absolute pathname for '" + pathname + "'.");
return;
}
} }
if (init->hasSearch()) if (init->hasSearch())
search = init->search(); search = init->search();
...@@ -247,95 +278,16 @@ URLPattern::URLPattern(Component* protocol, ...@@ -247,95 +278,16 @@ URLPattern::URLPattern(Component* protocol,
hash_(hash) {} hash_(hash) {}
bool URLPattern::test(const USVStringOrURLPatternInit& input, bool URLPattern::test(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state) { ExceptionState& exception_state) const {
// By default each URL component value starts with an empty string. The return Match(input, /*result=*/nullptr, exception_state);
// given input is then layered on top of these defaults.
String protocol(g_empty_string);
String username(g_empty_string);
String password(g_empty_string);
String hostname(g_empty_string);
String port(g_empty_string);
String pathname(g_empty_string);
String search(g_empty_string);
String hash(g_empty_string);
// TODO: Refactor the following input processing into a utility method that
// can be shared with exec().
if (input.IsURLPatternInit()) {
// Layer the URLPatternInit values on top of the default empty strings.
ApplyInit(input.GetAsURLPatternInit(), protocol, username, password,
hostname, port, pathname, search, hash, exception_state);
if (exception_state.HadException()) {
// Treat exceptions simply as a failure to match.
exception_state.ClearException();
return false;
}
// TODO: Canonicalize the hostname manually since we did not run the
// input through KURL.
// TODO: URL encode input component values using url::EncodeURIComponent()
// since we did not go through KURL
} else {
DCHECK(input.IsUSVString());
// The compile the input string as a fully resolved URL.
KURL url(input.GetAsUSVString());
if (!url.IsValid() || url.IsEmpty()) {
// Treat as failure to match, but don't throw an exception.
return false;
}
// TODO: Support relative URLs here by taking a string in a second argument.
// Apply the parsed URL components on top of our defaults.
if (url.Protocol())
protocol = url.Protocol();
if (url.User())
username = url.User();
if (url.Pass())
password = url.Pass();
if (url.Host())
hostname = url.Host();
if (url.HasPort() && url.Port() > 0)
port = String::Number(url.Port());
if (url.GetPath())
pathname = url.GetPath();
if (url.Query())
search = url.Query();
if (url.FragmentIdentifier())
hash = url.FragmentIdentifier();
}
// TODO: Should we do special processing for port to make "default" values
// match things like "80" for an http protocol?
// Each component of the pattern must match the corresponding component of
// the input. If a pattern Component is nullptr, then it matches any
// input and we can avoid running a real regular expression match.
return (!protocol_ || protocol_->regexp->Match(protocol) == 0) &&
(!username_ || username_->regexp->Match(username) == 0) &&
(!password_ || password_->regexp->Match(password) == 0) &&
(!hostname_ || hostname_->regexp->Match(hostname) == 0) &&
(!port_ || port_->regexp->Match(port) == 0) &&
(!pathname_ || pathname_->regexp->Match(pathname) == 0) &&
(!search_ || search_->regexp->Match(search) == 0) &&
(!hash_ || hash_->regexp->Match(hash) == 0);
} }
URLPatternResult* URLPattern::exec(const USVStringOrURLPatternInit& input, URLPatternResult* URLPattern::exec(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state) { ExceptionState& exception_state) const {
// TODO: Implement URLPatternResult* result = URLPatternResult::Create();
// TODO: Modernize ScriptRegexp() to support returning capture group values. if (!Match(input, result, exception_state))
exception_state.ThrowTypeError("The exec() method is not implemented yet.");
return nullptr; return nullptr;
} return result;
String URLPattern::toRegExp(const String& component,
ExceptionState& exception_state) {
// TODO: Implement
exception_state.ThrowTypeError(
"The toRegExp() method is not implemented yet.");
return String();
} }
void URLPattern::Trace(Visitor* visitor) const { void URLPattern::Trace(Visitor* visitor) const {
...@@ -392,7 +344,7 @@ URLPattern::Component* URLPattern::CompilePattern( ...@@ -392,7 +344,7 @@ URLPattern::Component* URLPattern::CompilePattern(
return nullptr; return nullptr;
} }
WTF::Vector<String> wtf_name_list; Vector<String> wtf_name_list;
wtf_name_list.ReserveInitialCapacity( wtf_name_list.ReserveInitialCapacity(
static_cast<wtf_size_t>(name_list.size())); static_cast<wtf_size_t>(name_list.size()));
for (const auto& name : name_list) { for (const auto& name : name_list) {
...@@ -404,4 +356,155 @@ URLPattern::Component* URLPattern::CompilePattern( ...@@ -404,4 +356,155 @@ URLPattern::Component* URLPattern::CompilePattern(
std::move(wtf_name_list)); std::move(wtf_name_list));
} }
bool URLPattern::Match(const USVStringOrURLPatternInit& input,
URLPatternResult* result,
ExceptionState& exception_state) const {
// By default each URL component value starts with an empty string. The
// given input is then layered on top of these defaults.
String protocol(g_empty_string);
String username(g_empty_string);
String password(g_empty_string);
String hostname(g_empty_string);
String port(g_empty_string);
String pathname("/");
String search(g_empty_string);
String hash(g_empty_string);
if (input.IsURLPatternInit()) {
// Layer the URLPatternInit values on top of the default empty strings.
ApplyInit(input.GetAsURLPatternInit(), protocol, username, password,
hostname, port, pathname, search, hash, exception_state);
if (exception_state.HadException()) {
// Treat exceptions simply as a failure to match.
exception_state.ClearException();
return false;
}
// TODO: Canonicalize the hostname manually since we did not run the
// input through KURL.
// TODO: URL encode input component values using url::EncodeURIComponent()
// since we did not go through KURL
} else {
DCHECK(input.IsUSVString());
// The compile the input string as a fully resolved URL.
KURL url(input.GetAsUSVString());
if (!url.IsValid() || url.IsEmpty()) {
// Treat as failure to match, but don't throw an exception.
return false;
}
// TODO: Support relative URLs here by taking a string in a second argument.
// Apply the parsed URL components on top of our defaults.
if (url.Protocol())
protocol = url.Protocol();
if (url.User())
username = url.User();
if (url.Pass())
password = url.Pass();
if (url.Host())
hostname = url.Host();
if (url.Port() > 0)
port = String::Number(url.Port());
if (url.GetPath())
pathname = url.GetPath();
if (url.Query())
search = url.Query();
if (url.FragmentIdentifier())
hash = url.FragmentIdentifier();
}
// The pathname should be resolved to an absolute value before now.
DCHECK(!pathname.IsEmpty());
DCHECK(pathname.StartsWith("/"));
// TODO: Should we do special processing for port to make "default" values
// match things like "80" for an http protocol? See
// https://github.com/WICG/urlpattern/issues/31.
Vector<String> protocol_group_list;
Vector<String> username_group_list;
Vector<String> password_group_list;
Vector<String> hostname_group_list;
Vector<String> port_group_list;
Vector<String> pathname_group_list;
Vector<String> search_group_list;
Vector<String> hash_group_list;
// If we are not generating a full result then we don't need to populate
// group lists.
auto* protocol_group_list_ref = result ? &protocol_group_list : nullptr;
auto* username_group_list_ref = result ? &username_group_list : nullptr;
auto* password_group_list_ref = result ? &password_group_list : nullptr;
auto* hostname_group_list_ref = result ? &hostname_group_list : nullptr;
auto* port_group_list_ref = result ? &port_group_list : nullptr;
auto* pathname_group_list_ref = result ? &pathname_group_list : nullptr;
auto* search_group_list_ref = result ? &search_group_list : nullptr;
auto* hash_group_list_ref = result ? &hash_group_list : nullptr;
// Each component of the pattern must match the corresponding component of
// the input. If a pattern Component is nullptr, then it matches any
// input and we can avoid running a real regular expression match.
bool matched =
(!protocol_ || protocol_->Match(protocol, protocol_group_list_ref)) &&
(!username_ || username_->Match(username, username_group_list_ref)) &&
(!password_ || password_->Match(password, password_group_list_ref)) &&
(!hostname_ || hostname_->Match(hostname, hostname_group_list_ref)) &&
(!port_ || port_->Match(port, port_group_list_ref)) &&
(!pathname_ || pathname_->Match(pathname, pathname_group_list_ref)) &&
(!search_ || search_->Match(search, search_group_list_ref)) &&
(!hash_ || hash_->Match(hash, hash_group_list_ref));
if (!matched || !result)
return matched;
result->setInput(input);
result->setProtocol(
MakeComponentResult(protocol_, protocol, protocol_group_list));
result->setUsername(
MakeComponentResult(username_, username, username_group_list));
result->setPassword(
MakeComponentResult(password_, password, password_group_list));
result->setHostname(
MakeComponentResult(hostname_, hostname, hostname_group_list));
result->setPort(MakeComponentResult(port_, port, port_group_list));
result->setPathname(MakeComponentResult(
pathname_, pathname, pathname_group_list, /*is_pathname=*/true));
result->setSearch(MakeComponentResult(search_, search, search_group_list));
result->setHash(MakeComponentResult(hash_, hash, hash_group_list));
return true;
}
// static
URLPatternComponentResult* URLPattern::MakeComponentResult(
Component* component,
const String& input,
const Vector<String>& group_list,
bool is_pathname) {
Vector<std::pair<String, String>> groups;
if (!component) {
// When there is not Component we must act as if there was a default
// wildcard pattern with a group. For most components the group ends
// up including the entire input. For pathname, however, the leading "/"
// is excluded from the group since its considered a prefix.
if (is_pathname) {
DCHECK(!input.IsEmpty());
DCHECK_EQ(input[0], '/');
groups.emplace_back("0", input.Substring(1));
} else {
groups.emplace_back("0", input);
}
} else {
DCHECK_EQ(component->name_list.size(), group_list.size());
for (wtf_size_t i = 0; i < group_list.size(); ++i) {
groups.emplace_back(component->name_list[i], group_list[i]);
}
}
auto* result = URLPatternComponentResult::Create();
result->setInput(input);
result->setGroups(groups);
return result;
}
} // namespace blink } // namespace blink
...@@ -15,6 +15,7 @@ struct Options; ...@@ -15,6 +15,7 @@ struct Options;
namespace blink { namespace blink {
class ExceptionState; class ExceptionState;
class URLPatternComponentResult;
class URLPatternInit; class URLPatternInit;
class URLPatternResult; class URLPatternResult;
class USVStringOrURLPatternInit; class USVStringOrURLPatternInit;
...@@ -38,10 +39,9 @@ class URLPattern : public ScriptWrappable { ...@@ -38,10 +39,9 @@ class URLPattern : public ScriptWrappable {
base::PassKey<URLPattern> key); base::PassKey<URLPattern> key);
bool test(const USVStringOrURLPatternInit& input, bool test(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state); ExceptionState& exception_state) const;
URLPatternResult* exec(const USVStringOrURLPatternInit& input, URLPatternResult* exec(const USVStringOrURLPatternInit& input,
ExceptionState& exception_state); ExceptionState& exception_state) const;
String toRegExp(const String& component, ExceptionState& exception_state);
// TODO: define a stringifier // TODO: define a stringifier
...@@ -60,6 +60,24 @@ class URLPattern : public ScriptWrappable { ...@@ -60,6 +60,24 @@ class URLPattern : public ScriptWrappable {
const liburlpattern::Options& options, const liburlpattern::Options& options,
ExceptionState& exception_state); ExceptionState& exception_state);
// A utility function to determine if a given |input| matches the pattern
// or not. Returns |true| if there is a match and |false| otherwise. If
// |result| is not nullptr then the URLPatternResult contents will be filled
// in as expected by the exec() method.
bool Match(const USVStringOrURLPatternInit& input,
URLPatternResult* result,
ExceptionState& exception_state) const;
// A utility function that constructs a URLPatternComponentResult for
// a given |component|, |input|, and |group_list|. The |component| may
// be nullptr. If the result is for the pathname component then you must
// set |is_pathname| to true.
static URLPatternComponentResult* MakeComponentResult(
Component* component,
const String& input,
const Vector<String>& group_list,
bool is_pathname = false);
// The compiled patterns for each URL component. If a Component member is // The compiled patterns for each URL component. If a Component member is
// nullptr then it should be treated as a wildcard matching any input. // nullptr then it should be treated as a wildcard matching any input.
Member<Component> protocol_; Member<Component> protocol_;
......
...@@ -13,5 +13,4 @@ typedef (USVString or URLPatternInit) URLPatternInput; ...@@ -13,5 +13,4 @@ typedef (USVString or URLPatternInit) URLPatternInput;
[RaisesException] constructor(URLPatternInit init); [RaisesException] constructor(URLPatternInit init);
[RaisesException] boolean test(URLPatternInput input); [RaisesException] boolean test(URLPatternInput input);
[RaisesException] URLPatternResult exec(URLPatternInput input); [RaisesException] URLPatternResult exec(URLPatternInput input);
[RaisesException] DOMString toRegExp(USVString component);
}; };
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// https://wicg.github.io/urlpattern/ // https://wicg.github.io/urlpattern/
dictionary URLPatternResult { dictionary URLPatternResult {
USVString input; URLPatternInput input;
URLPatternComponentResult protocol; URLPatternComponentResult protocol;
URLPatternComponentResult username; URLPatternComponentResult username;
URLPatternComponentResult password; URLPatternComponentResult password;
......
...@@ -2,78 +2,102 @@ ...@@ -2,78 +2,102 @@
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "pathname": "/foo/ba" }, "input": { "pathname": "/foo/ba" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "pathname": "/foo/bar/" }, "input": { "pathname": "/foo/bar/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": "https://example.com/foo/bar", "input": "https://example.com/foo/bar",
"expected": true "expected": {
"input": "https://example.com/foo/bar",
"hostname": { "input": "example.com", "groups": { "0": "example.com" } },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": { "0": "https" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": "https://example.com/foo/bar/baz", "input": "https://example.com/foo/bar/baz",
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "hostname": "example.com", "pathname": "/foo/bar" }, "input": { "hostname": "example.com", "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "hostname": "example.com", "pathname": "/foo/bar" },
"hostname": { "input": "example.com", "groups": { "0": "example.com" } },
"pathname": { "input": "/foo/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "hostname": "example.com", "pathname": "/foo/bar/baz" }, "input": { "hostname": "example.com", "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "pathname": "/foo/bar", "baseURL": "https://example.com" }, "input": { "pathname": "/foo/bar", "baseURL": "https://example.com" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar", "baseURL": "https://example.com" },
"hostname": { "input": "example.com", "groups": { "0": "example.com" } },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": { "0": "https" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar" }, "pattern": { "pathname": "/foo/bar" },
"input": { "pathname": "/foo/bar/baz", "baseURL": "https://example.com" }, "input": { "pathname": "/foo/bar/baz", "baseURL": "https://example.com" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "hostname": "example.com", "pathname": "/foo/bar" }, "input": { "hostname": "example.com", "pathname": "/foo/bar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "protocol": "https", "hostname": "example.com", "input": { "protocol": "https", "hostname": "example.com",
"pathname": "/foo/bar" }, "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "protocol": "https", "hostname": "example.com",
"pathname": "/foo/bar" },
"exactly_empty_components": [ "username", "password", "port" ],
"hostname": { "input": "example.com", "groups": {} },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "protocol": "https", "hostname": "example.com", "input": { "protocol": "https", "hostname": "example.com",
"pathname": "/foo/bar/baz" }, "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
...@@ -81,375 +105,507 @@ ...@@ -81,375 +105,507 @@
"input": { "protocol": "https", "hostname": "example.com", "input": { "protocol": "https", "hostname": "example.com",
"pathname": "/foo/bar", "search": "otherquery", "pathname": "/foo/bar", "search": "otherquery",
"hash": "otherhash" }, "hash": "otherhash" },
"expected": true "expected": {
"input": { "protocol": "https", "hostname": "example.com",
"pathname": "/foo/bar", "search": "otherquery",
"hash": "otherhash" },
"exactly_empty_components": [ "username", "password", "port" ],
"hash": { "input": "otherhash", "groups": { "0": "otherhash" } },
"hostname": { "input": "example.com", "groups": {} },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": {} },
"search": { "input": "otherquery", "groups": { "0": "otherquery" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": "https://example.com/foo/bar", "input": "https://example.com/foo/bar",
"expected": true "expected": {
"input": "https://example.com/foo/bar",
"exactly_empty_components": [ "username", "password", "port" ],
"hostname": { "input": "example.com", "groups": {} },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": "https://example.com/foo/bar?otherquery#otherhash", "input": "https://example.com/foo/bar?otherquery#otherhash",
"expected": true "expected": {
"input": "https://example.com/foo/bar?otherquery#otherhash",
"exactly_empty_components": [ "username", "password", "port" ],
"hash": { "input": "otherhash", "groups": { "0": "otherhash" } },
"hostname": { "input": "example.com", "groups": {} },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": {} },
"search": { "input": "otherquery", "groups": { "0": "otherquery" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": "https://example.com/foo/bar/baz", "input": "https://example.com/foo/bar/baz",
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": "https://other.com/foo/bar", "input": "https://other.com/foo/bar",
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": "http://other.com/foo/bar", "input": "http://other.com/foo/bar",
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "pathname": "/foo/bar", "baseURL": "https://example.com" }, "input": { "pathname": "/foo/bar", "baseURL": "https://example.com" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar", "baseURL": "https://example.com" },
"exactly_empty_components": [ "username", "password", "port" ],
"hostname": { "input": "example.com", "groups": {} },
"pathname": { "input": "/foo/bar", "groups": {} },
"protocol": { "input": "https", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "pathname": "/foo/bar/baz", "baseURL": "https://example.com" }, "input": { "pathname": "/foo/bar/baz", "baseURL": "https://example.com" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "pathname": "/foo/bar", "baseURL": "https://other.com" }, "input": { "pathname": "/foo/bar", "baseURL": "https://other.com" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/bar", "pattern": { "pathname": "/foo/bar",
"baseURL": "https://example.com?query#hash" }, "baseURL": "https://example.com?query#hash" },
"input": { "pathname": "/foo/bar", "baseURL": "http://example.com" }, "input": { "pathname": "/foo/bar", "baseURL": "http://example.com" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar" }, "pattern": { "pathname": "/foo/:bar" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar" }, "pattern": { "pathname": "/foo/:bar" },
"input": { "pathname": "/foo/index.html" }, "input": { "pathname": "/foo/index.html" },
"expected": true "expected": {
"input": { "pathname": "/foo/index.html" },
"pathname": { "input": "/foo/index.html", "groups": { "bar": "index.html" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar" }, "pattern": { "pathname": "/foo/:bar" },
"input": { "pathname": "/foo/bar/" }, "input": { "pathname": "/foo/bar/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar" }, "pattern": { "pathname": "/foo/:bar" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)" }, "pattern": { "pathname": "/foo/(.*)" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)" }, "pattern": { "pathname": "/foo/(.*)" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)" }, "pattern": { "pathname": "/foo/(.*)" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": true "expected": {
"input": { "pathname": "/foo/" },
"pathname": { "input": "/foo/", "groups": { "0": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)" }, "pattern": { "pathname": "/foo/(.*)" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar(.*)" }, "pattern": { "pathname": "/foo/:bar(.*)" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar(.*)" }, "pattern": { "pathname": "/foo/:bar(.*)" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar(.*)" }, "pattern": { "pathname": "/foo/:bar(.*)" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": true "expected": {
"input": { "pathname": "/foo/" },
"pathname": { "input": "/foo/", "groups": { "bar": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar(.*)" }, "pattern": { "pathname": "/foo/:bar(.*)" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar?" }, "pattern": { "pathname": "/foo/:bar?" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar?" }, "pattern": { "pathname": "/foo/:bar?" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": true "expected": {
"input": { "pathname": "/foo" },
"pathname": { "input": "/foo", "groups": { "bar": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar?" }, "pattern": { "pathname": "/foo/:bar?" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar?" }, "pattern": { "pathname": "/foo/:bar?" },
"input": { "pathname": "/foobar" }, "input": { "pathname": "/foobar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar?" }, "pattern": { "pathname": "/foo/:bar?" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar+" }, "pattern": { "pathname": "/foo/:bar+" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar+" }, "pattern": { "pathname": "/foo/:bar+" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar+" }, "pattern": { "pathname": "/foo/:bar+" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar+" }, "pattern": { "pathname": "/foo/:bar+" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar+" }, "pattern": { "pathname": "/foo/:bar+" },
"input": { "pathname": "/foobar" }, "input": { "pathname": "/foobar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar*" }, "pattern": { "pathname": "/foo/:bar*" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "bar": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar*" }, "pattern": { "pathname": "/foo/:bar*" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "bar": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar*" }, "pattern": { "pathname": "/foo/:bar*" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": true "expected": {
"input": { "pathname": "/foo" },
"pathname": { "input": "/foo", "groups": { "bar": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/:bar*" }, "pattern": { "pathname": "/foo/:bar*" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/:bar*" }, "pattern": { "pathname": "/foo/:bar*" },
"input": { "pathname": "/foobar" }, "input": { "pathname": "/foobar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)?" }, "pattern": { "pathname": "/foo/(.*)?" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)?" }, "pattern": { "pathname": "/foo/(.*)?" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)?" }, "pattern": { "pathname": "/foo/(.*)?" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": true "expected": {
"input": { "pathname": "/foo" },
"pathname": { "input": "/foo", "groups": { "0": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)?" }, "pattern": { "pathname": "/foo/(.*)?" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": true "expected": {
"input": { "pathname": "/foo/" },
"pathname": { "input": "/foo/", "groups": { "0": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)?" }, "pattern": { "pathname": "/foo/(.*)?" },
"input": { "pathname": "/foobar" }, "input": { "pathname": "/foobar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)?" }, "pattern": { "pathname": "/foo/(.*)?" },
"input": { "pathname": "/fo" }, "input": { "pathname": "/fo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)+" }, "pattern": { "pathname": "/foo/(.*)+" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)+" }, "pattern": { "pathname": "/foo/(.*)+" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)+" }, "pattern": { "pathname": "/foo/(.*)+" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)+" }, "pattern": { "pathname": "/foo/(.*)+" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": true "expected": {
"input": { "pathname": "/foo/" },
"pathname": { "input": "/foo/", "groups": { "0": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)+" }, "pattern": { "pathname": "/foo/(.*)+" },
"input": { "pathname": "/foobar" }, "input": { "pathname": "/foobar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)+" }, "pattern": { "pathname": "/foo/(.*)+" },
"input": { "pathname": "/fo" }, "input": { "pathname": "/fo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)*" }, "pattern": { "pathname": "/foo/(.*)*" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": { "0": "bar" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)*" }, "pattern": { "pathname": "/foo/(.*)*" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/baz" },
"pathname": { "input": "/foo/bar/baz", "groups": { "0": "bar/baz" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)*" }, "pattern": { "pathname": "/foo/(.*)*" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": true "expected": {
"input": { "pathname": "/foo" },
"pathname": { "input": "/foo", "groups": { "0": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)*" }, "pattern": { "pathname": "/foo/(.*)*" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": true "expected": {
"input": { "pathname": "/foo/" },
"pathname": { "input": "/foo/", "groups": { "0": "" } }
}
}, },
{ {
"pattern": { "pathname": "/foo/(.*)*" }, "pattern": { "pathname": "/foo/(.*)*" },
"input": { "pathname": "/foobar" }, "input": { "pathname": "/foobar" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo/(.*)*" }, "pattern": { "pathname": "/foo/(.*)*" },
"input": { "pathname": "/fo" }, "input": { "pathname": "/fo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}" }, "pattern": { "pathname": "/foo{/bar}" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}" }, "pattern": { "pathname": "/foo{/bar}" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}" }, "pattern": { "pathname": "/foo{/bar}" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}" }, "pattern": { "pathname": "/foo{/bar}" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}?" }, "pattern": { "pathname": "/foo{/bar}?" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}?" }, "pattern": { "pathname": "/foo{/bar}?" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}?" }, "pattern": { "pathname": "/foo{/bar}?" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": true "expected": {
"input": { "pathname": "/foo" },
"pathname": { "input": "/foo", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}?" }, "pattern": { "pathname": "/foo{/bar}?" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}+" }, "pattern": { "pathname": "/foo{/bar}+" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}+" }, "pattern": { "pathname": "/foo{/bar}+" },
"input": { "pathname": "/foo/bar/bar" }, "input": { "pathname": "/foo/bar/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/bar" },
"pathname": { "input": "/foo/bar/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}+" }, "pattern": { "pathname": "/foo{/bar}+" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}+" }, "pattern": { "pathname": "/foo{/bar}+" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}+" }, "pattern": { "pathname": "/foo{/bar}+" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}*" }, "pattern": { "pathname": "/foo{/bar}*" },
"input": { "pathname": "/foo/bar" }, "input": { "pathname": "/foo/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar" },
"pathname": { "input": "/foo/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}*" }, "pattern": { "pathname": "/foo{/bar}*" },
"input": { "pathname": "/foo/bar/bar" }, "input": { "pathname": "/foo/bar/bar" },
"expected": true "expected": {
"input": { "pathname": "/foo/bar/bar" },
"pathname": { "input": "/foo/bar/bar", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}*" }, "pattern": { "pathname": "/foo{/bar}*" },
"input": { "pathname": "/foo/bar/baz" }, "input": { "pathname": "/foo/bar/baz" },
"expected": false "expected": null
}, },
{ {
"pattern": { "pathname": "/foo{/bar}*" }, "pattern": { "pathname": "/foo{/bar}*" },
"input": { "pathname": "/foo" }, "input": { "pathname": "/foo" },
"expected": true "expected": {
"input": { "pathname": "/foo" },
"pathname": { "input": "/foo", "groups": {} }
}
}, },
{ {
"pattern": { "pathname": "/foo{/bar}*" }, "pattern": { "pathname": "/foo{/bar}*" },
"input": { "pathname": "/foo/" }, "input": { "pathname": "/foo/" },
"expected": false "expected": null
} }
] ]
...@@ -4,7 +4,72 @@ function runTests(data) { ...@@ -4,7 +4,72 @@ function runTests(data) {
for (let entry of data) { for (let entry of data) {
test(function() { test(function() {
const pattern = new URLPattern(entry.pattern); const pattern = new URLPattern(entry.pattern);
assert_equals(pattern.test(entry.input), entry.expected);
// First, validate the test() method by converting the expected result to
// a truthy value.
assert_equals(pattern.test(entry.input), !!entry.expected,
'test() result');
// Next, start validating the exec() method.
const result = pattern.exec(entry.input);
// On a failed match exec() returns null.
if (!entry.expected) {
assert_equals(result, entry.expected, 'exec() failed match result');
return;
}
// Next verify the result.input is correct. This may be a structured
// URLPatternInit dictionary object or a URL string.
if (typeof entry.expected.input === 'object') {
assert_object_equals(result.input, entry.expected.input,
'exec() result.input');
} else {
assert_equals(result.input, entry.expected.input,
'exec() result.input');
}
// Next we will compare the URLPatternComponentResult for each of these
// expected components.
const component_list = [
'protocol',
'username',
'password',
'hostname',
'password',
'pathname',
'search',
'hash',
];
for (let component of component_list) {
let expected_obj = entry.expected[component];
// If the test expectations don't include a component object, then
// we auto-generate one. This is convenient for the many cases
// where the pattern has a default wildcard or empty string pattern
// for a component and the input is essentially empty.
if (!expected_obj) {
// We must treat pathname specially since it always has at least one
// slash.
if (component === 'pathname')
expected_obj = { input: '/', groups: {} };
else
expected_obj = { input: '', groups: {} };
// Next, we must treat default wildcards differently than empty string
// patterns. The wildcard results in a capture group, but the empty
// string pattern does not. The expectation object must list which
// components should be empty instead of wildcards in
// |exactly_empty_components|.
if (!entry.expected.exactly_empty_components ||
!entry.expected.exactly_empty_components.includes(component)) {
expected_obj.groups['0'] = '';
}
}
assert_object_equals(result[component], expected_obj,
`exec() result for ${component}`);
}
}, `Pattern: ${JSON.stringify(entry.pattern)} Input: ${JSON.stringify(entry.input)}`); }, `Pattern: ${JSON.stringify(entry.pattern)} Input: ${JSON.stringify(entry.input)}`);
} }
} }
......
...@@ -1562,7 +1562,6 @@ interface URLPattern ...@@ -1562,7 +1562,6 @@ interface URLPattern
method constructor method constructor
method exec method exec
method test method test
method toRegExp
interface URLSearchParams interface URLSearchParams
attribute @@toStringTag attribute @@toStringTag
method @@iterator method @@iterator
......
...@@ -1592,7 +1592,6 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -1592,7 +1592,6 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method constructor [Worker] method constructor
[Worker] method exec [Worker] method exec
[Worker] method test [Worker] method test
[Worker] method toRegExp
[Worker] interface URLSearchParams [Worker] interface URLSearchParams
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] method @@iterator [Worker] method @@iterator
......
...@@ -8594,7 +8594,6 @@ interface URLPattern ...@@ -8594,7 +8594,6 @@ interface URLPattern
method constructor method constructor
method exec method exec
method test method test
method toRegExp
interface URLSearchParams interface URLSearchParams
attribute @@toStringTag attribute @@toStringTag
method @@iterator method @@iterator
......
...@@ -1439,7 +1439,6 @@ Starting worker: resources/global-interface-listing-worker.js ...@@ -1439,7 +1439,6 @@ Starting worker: resources/global-interface-listing-worker.js
[Worker] method constructor [Worker] method constructor
[Worker] method exec [Worker] method exec
[Worker] method test [Worker] method test
[Worker] method toRegExp
[Worker] interface URLSearchParams [Worker] interface URLSearchParams
[Worker] attribute @@toStringTag [Worker] attribute @@toStringTag
[Worker] method @@iterator [Worker] method @@iterator
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment