glob.test.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { describe, test, expect } from "bun:test"
  2. import path from "path"
  3. import fs from "fs/promises"
  4. import { Glob } from "../../src/util/glob"
  5. import { tmpdir } from "../fixture/fixture"
  6. describe("Glob", () => {
  7. describe("scan()", () => {
  8. test("finds files matching pattern", async () => {
  9. await using tmp = await tmpdir()
  10. await fs.writeFile(path.join(tmp.path, "a.txt"), "", "utf-8")
  11. await fs.writeFile(path.join(tmp.path, "b.txt"), "", "utf-8")
  12. await fs.writeFile(path.join(tmp.path, "c.md"), "", "utf-8")
  13. const results = await Glob.scan("*.txt", { cwd: tmp.path })
  14. expect(results.sort()).toEqual(["a.txt", "b.txt"])
  15. })
  16. test("returns absolute paths when absolute option is true", async () => {
  17. await using tmp = await tmpdir()
  18. await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
  19. const results = await Glob.scan("*.txt", { cwd: tmp.path, absolute: true })
  20. expect(results[0]).toBe(path.join(tmp.path, "file.txt"))
  21. })
  22. test("excludes directories by default", async () => {
  23. await using tmp = await tmpdir()
  24. await fs.mkdir(path.join(tmp.path, "subdir"))
  25. await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
  26. const results = await Glob.scan("*", { cwd: tmp.path })
  27. expect(results).toEqual(["file.txt"])
  28. })
  29. test("excludes directories when include is 'file'", async () => {
  30. await using tmp = await tmpdir()
  31. await fs.mkdir(path.join(tmp.path, "subdir"))
  32. await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
  33. const results = await Glob.scan("*", { cwd: tmp.path, include: "file" })
  34. expect(results).toEqual(["file.txt"])
  35. })
  36. test("includes directories when include is 'all'", async () => {
  37. await using tmp = await tmpdir()
  38. await fs.mkdir(path.join(tmp.path, "subdir"))
  39. await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
  40. const results = await Glob.scan("*", { cwd: tmp.path, include: "all" })
  41. expect(results.sort()).toEqual(["file.txt", "subdir"])
  42. })
  43. test("handles nested patterns", async () => {
  44. await using tmp = await tmpdir()
  45. await fs.mkdir(path.join(tmp.path, "nested"), { recursive: true })
  46. await fs.writeFile(path.join(tmp.path, "nested", "deep.txt"), "", "utf-8")
  47. const results = await Glob.scan("**/*.txt", { cwd: tmp.path })
  48. expect(results).toEqual([path.join("nested", "deep.txt")])
  49. })
  50. test("returns empty array for no matches", async () => {
  51. await using tmp = await tmpdir()
  52. const results = await Glob.scan("*.nonexistent", { cwd: tmp.path })
  53. expect(results).toEqual([])
  54. })
  55. test("does not follow symlinks by default", async () => {
  56. await using tmp = await tmpdir()
  57. await fs.mkdir(path.join(tmp.path, "realdir"))
  58. await fs.writeFile(path.join(tmp.path, "realdir", "file.txt"), "", "utf-8")
  59. await fs.symlink(path.join(tmp.path, "realdir"), path.join(tmp.path, "linkdir"))
  60. const results = await Glob.scan("**/*.txt", { cwd: tmp.path })
  61. expect(results).toEqual([path.join("realdir", "file.txt")])
  62. })
  63. test("follows symlinks when symlink option is true", async () => {
  64. await using tmp = await tmpdir()
  65. await fs.mkdir(path.join(tmp.path, "realdir"))
  66. await fs.writeFile(path.join(tmp.path, "realdir", "file.txt"), "", "utf-8")
  67. await fs.symlink(path.join(tmp.path, "realdir"), path.join(tmp.path, "linkdir"))
  68. const results = await Glob.scan("**/*.txt", { cwd: tmp.path, symlink: true })
  69. expect(results.sort()).toEqual([path.join("linkdir", "file.txt"), path.join("realdir", "file.txt")])
  70. })
  71. test("includes dotfiles when dot option is true", async () => {
  72. await using tmp = await tmpdir()
  73. await fs.writeFile(path.join(tmp.path, ".hidden"), "", "utf-8")
  74. await fs.writeFile(path.join(tmp.path, "visible"), "", "utf-8")
  75. const results = await Glob.scan("*", { cwd: tmp.path, dot: true })
  76. expect(results.sort()).toEqual([".hidden", "visible"])
  77. })
  78. test("excludes dotfiles when dot option is false", async () => {
  79. await using tmp = await tmpdir()
  80. await fs.writeFile(path.join(tmp.path, ".hidden"), "", "utf-8")
  81. await fs.writeFile(path.join(tmp.path, "visible"), "", "utf-8")
  82. const results = await Glob.scan("*", { cwd: tmp.path, dot: false })
  83. expect(results).toEqual(["visible"])
  84. })
  85. })
  86. describe("scanSync()", () => {
  87. test("finds files matching pattern synchronously", async () => {
  88. await using tmp = await tmpdir()
  89. await fs.writeFile(path.join(tmp.path, "a.txt"), "", "utf-8")
  90. await fs.writeFile(path.join(tmp.path, "b.txt"), "", "utf-8")
  91. const results = Glob.scanSync("*.txt", { cwd: tmp.path })
  92. expect(results.sort()).toEqual(["a.txt", "b.txt"])
  93. })
  94. test("respects options", async () => {
  95. await using tmp = await tmpdir()
  96. await fs.mkdir(path.join(tmp.path, "subdir"))
  97. await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
  98. const results = Glob.scanSync("*", { cwd: tmp.path, include: "all" })
  99. expect(results.sort()).toEqual(["file.txt", "subdir"])
  100. })
  101. })
  102. describe("match()", () => {
  103. test("matches simple patterns", () => {
  104. expect(Glob.match("*.txt", "file.txt")).toBe(true)
  105. expect(Glob.match("*.txt", "file.js")).toBe(false)
  106. })
  107. test("matches directory patterns", () => {
  108. expect(Glob.match("**/*.js", "src/index.js")).toBe(true)
  109. expect(Glob.match("**/*.js", "src/index.ts")).toBe(false)
  110. })
  111. test("matches dot files", () => {
  112. expect(Glob.match(".*", ".gitignore")).toBe(true)
  113. expect(Glob.match("**/*.md", ".github/README.md")).toBe(true)
  114. })
  115. test("matches brace expansion", () => {
  116. expect(Glob.match("*.{js,ts}", "file.js")).toBe(true)
  117. expect(Glob.match("*.{js,ts}", "file.ts")).toBe(true)
  118. expect(Glob.match("*.{js,ts}", "file.py")).toBe(false)
  119. })
  120. })
  121. })