shelving 1.253.1 → 1.253.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.253.1",
3
+ "version": "1.253.2",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,16 @@ describe("requireMetaURL", () => {
29
29
  expect(html).toBe("/");
30
30
  });
31
31
 
32
+ test("normalizes a trailing slash on the url away", () => {
33
+ // `/enquiry/loan/` resolves to the same path as `/enquiry/loan`, so trailing-slash URLs match the same route.
34
+ const html = renderToStaticMarkup(
35
+ <MetaContext value={createMeta({ root: "http://x.com/", url: "http://x.com/enquiry/loan/" })}>
36
+ <Probe />
37
+ </MetaContext>,
38
+ );
39
+ expect(html).toBe("/enquiry/loan");
40
+ });
41
+
32
42
  test("throws RequiredError when url is unset", () => {
33
43
  expect(() => renderToStaticMarkup(<Probe />)).toThrow(RequiredError);
34
44
  });
@@ -3,10 +3,12 @@ import { renderToStaticMarkup } from "react-dom/server";
3
3
  import { MetaContext } from "../misc/MetaContext.js";
4
4
  import { createMeta } from "../util/meta.js";
5
5
  import { Router } from "./Router.js";
6
+ import type { RouteProps } from "./Routes.js";
6
7
 
7
8
  const ROUTES = {
8
9
  "/": () => <main>Home</main>,
9
10
  "/about": () => <main>About</main>,
11
+ "/enquiry/{form}": ({ form }: RouteProps) => <main>Enquiry {form}</main>,
10
12
  } as const;
11
13
 
12
14
  function render(url: string) {
@@ -25,4 +27,11 @@ describe("Router", () => {
25
27
  test("throws when no route matches and no fallback is given", () => {
26
28
  expect(() => render("./missing")).toThrow();
27
29
  });
30
+
31
+ test("matches a route when the url has a trailing slash", () => {
32
+ // A trailing slash on the url resolves to the same route as the slash-less form.
33
+ expect(render("http://x.com/about/")).toContain("About");
34
+ expect(render("http://x.com/enquiry/loan")).toContain("Enquiry loan");
35
+ expect(render("http://x.com/enquiry/loan/")).toContain("Enquiry loan");
36
+ });
28
37
  });
package/util/url.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { AnyCaller } from "./function.js";
2
2
  import type { Nullish } from "./null.js";
3
- import type { AbsolutePath } from "./path.js";
3
+ import { type AbsolutePath } from "./path.js";
4
4
  import type { ImmutableURI } from "./uri.js";
5
5
  /**
6
6
  * A URL string has a protocol and a `//`.
@@ -133,6 +133,7 @@ export declare function requireURL(target: PossibleURL, base?: PossibleURL, call
133
133
  * - Need to be valid _URLs_ not just _URIs_, i.e. needs to have `protocol://` at the start.
134
134
  * - Origins need to match, i.e. `http://localhost` !== `http://localhost:4020`
135
135
  * - Relative targets are resolved against the normalized base URL.
136
+ * - The resolved target pathname is normalised with `cleanPath()` — runs of slashes are collapsed and a trailing slash is stripped (the root `/` is preserved) — so `/foo` and `/foo/` resolve to the same path. This mirrors the path-based sibling `matchPathPrefix()`.
136
137
  *
137
138
  * @param target URL to match against `base` — if this is a relative path it will be resolved against `base`
138
139
  * @param base Base URL the `target` is matched against.
@@ -142,6 +143,7 @@ export declare function requireURL(target: PossibleURL, base?: PossibleURL, call
142
143
  * @throws RequiredError If `target` or `base` cannot be resolved to a true URL.
143
144
  *
144
145
  * @example matchURLPrefix("http://x.com/a/b", "http://x.com/a/"); // `/b`
146
+ * @example matchURLPrefix("http://x.com/a/b/", "http://x.com/a/"); // `/b` (trailing slash normalised away)
145
147
  *
146
148
  * @see https://dhoulb.github.io/shelving/util/url/matchURLPrefix
147
149
  */
package/util/url.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { RequiredError } from "../error/RequiredError.js";
2
+ import { cleanPath } from "./path.js";
2
3
  export const ImmutableURL = URL;
3
4
  /**
4
5
  * Is an unknown value a URL object?
@@ -106,6 +107,7 @@ export function requireURL(target, base, caller = requireURL) {
106
107
  * - Need to be valid _URLs_ not just _URIs_, i.e. needs to have `protocol://` at the start.
107
108
  * - Origins need to match, i.e. `http://localhost` !== `http://localhost:4020`
108
109
  * - Relative targets are resolved against the normalized base URL.
110
+ * - The resolved target pathname is normalised with `cleanPath()` — runs of slashes are collapsed and a trailing slash is stripped (the root `/` is preserved) — so `/foo` and `/foo/` resolve to the same path. This mirrors the path-based sibling `matchPathPrefix()`.
109
111
  *
110
112
  * @param target URL to match against `base` — if this is a relative path it will be resolved against `base`
111
113
  * @param base Base URL the `target` is matched against.
@@ -115,6 +117,7 @@ export function requireURL(target, base, caller = requireURL) {
115
117
  * @throws RequiredError If `target` or `base` cannot be resolved to a true URL.
116
118
  *
117
119
  * @example matchURLPrefix("http://x.com/a/b", "http://x.com/a/"); // `/b`
120
+ * @example matchURLPrefix("http://x.com/a/b/", "http://x.com/a/"); // `/b` (trailing slash normalised away)
118
121
  *
119
122
  * @see https://dhoulb.github.io/shelving/util/url/matchURLPrefix
120
123
  */
@@ -126,7 +129,7 @@ export function matchURLPrefix(target, base, caller = matchURLPrefix) {
126
129
  if (targetURL.origin !== baseURL.origin)
127
130
  return;
128
131
  const basePath = baseURL.pathname;
129
- const targetPath = targetURL.pathname;
132
+ const targetPath = cleanPath(targetURL.pathname);
130
133
  if (basePath === "/")
131
134
  return targetPath;
132
135
  // `basePath` may or may not have a trailing slash, so strip it and re-assert the directory boundary explicitly.