From affd06d05cc756df33854bc1d56e2284ff22b22d Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Fri, 7 Jul 2023 15:10:33 -0700 Subject: Update types, partially fix `typecheck` (#3551) * Update types * Remove caret --- test/js/node/watch/fs.watch.test.js | 522 -------------------------- test/js/node/watch/fs.watch.test.ts | 522 ++++++++++++++++++++++++++ test/js/third_party/socket.io/support/util.ts | 1 + test/js/web/html/FormData.test.ts | 19 +- test/js/web/html/URLSearchParams.test.ts | 5 + 5 files changed, 537 insertions(+), 532 deletions(-) delete mode 100644 test/js/node/watch/fs.watch.test.js create mode 100644 test/js/node/watch/fs.watch.test.ts (limited to 'test/js') diff --git a/test/js/node/watch/fs.watch.test.js b/test/js/node/watch/fs.watch.test.js deleted file mode 100644 index faf6a8546..000000000 --- a/test/js/node/watch/fs.watch.test.js +++ /dev/null @@ -1,522 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { tempDirWithFiles, bunRun, bunRunAsScript } from "harness"; -import { pathToFileURL } from "bun"; - -import { describe, expect, test } from "bun:test"; -// Because macOS (and possibly other operating systems) can return a watcher -// before it is actually watching, we need to repeat the operation to avoid -// a race condition. -function repeat(fn) { - const interval = setInterval(fn, 20); - return interval; -} -const encodingFileName = `新建文夹件.txt`; -const testDir = tempDirWithFiles("watch", { - "watch.txt": "hello", - "relative.txt": "hello", - "abort.txt": "hello", - "url.txt": "hello", - "close.txt": "hello", - "close-close.txt": "hello", - "sym-sync.txt": "hello", - "sym.txt": "hello", - [encodingFileName]: "hello", -}); - -describe("fs.watch", () => { - test("non-persistent watcher should not block the event loop", done => { - try { - // https://github.com/joyent/node/issues/2293 - non-persistent watcher should not block the event loop - bunRun(path.join(import.meta.dir, "fixtures", "persistent.js")); - done(); - } catch (e) { - done(e); - } - }); - - test("watcher should close and not block the event loop", done => { - try { - bunRun(path.join(import.meta.dir, "fixtures", "close.js")); - done(); - } catch (e) { - done(e); - } - }); - - test("unref watcher should not block the event loop", done => { - try { - bunRun(path.join(import.meta.dir, "fixtures", "unref.js")); - done(); - } catch (e) { - done(e); - } - }); - - test("should work with relative files", done => { - try { - bunRunAsScript(testDir, path.join(import.meta.dir, "fixtures", "relative.js")); - done(); - } catch (e) { - done(e); - } - }); - - test("add file/folder to folder", done => { - let count = 0; - const root = path.join(testDir, "add-directory"); - try { - fs.mkdirSync(root); - } catch {} - let err = undefined; - const watcher = fs.watch(root, { signal: AbortSignal.timeout(3000) }); - watcher.on("change", (event, filename) => { - count++; - try { - expect(event).toBe("rename"); - expect(["new-file.txt", "new-folder.txt"]).toContain(filename); - if (count >= 2) { - watcher.close(); - } - } catch (e) { - err = e; - watcher.close(); - } - }); - - watcher.on("error", e => (err = e)); - watcher.on("close", () => { - clearInterval(interval); - done(err); - }); - - const interval = repeat(() => { - fs.writeFileSync(path.join(root, "new-file.txt"), "hello"); - fs.mkdirSync(path.join(root, "new-folder.txt")); - fs.rmdirSync(path.join(root, "new-folder.txt")); - }); - }); - - test("add file/folder to subfolder", done => { - let count = 0; - const root = path.join(testDir, "add-subdirectory"); - try { - fs.mkdirSync(root); - } catch {} - const subfolder = path.join(root, "subfolder"); - fs.mkdirSync(subfolder); - const watcher = fs.watch(root, { recursive: true, signal: AbortSignal.timeout(3000) }); - let err = undefined; - watcher.on("change", (event, filename) => { - const basename = path.basename(filename); - - if (basename === "subfolder") return; - count++; - try { - expect(event).toBe("rename"); - expect(["new-file.txt", "new-folder.txt"]).toContain(basename); - if (count >= 2) { - watcher.close(); - } - } catch (e) { - err = e; - watcher.close(); - } - }); - watcher.on("error", e => (err = e)); - watcher.on("close", () => { - clearInterval(interval); - done(err); - }); - - const interval = repeat(() => { - fs.writeFileSync(path.join(subfolder, "new-file.txt"), "hello"); - fs.mkdirSync(path.join(subfolder, "new-folder.txt")); - fs.rmdirSync(path.join(subfolder, "new-folder.txt")); - }); - }); - - test("should emit event when file is deleted", done => { - const testsubdir = tempDirWithFiles("subdir", { - "deleted.txt": "hello", - }); - const filepath = path.join(testsubdir, "deleted.txt"); - let err = undefined; - const watcher = fs.watch(testsubdir, function (event, filename) { - try { - expect(event).toBe("rename"); - expect(filename).toBe("deleted.txt"); - } catch (e) { - err = e; - } finally { - clearInterval(interval); - watcher.close(); - } - }); - - watcher.once("close", () => { - done(err); - }); - - const interval = repeat(() => { - fs.rmSync(filepath, { force: true }); - const fd = fs.openSync(filepath, "w"); - fs.closeSync(fd); - }); - }); - - test("should emit 'change' event when file is modified", done => { - const filepath = path.join(testDir, "watch.txt"); - - const watcher = fs.watch(filepath); - let err = undefined; - watcher.on("change", function (event, filename) { - try { - expect(event).toBe("change"); - expect(filename).toBe("watch.txt"); - } catch (e) { - err = e; - } finally { - clearInterval(interval); - watcher.close(); - } - }); - - watcher.once("close", () => { - done(err); - }); - - const interval = repeat(() => { - fs.writeFileSync(filepath, "world"); - }); - }); - - test("should error on invalid path", done => { - try { - fs.watch(path.join(testDir, "404.txt")); - done(new Error("should not reach here")); - } catch (err) { - expect(err).toBeInstanceOf(Error); - expect(err.code).toBe("ENOENT"); - expect(err.syscall).toBe("watch"); - done(); - } - }); - - const encodings = ["utf8", "buffer", "hex", "ascii", "base64", "utf16le", "ucs2", "latin1", "binary"]; - - test(`should work with encodings ${encodings.join(", ")}`, async () => { - const watchers = []; - const filepath = path.join(testDir, encodingFileName); - - const promises = []; - encodings.forEach(name => { - const encoded_filename = - name !== "buffer" ? Buffer.from(encodingFileName, "utf8").toString(name) : Buffer.from(encodingFileName); - - promises.push( - new Promise((resolve, reject) => { - watchers.push( - fs.watch(filepath, { encoding: name }, (event, filename) => { - try { - expect(event).toBe("change"); - - if (name !== "buffer") { - expect(filename).toBe(encoded_filename); - } else { - expect(filename).toBeInstanceOf(Buffer); - expect(filename.toString("utf8")).toBe(encodingFileName); - } - - resolve(); - } catch (e) { - reject(e); - } - }), - ); - }), - ); - }); - - const interval = repeat(() => { - fs.writeFileSync(filepath, "world"); - }); - - try { - await Promise.all(promises); - } finally { - clearInterval(interval); - watchers.forEach(watcher => watcher.close()); - } - }); - - test("should work with url", done => { - const filepath = path.join(testDir, "url.txt"); - try { - const watcher = fs.watch(pathToFileURL(filepath)); - let err = undefined; - watcher.on("change", function (event, filename) { - try { - expect(event).toBe("change"); - expect(filename).toBe("url.txt"); - } catch (e) { - err = e; - } finally { - clearInterval(interval); - watcher.close(); - } - }); - - watcher.once("close", () => { - done(err); - }); - - const interval = repeat(() => { - fs.writeFileSync(filepath, "world"); - }); - } catch (e) { - done(e); - } - }); - - test("calling close from error event should not throw", done => { - const filepath = path.join(testDir, "close.txt"); - try { - const ac = new AbortController(); - const watcher = fs.watch(pathToFileURL(filepath), { signal: ac.signal }); - watcher.once("error", () => { - try { - watcher.close(); - done(); - } catch (e) { - done("Should not error when calling close from error event"); - } - }); - ac.abort(); - } catch (e) { - done(e); - } - }); - - test("calling close from close event should not throw", done => { - const filepath = path.join(testDir, "close-close.txt"); - try { - const ac = new AbortController(); - const watcher = fs.watch(pathToFileURL(filepath), { signal: ac.signal }); - - watcher.once("close", () => { - try { - watcher.close(); - done(); - } catch (e) { - done("Should not error when calling close from close event"); - } - }); - - ac.abort(); - } catch (e) { - done(e); - } - }); - - test("Signal aborted after creating the watcher", async () => { - const filepath = path.join(testDir, "abort.txt"); - - const ac = new AbortController(); - const promise = new Promise((resolve, reject) => { - const watcher = fs.watch(filepath, { signal: ac.signal }); - watcher.once("error", err => (err.message === "The operation was aborted." ? resolve() : reject(err))); - watcher.once("close", () => reject()); - }); - await Bun.sleep(10); - ac.abort(); - await promise; - }); - - test("Signal aborted before creating the watcher", async () => { - const filepath = path.join(testDir, "abort.txt"); - - const signal = AbortSignal.abort(); - await new Promise((resolve, reject) => { - const watcher = fs.watch(filepath, { signal }); - watcher.once("error", err => (err.message === "The operation was aborted." ? resolve() : reject(err))); - watcher.once("close", () => reject()); - }); - }); - - test("should work with symlink", async () => { - const filepath = path.join(testDir, "sym-symlink2.txt"); - await fs.promises.symlink(path.join(testDir, "sym-sync.txt"), filepath); - - const interval = repeat(() => { - fs.writeFileSync(filepath, "hello"); - }); - - const promise = new Promise((resolve, reject) => { - let timeout = null; - const watcher = fs.watch(filepath, event => { - clearTimeout(timeout); - clearInterval(interval); - try { - resolve(event); - } catch (e) { - reject(e); - } finally { - watcher.close(); - } - }); - setTimeout(() => { - clearInterval(interval); - watcher?.close(); - reject("timeout"); - }, 3000); - }); - expect(promise).resolves.toBe("change"); - }); -}); - -describe("fs.promises.watch", () => { - test("add file/folder to folder", async () => { - let count = 0; - const root = path.join(testDir, "add-promise-directory"); - try { - fs.mkdirSync(root); - } catch {} - let success = false; - let err = undefined; - try { - const ac = new AbortController(); - const watcher = fs.promises.watch(root, { signal: ac.signal }); - - const interval = repeat(() => { - fs.writeFileSync(path.join(root, "new-file.txt"), "hello"); - fs.mkdirSync(path.join(root, "new-folder.txt")); - fs.rmdirSync(path.join(root, "new-folder.txt")); - }); - - for await (const event of watcher) { - count++; - try { - expect(event.eventType).toBe("rename"); - expect(["new-file.txt", "new-folder.txt"]).toContain(event.filename); - - if (count >= 2) { - success = true; - clearInterval(interval); - ac.abort(); - } - } catch (e) { - err = e; - clearInterval(interval); - ac.abort(); - } - } - } catch (e) { - if (!success) { - throw err || e; - } - } - }); - - test("add file/folder to subfolder", async () => { - let count = 0; - const root = path.join(testDir, "add-promise-subdirectory"); - try { - fs.mkdirSync(root); - } catch {} - const subfolder = path.join(root, "subfolder"); - fs.mkdirSync(subfolder); - let success = false; - let err = undefined; - - try { - const ac = new AbortController(); - const watcher = fs.promises.watch(root, { recursive: true, signal: ac.signal }); - - const interval = repeat(() => { - fs.writeFileSync(path.join(subfolder, "new-file.txt"), "hello"); - fs.mkdirSync(path.join(subfolder, "new-folder.txt")); - fs.rmdirSync(path.join(subfolder, "new-folder.txt")); - }); - for await (const event of watcher) { - const basename = path.basename(event.filename); - if (basename === "subfolder") continue; - - count++; - try { - expect(event.eventType).toBe("rename"); - expect(["new-file.txt", "new-folder.txt"]).toContain(basename); - - if (count >= 2) { - success = true; - clearInterval(interval); - ac.abort(); - } - } catch (e) { - err = e; - clearInterval(interval); - ac.abort(); - } - } - } catch (e) { - if (!success) { - throw err || e; - } - } - }); - - test("Signal aborted after creating the watcher", async () => { - const filepath = path.join(testDir, "abort.txt"); - - const ac = new AbortController(); - const watcher = fs.promises.watch(filepath, { signal: ac.signal }); - - const promise = (async () => { - try { - for await (const _ of watcher); - } catch (e) { - expect(e.message).toBe("The operation was aborted."); - } - })(); - await Bun.sleep(10); - ac.abort(); - await promise; - }); - - test("Signal aborted before creating the watcher", async () => { - const filepath = path.join(testDir, "abort.txt"); - - const signal = AbortSignal.abort(); - const watcher = fs.promises.watch(filepath, { signal }); - await (async () => { - try { - for await (const _ of watcher); - } catch (e) { - expect(e.message).toBe("The operation was aborted."); - } - })(); - }); - - test("should work with symlink", async () => { - const filepath = path.join(testDir, "sym-symlink.txt"); - await fs.promises.symlink(path.join(testDir, "sym.txt"), filepath); - - const watcher = fs.promises.watch(filepath); - const interval = repeat(() => { - fs.writeFileSync(filepath, "hello"); - }); - - const promise = (async () => { - try { - for await (const event of watcher) { - return event.eventType; - } - } catch (e) { - expect("unreacheable").toBe(false); - } finally { - clearInterval(interval); - } - })(); - expect(promise).resolves.toBe("change"); - }); -}); diff --git a/test/js/node/watch/fs.watch.test.ts b/test/js/node/watch/fs.watch.test.ts new file mode 100644 index 000000000..aa7959bed --- /dev/null +++ b/test/js/node/watch/fs.watch.test.ts @@ -0,0 +1,522 @@ +import fs, { FSWatcher } from "node:fs"; +import path from "path"; +import { tempDirWithFiles, bunRun, bunRunAsScript } from "harness"; +import { pathToFileURL } from "bun"; + +import { describe, expect, test } from "bun:test"; +// Because macOS (and possibly other operating systems) can return a watcher +// before it is actually watching, we need to repeat the operation to avoid +// a race condition. +function repeat(fn: any) { + const interval = setInterval(fn, 20); + return interval; +} +const encodingFileName = `新建文夹件.txt`; +const testDir = tempDirWithFiles("watch", { + "watch.txt": "hello", + "relative.txt": "hello", + "abort.txt": "hello", + "url.txt": "hello", + "close.txt": "hello", + "close-close.txt": "hello", + "sym-sync.txt": "hello", + "sym.txt": "hello", + [encodingFileName]: "hello", +}); + +describe("fs.watch", () => { + test("non-persistent watcher should not block the event loop", done => { + try { + // https://github.com/joyent/node/issues/2293 - non-persistent watcher should not block the event loop + bunRun(path.join(import.meta.dir, "fixtures", "persistent.js")); + done(); + } catch (e: any) { + done(e); + } + }); + + test("watcher should close and not block the event loop", done => { + try { + bunRun(path.join(import.meta.dir, "fixtures", "close.js")); + done(); + } catch (e: any) { + done(e); + } + }); + + test("unref watcher should not block the event loop", done => { + try { + bunRun(path.join(import.meta.dir, "fixtures", "unref.js")); + done(); + } catch (e: any) { + done(e); + } + }); + + test("should work with relative files", done => { + try { + bunRunAsScript(testDir, path.join(import.meta.dir, "fixtures", "relative.js")); + done(); + } catch (e: any) { + done(e); + } + }); + + test("add file/folder to folder", done => { + let count = 0; + const root = path.join(testDir, "add-directory"); + try { + fs.mkdirSync(root); + } catch {} + let err: Error | undefined = undefined; + const watcher = fs.watch(root, { signal: AbortSignal.timeout(3000) }); + watcher.on("change", (event, filename) => { + count++; + try { + expect(event).toBe("rename"); + expect(["new-file.txt", "new-folder.txt"]).toContain(filename); + if (count >= 2) { + watcher.close(); + } + } catch (e: any) { + err = e; + watcher.close(); + } + }); + + watcher.on("error", e => (err = e)); + watcher.on("close", () => { + clearInterval(interval); + done(err); + }); + + const interval = repeat(() => { + fs.writeFileSync(path.join(root, "new-file.txt"), "hello"); + fs.mkdirSync(path.join(root, "new-folder.txt")); + fs.rmdirSync(path.join(root, "new-folder.txt")); + }); + }); + + test("add file/folder to subfolder", done => { + let count = 0; + const root = path.join(testDir, "add-subdirectory"); + try { + fs.mkdirSync(root); + } catch {} + const subfolder = path.join(root, "subfolder"); + fs.mkdirSync(subfolder); + const watcher = fs.watch(root, { recursive: true, signal: AbortSignal.timeout(3000) }); + let err: Error | undefined = undefined; + watcher.on("change", (event, filename) => { + const basename = path.basename(filename as string); + + if (basename === "subfolder") return; + count++; + try { + expect(event).toBe("rename"); + expect(["new-file.txt", "new-folder.txt"]).toContain(basename); + if (count >= 2) { + watcher.close(); + } + } catch (e: any) { + err = e; + watcher.close(); + } + }); + watcher.on("error", e => (err = e)); + watcher.on("close", () => { + clearInterval(interval); + done(err); + }); + + const interval = repeat(() => { + fs.writeFileSync(path.join(subfolder, "new-file.txt"), "hello"); + fs.mkdirSync(path.join(subfolder, "new-folder.txt")); + fs.rmdirSync(path.join(subfolder, "new-folder.txt")); + }); + }); + + test("should emit event when file is deleted", done => { + const testsubdir = tempDirWithFiles("subdir", { + "deleted.txt": "hello", + }); + const filepath = path.join(testsubdir, "deleted.txt"); + let err: Error | undefined = undefined; + const watcher = fs.watch(testsubdir, function (event, filename) { + try { + expect(event).toBe("rename"); + expect(filename).toBe("deleted.txt"); + } catch (e: any) { + err = e; + } finally { + clearInterval(interval); + watcher.close(); + } + }); + + watcher.once("close", () => { + done(err); + }); + + const interval = repeat(() => { + fs.rmSync(filepath, { force: true }); + const fd = fs.openSync(filepath, "w"); + fs.closeSync(fd); + }); + }); + + test("should emit 'change' event when file is modified", done => { + const filepath = path.join(testDir, "watch.txt"); + + const watcher = fs.watch(filepath); + let err: Error | undefined = undefined; + watcher.on("change", function (event, filename) { + try { + expect(event).toBe("change"); + expect(filename).toBe("watch.txt"); + } catch (e: any) { + err = e; + } finally { + clearInterval(interval); + watcher.close(); + } + }); + + watcher.once("close", () => { + done(err); + }); + + const interval = repeat(() => { + fs.writeFileSync(filepath, "world"); + }); + }); + + test("should error on invalid path", done => { + try { + fs.watch(path.join(testDir, "404.txt")); + done(new Error("should not reach here")); + } catch (err: any) { + expect(err).toBeInstanceOf(Error); + expect(err.code).toBe("ENOENT"); + expect(err.syscall).toBe("watch"); + done(); + } + }); + + const encodings = ["utf8", "buffer", "hex", "ascii", "base64", "utf16le", "ucs2", "latin1", "binary"] as const; + + test(`should work with encodings ${encodings.join(", ")}`, async () => { + const watchers: FSWatcher[] = []; + const filepath = path.join(testDir, encodingFileName); + + const promises: Promise[] = []; + encodings.forEach(name => { + const encoded_filename = + name !== "buffer" ? Buffer.from(encodingFileName, "utf8").toString(name) : Buffer.from(encodingFileName); + + promises.push( + new Promise((resolve, reject) => { + watchers.push( + fs.watch(filepath, { encoding: name }, (event, filename) => { + try { + expect(event).toBe("change"); + + if (name !== "buffer") { + expect(filename).toBe(encoded_filename); + } else { + expect(filename).toBeInstanceOf(Buffer); + expect((filename as any as Buffer)!.toString("utf8")).toBe(encodingFileName); + } + + resolve(undefined); + } catch (e: any) { + reject(e); + } + }), + ); + }), + ); + }); + + const interval = repeat(() => { + fs.writeFileSync(filepath, "world"); + }); + + try { + await Promise.all(promises); + } finally { + clearInterval(interval); + watchers.forEach(watcher => watcher.close()); + } + }); + + test("should work with url", done => { + const filepath = path.join(testDir, "url.txt"); + try { + const watcher = fs.watch(pathToFileURL(filepath)); + let err: Error | undefined = undefined; + watcher.on("change", function (event, filename) { + try { + expect(event).toBe("change"); + expect(filename).toBe("url.txt"); + } catch (e: any) { + err = e; + } finally { + clearInterval(interval); + watcher.close(); + } + }); + + watcher.once("close", () => { + done(err); + }); + + const interval = repeat(() => { + fs.writeFileSync(filepath, "world"); + }); + } catch (e: any) { + done(e); + } + }); + + test("calling close from error event should not throw", done => { + const filepath = path.join(testDir, "close.txt"); + try { + const ac = new AbortController(); + const watcher = fs.watch(pathToFileURL(filepath), { signal: ac.signal }); + watcher.once("error", () => { + try { + watcher.close(); + done(); + } catch (e: any) { + done("Should not error when calling close from error event"); + } + }); + ac.abort(); + } catch (e: any) { + done(e); + } + }); + + test("calling close from close event should not throw", done => { + const filepath = path.join(testDir, "close-close.txt"); + try { + const ac = new AbortController(); + const watcher = fs.watch(pathToFileURL(filepath), { signal: ac.signal }); + + watcher.once("close", () => { + try { + watcher.close(); + done(); + } catch (e: any) { + done("Should not error when calling close from close event"); + } + }); + + ac.abort(); + } catch (e: any) { + done(e); + } + }); + + test("Signal aborted after creating the watcher", async () => { + const filepath = path.join(testDir, "abort.txt"); + + const ac = new AbortController(); + const promise = new Promise((resolve, reject) => { + const watcher = fs.watch(filepath, { signal: ac.signal }); + watcher.once("error", err => (err.message === "The operation was aborted." ? resolve(undefined) : reject(err))); + watcher.once("close", () => reject()); + }); + await Bun.sleep(10); + ac.abort(); + await promise; + }); + + test("Signal aborted before creating the watcher", async () => { + const filepath = path.join(testDir, "abort.txt"); + + const signal = AbortSignal.abort(); + await new Promise((resolve, reject) => { + const watcher = fs.watch(filepath, { signal }); + watcher.once("error", err => (err.message === "The operation was aborted." ? resolve(undefined) : reject(err))); + watcher.once("close", () => reject()); + }); + }); + + test("should work with symlink", async () => { + const filepath = path.join(testDir, "sym-symlink2.txt"); + await fs.promises.symlink(path.join(testDir, "sym-sync.txt"), filepath); + + const interval = repeat(() => { + fs.writeFileSync(filepath, "hello"); + }); + + const promise = new Promise((resolve, reject) => { + let timeout: any = null; + const watcher = fs.watch(filepath, event => { + clearTimeout(timeout); + clearInterval(interval); + try { + resolve(event); + } catch (e: any) { + reject(e); + } finally { + watcher.close(); + } + }); + setTimeout(() => { + clearInterval(interval); + watcher?.close(); + reject("timeout"); + }, 3000); + }); + expect(promise).resolves.toBe("change"); + }); +}); + +describe("fs.promises.watch", () => { + test("add file/folder to folder", async () => { + let count = 0; + const root = path.join(testDir, "add-promise-directory"); + try { + fs.mkdirSync(root); + } catch {} + let success = false; + let err: Error | undefined = undefined; + try { + const ac = new AbortController(); + const watcher = fs.promises.watch(root, { signal: ac.signal }); + + const interval = repeat(() => { + fs.writeFileSync(path.join(root, "new-file.txt"), "hello"); + fs.mkdirSync(path.join(root, "new-folder.txt")); + fs.rmdirSync(path.join(root, "new-folder.txt")); + }); + + for await (const event of watcher) { + count++; + try { + expect(event.eventType).toBe("rename"); + expect(["new-file.txt", "new-folder.txt"]).toContain(event.filename); + + if (count >= 2) { + success = true; + clearInterval(interval); + ac.abort(); + } + } catch (e: any) { + err = e; + clearInterval(interval); + ac.abort(); + } + } + } catch (e: any) { + if (!success) { + throw err || e; + } + } + }); + + test("add file/folder to subfolder", async () => { + let count = 0; + const root = path.join(testDir, "add-promise-subdirectory"); + try { + fs.mkdirSync(root); + } catch {} + const subfolder = path.join(root, "subfolder"); + fs.mkdirSync(subfolder); + let success = false; + let err: Error | undefined = undefined; + + try { + const ac = new AbortController(); + const watcher = fs.promises.watch(root, { recursive: true, signal: ac.signal }); + + const interval = repeat(() => { + fs.writeFileSync(path.join(subfolder, "new-file.txt"), "hello"); + fs.mkdirSync(path.join(subfolder, "new-folder.txt")); + fs.rmdirSync(path.join(subfolder, "new-folder.txt")); + }); + for await (const event of watcher) { + const basename = path.basename(event.filename!); + if (basename === "subfolder") continue; + + count++; + try { + expect(event.eventType).toBe("rename"); + expect(["new-file.txt", "new-folder.txt"]).toContain(basename); + + if (count >= 2) { + success = true; + clearInterval(interval); + ac.abort(); + } + } catch (e: any) { + err = e; + clearInterval(interval); + ac.abort(); + } + } + } catch (e: any) { + if (!success) { + throw err || e; + } + } + }); + + test("Signal aborted after creating the watcher", async () => { + const filepath = path.join(testDir, "abort.txt"); + + const ac = new AbortController(); + const watcher = fs.promises.watch(filepath, { signal: ac.signal }); + + const promise = (async () => { + try { + for await (const _ of watcher); + } catch (e: any) { + expect(e.message).toBe("The operation was aborted."); + } + })(); + await Bun.sleep(10); + ac.abort(); + await promise; + }); + + test("Signal aborted before creating the watcher", async () => { + const filepath = path.join(testDir, "abort.txt"); + + const signal = AbortSignal.abort(); + const watcher = fs.promises.watch(filepath, { signal }); + await (async () => { + try { + for await (const _ of watcher); + } catch (e: any) { + expect(e.message).toBe("The operation was aborted."); + } + })(); + }); + + test("should work with symlink", async () => { + const filepath = path.join(testDir, "sym-symlink.txt"); + await fs.promises.symlink(path.join(testDir, "sym.txt"), filepath); + + const watcher = fs.promises.watch(filepath); + const interval = repeat(() => { + fs.writeFileSync(filepath, "hello"); + }); + + const promise = (async () => { + try { + for await (const event of watcher) { + return event.eventType; + } + } catch (e: any) { + expect("unreacheable").toBe(false); + } finally { + clearInterval(interval); + } + })(); + expect(promise).resolves.toBe("change"); + }); +}); diff --git a/test/js/third_party/socket.io/support/util.ts b/test/js/third_party/socket.io/support/util.ts index 597b40d65..b5f515568 100644 --- a/test/js/third_party/socket.io/support/util.ts +++ b/test/js/third_party/socket.io/support/util.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import type { Server } from "socket.io"; import request from "supertest"; diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts index cbaf5aaa7..45b4f2f5a 100644 --- a/test/js/web/html/FormData.test.ts +++ b/test/js/web/html/FormData.test.ts @@ -301,17 +301,18 @@ describe("FormData", () => { expect(await (body.get("foo") as Blob).text()).toBe("baz"); server.stop(true); }); - + type FetchReqArgs = [request: Request, init?: RequestInit]; + type FetchURLArgs = [url: string | URL | Request, init?: FetchRequestInit]; for (let useRequestConstructor of [true, false]) { describe(useRequestConstructor ? "Request constructor" : "fetch()", () => { - function send(args: Parameters) { + function send(args: FetchReqArgs | FetchURLArgs) { if (useRequestConstructor) { - return fetch(new Request(...args)); + return fetch(new Request(...(args as FetchReqArgs))); } else { - return fetch(...args); + return fetch(...(args as FetchURLArgs)); } } - for (let headers of [{}, undefined, { headers: { X: "Y" } }]) { + for (let headers of [{} as {}, undefined, { headers: { X: "Y" } }]) { describe("headers: " + Bun.inspect(headers).replaceAll(/([\n ])/gim, ""), () => { it("send on HTTP server with FormData & Blob (roundtrip)", async () => { let contentType = ""; @@ -330,11 +331,10 @@ describe("FormData", () => { form.append("bar", "baz"); // @ts-ignore - const reqBody = [ + const reqBody: FetchURLArgs = [ `http://${server.hostname}:${server.port}`, { body: form, - headers, method: "POST", }, @@ -364,7 +364,6 @@ describe("FormData", () => { form.append("foo", file); form.append("bar", "baz"); - // @ts-ignore const reqBody = [ `http://${server.hostname}:${server.port}`, { @@ -374,7 +373,7 @@ describe("FormData", () => { method: "POST", }, ]; - const res = await send(reqBody); + const res = await send(reqBody as FetchURLArgs); const body = await res.formData(); expect(await (body.get("foo") as Blob).text()).toBe(text); expect(contentType).toContain("multipart/form-data"); @@ -410,7 +409,7 @@ describe("FormData", () => { method: "POST", }, ]; - const res = await send(reqBody); + const res = await send(reqBody as FetchURLArgs); const body = await res.formData(); expect(contentType).toContain("multipart/form-data"); expect(body.get("foo")).toBe("boop"); diff --git a/test/js/web/html/URLSearchParams.test.ts b/test/js/web/html/URLSearchParams.test.ts index 120bb2321..41c42c25d 100644 --- a/test/js/web/html/URLSearchParams.test.ts +++ b/test/js/web/html/URLSearchParams.test.ts @@ -7,15 +7,20 @@ describe("URLSearchParams", () => { params.append("foo", "bar"); params.append("foo", "boop"); params.append("bar", "baz"); + // @ts-ignore expect(params.length).toBe(3); params.delete("foo"); + // @ts-ignore expect(params.length).toBe(1); params.append("foo", "bar"); + // @ts-ignore expect(params.length).toBe(2); params.delete("foo"); params.delete("foo"); + // @ts-ignore expect(params.length).toBe(1); params.delete("bar"); + // @ts-ignore expect(params.length).toBe(0); }); -- cgit v1.2.3