<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>

<head>
  <meta charset="utf-8">
  <title>PathUtils tests</title>
</head>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/WorkerHandler.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<script>
  "use strict";

  const { AppConstants } = ChromeUtils.import(
    "resource://gre/modules/AppConstants.jsm"
  );
  const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");

  const UNRECOGNIZED_PATH = /Could not initialize path: NS_ERROR_FILE_UNRECOGNIZED_PATH/;
  const SPLIT_RELATIVE_ABSOLUTE = /PathUtils.splitRelative requires a relative path/;
  const SPLIT_RELATIVE_EMPTY = /PathUtils.splitRelative: Empty directory components \(""\) not allowed by options/;
  const SPLIT_RELATIVE_PARENT = /PathUtils.splitRelative: Parent directory components \("\.\."\) not allowed by options/;
  const SPLIT_RELATIVE_CURRENT = /PathUtils.splitRelative: Current directory components \("\."\) not allowed by options/;
  const EMPTY_PATH = /PathUtils does not support empty paths/;
  const JOIN = /Could not append to path/;

  add_task(function test_filename() {
    Assert.throws(
      () => PathUtils.filename(""),
      EMPTY_PATH,
      "PathUtils.filename() does not support empty paths"
    );
    Assert.throws(
      () => PathUtils.filename("foo.txt"),
      UNRECOGNIZED_PATH,
      "PathUtils.filename() does not support relative paths"
    );

    if (Services.appinfo.OS === "WINNT") {
      is(
        PathUtils.filename("C:"),
        "C:",
        "PathUtils.filename() with a drive path"
      );
      is(
        PathUtils.filename("C:\\"),
        "C:",
        "PathUtils.filename() with a drive path"
      );
      is(
        PathUtils.filename("C:\\Windows"),
        "Windows",
        "PathUtils.filename() with a path with 2 components"
      );
      is(
        PathUtils.filename("C:\\Windows\\"),
        "Windows",
        "PathUtils.filename() with a path with 2 components and a trailing slash"
      );
      is(
        PathUtils.filename("C:\\Windows\\System32"),
        "System32",
        "PathUtils.filename() with a path with 3 components"
      );
      is(
        PathUtils.filename("\\\\server"),
        "\\\\server",
        "PathUtils.filename() with a UNC server path"
      );
      is(
        PathUtils.filename("C:\\file.dat"),
        "file.dat",
        "PathUtils.filename() with a file path"
      );
    } else {
      is(
        PathUtils.filename("/"),
        "/",
        "PathUtils.filename() with a root path"
      );
      is(
        PathUtils.filename("/usr/"),
        "usr",
        "PathUtils.filename() with a non-root path"
      );
      is(
        PathUtils.filename("/usr/lib/libfoo.so"),
        "libfoo.so",
        "PathUtils.filename() with a path with 3 components"
      );
    }
  });

  add_task(function test_parent() {
    Assert.throws(
      () => PathUtils.parent("."),
      UNRECOGNIZED_PATH,
      "PathUtils.parent() does not support relative paths"
    );
    Assert.throws(
      () => PathUtils.parent(""),
      EMPTY_PATH,
      "PathUtils.parent() does not support empty paths"
    );

    if (Services.appinfo.OS === "WINNT") {
      is(
        PathUtils.parent("C:"),
        null,
        "PathUtils.parent() with a drive path"
      );
      is(
        PathUtils.parent("\\\\server"),
        null,
        "PathUtils.parent() with a UNC server path"
      );
      is(
        PathUtils.parent("\\\\server\\foo"),
        "\\\\server",
        "PathUtils.parent() with a UNC server path and child component"
      );

      Assert.throws(
        () => PathUtils.parent("C:", -1),
        /^NotSupportedError: PathUtils.parent: A depth of at least 1 is required/,
        "PathUtils.parent() with a negative depth throws"
      );
      Assert.throws(
        () => PathUtils.parent("C:", 0),
        /^NotSupportedError: PathUtils.parent: A depth of at least 1 is required/,
        "PathUtils.parent() with a zero depth throws"
      );

      {
        const path = "C:\\Users\\User\\AppData\\Local\\Mozilla\\Firefox\\Profiles\\foo.default";

        const expected = [
          "C:\\Users\\User\\AppData\\Local\\Mozilla\\Firefox\\Profiles",
          "C:\\Users\\User\\AppData\\Local\\Mozilla\\Firefox",
          "C:\\Users\\User\\AppData\\Local\\Mozilla",
          "C:\\Users\\User\\AppData\\Local",
          "C:\\Users\\User\\AppData",
          "C:\\Users\\User",
          "C:\\Users",
          "C:",
          null,
        ];

        for (const [i, parent] of expected.entries()) {
          is(PathUtils.parent(path, i + 1), parent, `PathUtils.parent() with depth=${i + 1}`)
        }
      }
    } else {
      is(
        PathUtils.parent("/"),
        null,
        "PathUtils.parent() with a root path"
      );
      is(
        PathUtils.parent("/var"),
        "/",
        "PathUtils.parent() with a 2 component path"
      );
      is(
        PathUtils.parent("/var/run"),
        "/var",
        "PathUtils.parent() with a 3 component path"
      );

      Assert.throws(
        () => PathUtils.parent("/", -1),
        /^NotSupportedError: PathUtils.parent: A depth of at least 1 is required/,
        "PathUtils.parent() with a negative depth throws"
      );
      Assert.throws(
        () => PathUtils.parent("/", 0),
        /^NotSupportedError: PathUtils.parent: A depth of at least 1 is required/,
        "PathUtils.parent() with a zero depth throws"
      );

      {
        const path = "/home/user/.mozilla/firefox/foo.default";
        const expected = [
          "/home/user/.mozilla/firefox",
          "/home/user/.mozilla",
          "/home/user",
          "/home",
          "/",
          null,
        ];

        for (const [i, parent] of expected.entries()) {
          is(
            PathUtils.parent(path, i + 1),
            parent,
            `PathUtils.parent() with depth=${i + 1}`
          );
        }
      }
    }
  });

  add_task(function test_join() {
    Assert.throws(
      () => PathUtils.join(),
      EMPTY_PATH,
      "PathUtils.join() does not support empty paths"
    );
    Assert.throws(
      () => PathUtils.join(""),
      EMPTY_PATH,
      "PathUtils.join() does not support empty paths"
    );
    Assert.throws(
      () => PathUtils.join("foo", "bar"),
      UNRECOGNIZED_PATH,
      "PathUtils.join() does not support relative paths"
    );
    Assert.throws(
      () => PathUtils.join("."),
      UNRECOGNIZED_PATH,
      "PathUtils.join() does not support relative paths"
    );

    if (Services.appinfo.OS === "WINNT") {
      is(
        PathUtils.join("C:"),
        "C:",
        "PathUtils.join() with a single path"
      );
      is(
        PathUtils.join("C:\\Windows", "System32"),
        "C:\\Windows\\System32",
        "PathUtils.join() with a 2 component path and an additional component"
      );
      is(
        PathUtils.join("C:", "Users", "Example"),
        "C:\\Users\\Example",
        "PathUtils.join() with a root path and two additional components"
      );
      is(
        PathUtils.join("\\\\server", "Files", "Example.dat"),
        "\\\\server\\Files\\Example.dat",
        "PathUtils.join() with a server path"
      );
    } else {
      is(
        PathUtils.join("/"),
        "/",
        "PathUtils.join() with a root path"
      );
      is(
        PathUtils.join("/usr", "lib"),
        "/usr/lib",
        "PathUtils.join() with a 2 component path and an additional component"
      );
      is(
        PathUtils.join("/", "home", "example"),
        "/home/example",
        "PathUtils.join() with a root path and two additional components"
      );
    }
  });

  add_task(function test_join_relative() {
    if (Services.appinfo.OS === "WINNT") {
      is(
        PathUtils.joinRelative("C:", ""),
        "C:",
        "PathUtils.joinRelative() with an empty relative path"
      );

      is(
        PathUtils.joinRelative("C:", "foo\\bar\\baz"),
        "C:\\foo\\bar\\baz",
        "PathUtils.joinRelative() with a relative path containing path separators"
      );
    } else {
      is(
        PathUtils.joinRelative("/", ""),
        "/",
        "PathUtils.joinRelative() with an empty relative path"
      );

      is(
        PathUtils.joinRelative("/", "foo/bar/baz"),
        "/foo/bar/baz",
        "PathUtils.joinRelative() with a relative path containing path separators"
      );
    }
  });

  add_task(async function test_normalize() {
    Assert.throws(
      () => PathUtils.normalize(""),
      EMPTY_PATH,
      "PathUtils.normalize() does not support empty paths"
    );
    Assert.throws(
      () => PathUtils.normalize("."),
      UNRECOGNIZED_PATH,
      "PathUtils.normalize() does not support relative paths"
    );

    if (Services.appinfo.OS === "WINNT") {
      is(
        PathUtils.normalize("C:\\\\Windows\\\\..\\\\\\.\\Users\\..\\Windows"),
        "C:\\Windows",
        "PathUtils.normalize() with a non-normalized path"
      );
    } else {
      // nsLocalFileUnix::Normalize() calls realpath, which resolves symlinks
      // and requires the file to exist.
      //
      // On Darwin, the temp directory is located in `/private/var`, which is a
      // symlink to `/var`, so we need to pre-normalize our temporary directory
      // or expected paths won't match.
      const tmpDir = PathUtils.join(
        PathUtils.normalize(PathUtils.tempDir),
        "pathutils_test"
      );

      await IOUtils.makeDirectory(tmpDir, { ignoreExisting: true });
      info(`created tmpDir ${tmpDir}`);
      SimpleTest.registerCleanupFunction(async () => {
        await IOUtils.remove(tmpDir, {
          recursive: true,
        });
      });

      await IOUtils.makeDirectory(PathUtils.join(tmpDir, "foo", "bar"), {
        createAncestors: true,
      });

      is(
        PathUtils.normalize("/"),
        "/",
        "PathUtils.normalize() with a normalized path"
      );

      is(
        PathUtils.normalize(
          PathUtils.join(
            tmpDir,
            "foo",
            ".",
            "bar",
            ".",
          )
        ),
        PathUtils.join(tmpDir, "foo", "bar"),
        "PathUtils.normalize() with a non-normalized path"
      );
    }
  });

  add_task(function test_split() {
    Assert.throws(
      () => PathUtils.split("foo"),
      UNRECOGNIZED_PATH,
      "PathUtils.split() does not support relative paths"
    );
    Assert.throws(
      () => PathUtils.split(""),
      EMPTY_PATH,
      "PathUtils.split() does not support empty paths"
    );

    if (Services.appinfo.OS === "WINNT") {
      Assert.deepEqual(
        PathUtils.split("C:\\Users\\Example"),
        ["C:", "Users", "Example"],
        "PathUtils.split() on an absolute path"
      );

      Assert.deepEqual(
        PathUtils.split("C:\\Users\\Example\\"),
        ["C:", "Users", "Example"],
        "PathUtils.split() on an absolute path with a trailing slash"
      );

      Assert.deepEqual(
        PathUtils.split("\\\\server\\Files\\Example.dat"),
        ["\\\\server", "Files", "Example.dat"],
        "PathUtils.split() with a server as the root"
      );
    } else {
      Assert.deepEqual(
        PathUtils.split("/home/foo"),
        ["/", "home", "foo"],
        "PathUtils.split() on absolute path"
      );

      Assert.deepEqual(
        PathUtils.split("/home/foo/"),
        ["/", "home", "foo"],
        "PathUtils.split() on absolute path with trailing slash"
      );
    }
  });

  add_task(function test_splitRelative() {
    Assert.throws(
      () => PathUtils.splitRelative(""),
      EMPTY_PATH,
      "PathUtils.splitRelative() should throw with empty path"
    );

    if (Services.appinfo.OS === "WINNT") {
      Assert.throws(
        () => PathUtils.splitRelative("C:\\"),
        SPLIT_RELATIVE_ABSOLUTE,
        "PathUtils.splitRelative() should throw with a drive path"
      );

      Assert.throws(
        () => PathUtils.splitRelative("\\\\server\\share\\"),
        SPLIT_RELATIVE_ABSOLUTE,
        "PathUtils.splitRelative() should throw with a UNC path"
      );

      Assert.throws(
        () => PathUtils.splitRelative("foo\\\\bar"),
        SPLIT_RELATIVE_EMPTY,
        "PathUtils.splitRelative() should throw with empty component by default"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo\\\\bar", { allowEmpty: true }),
        ["foo", "", "bar"],
        "PathUtils.splitRelative() with an empty component is allowed with option"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo"),
        ["foo"],
        "PathUtils.splitRelative() on a relative path with one component"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo\\"),
        ["foo"],
        "PathUtils.splitRelative() on a relative path with one component and a trailing slash"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo\\bar"),
        ["foo", "bar"],
        "PathUtils.splitRelative() on a relative path with two components"
      );
    } else {
      Assert.throws(
        () => PathUtils.splitRelative("/foo/bar"),
        SPLIT_RELATIVE_ABSOLUTE,
        "PathUtils.splitRelative() should throw with an absolute path"
      );

      Assert.throws(
        () => PathUtils.splitRelative("foo//bar"),
        SPLIT_RELATIVE_EMPTY,
        "PathUtils.splitRelative() should throw with empty component by default"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo//bar", { allowEmpty: true }),
        ["foo", "", "bar"],
        "PathUtils.splitRelative() with an empty component is allowed with option"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo"),
        ["foo"],
        "PathUtils.splitRelative() on a relative path with one component"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo/"),
        ["foo"],
        "PathUtils.splitRelative() on a relative path with one component and a trailing slash"
      );

      Assert.deepEqual(
        PathUtils.splitRelative("foo/bar"),
        ["foo", "bar"],
        "PathUtils.splitRelative() on a relative path with two components",
      );
    }

    Assert.throws(
      () => PathUtils.splitRelative("."),
      SPLIT_RELATIVE_CURRENT,
      "PathUtils.splitRelative() with a current dir component is disallowed by default"
    );

    Assert.throws(
      () => PathUtils.splitRelative(".."),
      SPLIT_RELATIVE_PARENT,
      "PathUtils.splitRelative() with a parent dir component is disallowed by default"
    );

    Assert.deepEqual(
      PathUtils.splitRelative(".", { allowCurrentDir: true }),
      ["."],
      "PathUtils.splitRelative() with a current dir component is allowed with option"
    );

    Assert.deepEqual(
      PathUtils.splitRelative("..", { allowParentDir: true }),
      [".."],
      "PathUtils.splitRelative() with a parent dir component is allowed with option"
    );
  });

  add_task(function test_toFileURI() {
    Assert.throws(
      () => PathUtils.toFileURI("."),
      UNRECOGNIZED_PATH,
      "PathUtils.toFileURI() does not support relative paths"
    );
    Assert.throws(
      () => PathUtils.toFileURI(""),
      EMPTY_PATH,
      "PathUtils.toFileURI() does not support empty paths"
    );

    if (Services.appinfo.OS === "WINNT") {
      is(
        PathUtils.toFileURI("C:\\"),
        "file:///C:/",
        "PathUtils.toFileURI() with a root path"
      );

      is(
        PathUtils.toFileURI("C:\\Windows\\"),
        "file:///C:/Windows/",
        "PathUtils.toFileURI() with a non-root directory path"
      );

      is(
        PathUtils.toFileURI("C:\\Windows\\system32\\notepad.exe"),
        "file:///C:/Windows/system32/notepad.exe",
        "PathUtils.toFileURI() with a file path"
      );
    } else {
      is(
        PathUtils.toFileURI("/"),
        "file:///",
        "PathUtils.toFileURI() with a root path"
      );

      is(
        PathUtils.toFileURI("/bin"),
        "file:///bin/",
        "PathUtils.toFileURI() with a non-root directory path"
      );

      is(
        PathUtils.toFileURI("/bin/ls"),
        "file:///bin/ls",
        "PathUtils.toFileURI() with a file path"
      );
    }
  });

  add_task(async function test_isAbsolute() {
    if (Services.appinfo.OS === "WINNT") {
      ok(PathUtils.isAbsolute("C:"), "Drive paths are absolute paths on Windows");
      ok(PathUtils.isAbsolute("C:\\Windows"), "Paths from the root are absolute paths on Windows");
      ok(!PathUtils.isAbsolute("foo"), "Paths containing a single item are not absolute paths on Windows");
      ok(!PathUtils.isAbsolute(".\\foo"), "Paths relative to the current working directory are not absolute paths on Windows");
      ok(!PathUtils.isAbsolute("..\\foo"), "Paths relative to the parent directory are not absolute paths on Windows");
    } else {
      ok(PathUtils.isAbsolute("/"), "Root paths are absolute paths");
      ok(PathUtils.isAbsolute("/home"), "Paths with a root stem are absolute paths");
      ok(!PathUtils.isAbsolute("foo"), "Paths containing a single non-root item are not absolute paths");
      ok(!PathUtils.isAbsolute("./foo"), "Paths relative to the current working directory are not absolute paths");
      ok(!PathUtils.isAbsolute("../foo"), "Paths relative to the parent directory are not absolute paths");
    }
  });

  add_task(async function test_getDirectories() {
    // See: nsAppDirectoryServiceDefs.h
    const tests = [
      ["profileDir", "ProfD"],
      ["localProfileDir", "ProfLD"],
      ["tempDir", "TmpD"],
    ];

    for (const [attrName, dirConstant] of tests) {
      const expected = Services.dirsvc.get(dirConstant, Ci.nsIFile).path;

      const attrValue = PathUtils[attrName];
      is(attrValue, expected, `PathUtils.${attrName} == Services.dirsvc.get("${dirConstant}", Ci.nsIFile).path`);
    }
  });
</script>

<body>
</body>

</html>