From 22b4d03d37defda0c4b59113b669378eeb9baac7 Mon Sep 17 00:00:00 2001 From: Michael Herzner Date: Fri, 14 Jun 2024 01:54:18 +0200 Subject: [PATCH] test(path): improve test coverage (#5038) --- path/_common/assert_path_test.ts | 17 +++++++++ path/_common/basename_test.ts | 49 ++++++++++++++++++++++++ path/_common/format_test.ts | 54 +++++++++++++++++++++++++++ path/_common/glob_to_reg_exp.ts | 34 +++++++++-------- path/_common/normalize_string_test.ts | 26 +++++++++++++ path/_common/to_file_url_test.ts | 21 +++++++++++ 6 files changed, 185 insertions(+), 16 deletions(-) create mode 100644 path/_common/assert_path_test.ts create mode 100644 path/_common/basename_test.ts create mode 100644 path/_common/format_test.ts create mode 100644 path/_common/normalize_string_test.ts create mode 100644 path/_common/to_file_url_test.ts diff --git a/path/_common/assert_path_test.ts b/path/_common/assert_path_test.ts new file mode 100644 index 000000000000..cf2161eea113 --- /dev/null +++ b/path/_common/assert_path_test.ts @@ -0,0 +1,17 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, assertThrows } from "@std/assert"; +import { assertPath } from "./assert_path.ts"; + +Deno.test("assertPath()", () => { + assertEquals(assertPath(""), undefined); + assertEquals(assertPath("foo"), undefined); +}); + +Deno.test("assertPath() throws", () => { + assertThrows( + () => assertPath(undefined), + TypeError, + "Path must be a string. Received undefined", + ); +}); diff --git a/path/_common/basename_test.ts b/path/_common/basename_test.ts new file mode 100644 index 000000000000..71961fa1cd86 --- /dev/null +++ b/path/_common/basename_test.ts @@ -0,0 +1,49 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, assertThrows } from "@std/assert"; +import { assertArgs, lastPathSegment, stripSuffix } from "./basename.ts"; +import { CHAR_FORWARD_SLASH } from "./constants.ts"; + +Deno.test("assertArgs()", () => { + assertEquals(assertArgs("", ""), ""); + assertEquals(assertArgs("foo", "bar"), undefined); + // @ts-expect-error - testing invalid input for suffix + assertEquals(assertArgs("", undefined), ""); +}); + +Deno.test("assertArgs() throws", () => { + assertThrows( + // @ts-expect-error - testing invalid input + () => assertArgs(undefined, "bar"), + TypeError, + "Path must be a string. Received undefined", + ); + assertThrows( + // @ts-expect-error - testing invalid input + () => assertArgs("foo", undefined), + TypeError, + "Suffix must be a string. Received undefined", + ); +}); + +Deno.test("lastPathSegment()", () => { + assertEquals( + lastPathSegment("foo", (char) => char === CHAR_FORWARD_SLASH), + "foo", + ); + assertEquals( + lastPathSegment("foo/bar", (char) => char === CHAR_FORWARD_SLASH), + "bar", + ); + assertEquals( + lastPathSegment("foo/bar/baz", (char) => char === CHAR_FORWARD_SLASH), + "baz", + ); +}); + +Deno.test("stripSuffix()", () => { + assertEquals(stripSuffix("foo", "bar"), "foo"); + assertEquals(stripSuffix("foobar", "bar"), "foo"); + assertEquals(stripSuffix("foobar", "baz"), "foobar"); + assertEquals(stripSuffix("foobar", "foobar"), "foobar"); +}); diff --git a/path/_common/format_test.ts b/path/_common/format_test.ts new file mode 100644 index 000000000000..fd1ee971c389 --- /dev/null +++ b/path/_common/format_test.ts @@ -0,0 +1,54 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, assertThrows } from "@std/assert"; +import { _format, assertArg } from "./format.ts"; + +Deno.test("_format()", () => { + assertEquals(_format("", {}), ""); + assertEquals(_format("", { root: "/" }), "/"); + assertEquals(_format("", { dir: "/foo/bar" }), "/foo/bar"); + assertEquals(_format("", { base: "baz" }), "baz"); + assertEquals(_format("", { name: "baz" }), "baz"); + assertEquals(_format("", { ext: ".js" }), ".js"); + assertEquals(_format("", { name: "baz", ext: ".js" }), "baz.js"); + assertEquals(_format("", { root: "/", base: "baz" }), "/baz"); + assertEquals(_format("", { root: "/", name: "baz" }), "/baz"); + assertEquals(_format("", { root: "/", ext: ".js" }), "/.js"); + assertEquals(_format("", { root: "/", name: "baz", ext: ".js" }), "/baz.js"); + assertEquals(_format("/", { dir: "/foo/bar", base: "baz" }), "/foo/bar/baz"); + assertEquals( + _format("/", { dir: "/foo/bar", base: "baz", ext: ".js" }), + "/foo/bar/baz", + ); + assertEquals( + _format("/", { dir: "/foo/bar", name: "baz", ext: ".js" }), + "/foo/bar/baz.js", + ); +}); + +Deno.test("assertArg()", () => { + assertEquals(assertArg({}), undefined); + assertEquals(assertArg({ root: "/" }), undefined); + assertEquals(assertArg({ dir: "/foo/bar" }), undefined); +}); + +Deno.test("assertArg() throws", () => { + assertThrows( + // @ts-expect-error - testing invalid input + () => assertArg(null), + TypeError, + `The "pathObject" argument must be of type Object. Received type object`, + ); + assertThrows( + // @ts-expect-error - testing invalid input + () => assertArg(undefined), + TypeError, + `The "pathObject" argument must be of type Object. Received type undefined`, + ); + assertThrows( + // @ts-expect-error - testing invalid input + () => assertArg(""), + TypeError, + `The "pathObject" argument must be of type Object. Received type string`, + ); +}); diff --git a/path/_common/glob_to_reg_exp.ts b/path/_common/glob_to_reg_exp.ts index ae2274b5bf3d..8b18fde0ca2a 100644 --- a/path/_common/glob_to_reg_exp.ts +++ b/path/_common/glob_to_reg_exp.ts @@ -27,7 +27,7 @@ export interface GlobOptions { /** Options for {@linkcode globToRegExp}. */ export type GlobToRegExpOptions = GlobOptions; -const regExpEscapeChars = [ +const REG_EXP_ESCAPE_CHARS = [ "!", "$", "(", @@ -42,8 +42,12 @@ const regExpEscapeChars = [ "^", "{", "|", -]; -const rangeEscapeChars = ["-", "\\", "]"]; +] as const; +const RANGE_ESCAPE_CHARS = ["-", "\\", "]"] as const; + +type RegExpEscapeChar = typeof REG_EXP_ESCAPE_CHARS[number]; +type RangeEscapeChar = typeof RANGE_ESCAPE_CHARS[number]; +type EscapeChar = RegExpEscapeChar | RangeEscapeChar; export interface GlobConstants { sep: string; @@ -88,8 +92,12 @@ export function _globToRegExp( for (; i < glob.length && !c.seps.includes(glob[i]!); i++) { if (inEscape) { inEscape = false; - const escapeChars = inRange ? rangeEscapeChars : regExpEscapeChars; - segment += escapeChars.includes(glob[i]!) ? `\\${glob[i]}` : glob[i]; + const escapeChars = (inRange + ? RANGE_ESCAPE_CHARS + : REG_EXP_ESCAPE_CHARS) as unknown as EscapeChar[]; + segment += escapeChars.includes(glob[i]! as EscapeChar) + ? `\\${glob[i]}` + : glob[i]; continue; } @@ -146,11 +154,7 @@ export function _globToRegExp( } if (inRange) { - if (glob[i] === "\\") { - segment += `\\\\`; - } else { - segment += glob[i]; - } + segment += glob[i]; continue; } @@ -252,7 +256,7 @@ export function _globToRegExp( continue; } - segment += regExpEscapeChars.includes(glob[i]!) + segment += REG_EXP_ESCAPE_CHARS.includes(glob[i]! as RegExpEscapeChar) ? `\\${glob[i]}` : glob[i]; } @@ -262,7 +266,9 @@ export function _globToRegExp( // Parse failure. Take all characters from this segment literally. segment = ""; for (const c of glob.slice(j, i)) { - segment += regExpEscapeChars.includes(c) ? `\\${c}` : c; + segment += REG_EXP_ESCAPE_CHARS.includes(c as RegExpEscapeChar) + ? `\\${c}` + : c; endsWithSep = false; } } @@ -276,10 +282,6 @@ export function _globToRegExp( // Terminates with `i` at the start of the next segment. while (c.seps.includes(glob[i]!)) i++; - // Check that the next value of `j` is indeed higher than the current value. - if (!(i > j)) { - throw new Error("Assertion failure: i > j (potential infinite loop)"); - } j = i; } diff --git a/path/_common/normalize_string_test.ts b/path/_common/normalize_string_test.ts new file mode 100644 index 000000000000..8ea883a7dbcf --- /dev/null +++ b/path/_common/normalize_string_test.ts @@ -0,0 +1,26 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { assertEquals } from "@std/assert"; +import { CHAR_FORWARD_SLASH } from "./constants.ts"; +import { normalizeString } from "./normalize_string.ts"; + +function isSeparator(code: number): boolean { + return code === CHAR_FORWARD_SLASH; +} + +Deno.test("normalizeSring()", () => { + assertEquals(normalizeString("", true, "/", isSeparator), ""); + assertEquals(normalizeString("", false, "/", isSeparator), ""); + assertEquals(normalizeString("a/../b", true, "/", isSeparator), "b"); + assertEquals(normalizeString("foo/bar/", true, "/", isSeparator), "foo/bar"); + assertEquals(normalizeString("/foo/bar", true, "/", isSeparator), "foo/bar"); + assertEquals(normalizeString("./foo/bar", true, "/", isSeparator), "foo/bar"); + assertEquals( + normalizeString("../foo/bar/baz/", true, "/", isSeparator), + "../foo/bar/baz", + ); + assertEquals( + normalizeString("/foo/../../bar", true, "/", isSeparator), + "../bar", + ); +}); diff --git a/path/_common/to_file_url_test.ts b/path/_common/to_file_url_test.ts new file mode 100644 index 000000000000..a098bc9dcd1c --- /dev/null +++ b/path/_common/to_file_url_test.ts @@ -0,0 +1,21 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { assertEquals } from "@std/assert"; +import { encodeWhitespace } from "./to_file_url.ts"; + +Deno.test("encodeWhitespace()", () => { + assertEquals(encodeWhitespace("foo"), "foo"); + assertEquals(encodeWhitespace("foo\tbar"), "foo%09bar"); + assertEquals(encodeWhitespace("foo\nbar"), "foo%0Abar"); + assertEquals(encodeWhitespace("foo\vbar"), "foo%0Bbar"); + assertEquals(encodeWhitespace("foo\fbar"), "foo%0Cbar"); + assertEquals(encodeWhitespace("foo\rbar"), "foo%0Dbar"); + assertEquals(encodeWhitespace("foo bar"), "foo%20bar"); + assertEquals(encodeWhitespace("foo\u0009bar"), "foo%09bar"); + assertEquals(encodeWhitespace("foo\u000Abar"), "foo%0Abar"); + assertEquals(encodeWhitespace("foo\u000Bbar"), "foo%0Bbar"); + assertEquals(encodeWhitespace("foo\u000Cbar"), "foo%0Cbar"); + assertEquals(encodeWhitespace("foo\u000Dbar"), "foo%0Dbar"); + assertEquals(encodeWhitespace("foo\u0020bar"), "foo%20bar"); + assertEquals(encodeWhitespace("foo\ufeffbar"), "foobar"); +});