diff options
author | 2022-06-22 23:21:48 -0700 | |
---|---|---|
committer | 2022-06-22 23:21:48 -0700 | |
commit | 729d445b6885f69dd2c6355f38707bd42851c791 (patch) | |
tree | f87a7c408929ea3f57bbb7ace380cf869da83c0e /test/bun.js | |
parent | 25f820c6bf1d8ec6d444ef579cc036b8c0607b75 (diff) | |
download | bun-729d445b6885f69dd2c6355f38707bd42851c791.tar.gz bun-729d445b6885f69dd2c6355f38707bd42851c791.tar.zst bun-729d445b6885f69dd2c6355f38707bd42851c791.zip |
change the directory structurejarred/rename
Diffstat (limited to 'test/bun.js')
111 files changed, 10988 insertions, 0 deletions
diff --git a/test/bun.js/atob.test.js b/test/bun.js/atob.test.js new file mode 100644 index 000000000..4945829e1 --- /dev/null +++ b/test/bun.js/atob.test.js @@ -0,0 +1,77 @@ +import { expect, it } from "bun:test"; + +function expectInvalidCharacters(val) { + try { + atob(val); + throw new Error("Expected error"); + } catch (error) { + expect(error.message).toBe("The string contains invalid characters."); + } +} + +it("atob", () => { + expect(atob("YQ==")).toBe("a"); + expect(atob("YWI=")).toBe("ab"); + expect(atob("YWJj")).toBe("abc"); + expect(atob("YWJjZA==")).toBe("abcd"); + expect(atob("YWJjZGU=")).toBe("abcde"); + expect(atob("YWJjZGVm")).toBe("abcdef"); + expect(atob("zzzz")).toBe("Ï<ó"); + expect(atob("")).toBe(""); + expect(atob(null)).toBe("ée"); + expect(atob("6ek=")).toBe("éé"); + expect(atob("6ek")).toBe("éé"); + expect(atob("gIE=")).toBe(""); + expect(atob("zz")).toBe("Ï"); + expect(atob("zzz")).toBe("Ï<"); + expect(atob("zzz=")).toBe("Ï<"); + expect(atob(" YQ==")).toBe("a"); + expect(atob("YQ==\u000a")).toBe("a"); + + try { + atob(); + } catch (error) { + expect(error.name).toBe("TypeError"); + } + expectInvalidCharacters(undefined); + expectInvalidCharacters(" abcd==="); + expectInvalidCharacters("abcd=== "); + expectInvalidCharacters("abcd ==="); + expectInvalidCharacters("тест"); + expectInvalidCharacters("z"); + expectInvalidCharacters("zzz=="); + expectInvalidCharacters("zzz==="); + expectInvalidCharacters("zzz===="); + expectInvalidCharacters("zzz====="); + expectInvalidCharacters("zzzzz"); + expectInvalidCharacters("z=zz"); + expectInvalidCharacters("="); + expectInvalidCharacters("=="); + expectInvalidCharacters("==="); + expectInvalidCharacters("===="); + expectInvalidCharacters("====="); +}); + +it("btoa", () => { + expect(btoa("a")).toBe("YQ=="); + expect(btoa("ab")).toBe("YWI="); + expect(btoa("abc")).toBe("YWJj"); + expect(btoa("abcd")).toBe("YWJjZA=="); + expect(btoa("abcde")).toBe("YWJjZGU="); + expect(btoa("abcdef")).toBe("YWJjZGVm"); + expect(typeof btoa).toBe("function"); + try { + btoa(); + throw new Error("Expected error"); + } catch (error) { + expect(error.name).toBe("TypeError"); + } + var window = "[object Window]"; + expect(btoa("")).toBe(""); + expect(btoa(null)).toBe("bnVsbA=="); + expect(btoa(undefined)).toBe("dW5kZWZpbmVk"); + expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd"); + expect(btoa("éé")).toBe("6ek="); + expect(btoa("\u0080\u0081")).toBe("gIE="); + expect(btoa(Bun)).toBe(btoa("[object Bun]")); +}); diff --git a/test/bun.js/baz.js b/test/bun.js/baz.js new file mode 100644 index 000000000..5837bb3bb --- /dev/null +++ b/test/bun.js/baz.js @@ -0,0 +1,2 @@ +// this file is used in resolve.test.js +export default {}; diff --git a/test/bun.js/buffer.test.js b/test/bun.js/buffer.test.js new file mode 100644 index 000000000..6e9a3c6b4 --- /dev/null +++ b/test/bun.js/buffer.test.js @@ -0,0 +1,304 @@ +// import { describe, it, expect, beforeEach, afterEach } from "bun:test"; +// import { gc } from "./gc"; + +// beforeEach(() => gc()); +// afterEach(() => gc()); + +// it("buffer", () => { +// var buf = new Buffer(20); +// gc(); +// // if this fails or infinitely loops, it means there is a memory issue with the JSC::Structure object +// expect(Object.keys(buf).length > 0).toBe(true); +// gc(); +// expect(buf.write("hello world ")).toBe(12); +// expect(buf.write("hello world ", "utf8")).toBe(12); + +// gc(); +// expect(buf.toString("utf8", 0, "hello world ".length)).toBe("hello world "); +// gc(); +// expect(buf.toString("base64url", 0, "hello world ".length)).toBe( +// btoa("hello world ") +// ); +// gc(); +// expect(buf instanceof Uint8Array).toBe(true); +// gc(); +// expect(buf instanceof Buffer).toBe(true); +// gc(); +// expect(buf.slice() instanceof Uint8Array).toBe(true); +// gc(); +// expect(buf.slice(0, 1) instanceof Buffer).toBe(true); +// gc(); +// expect(buf.slice(0, 1) instanceof Uint8Array).toBe(true); +// gc(); +// expect(buf.slice(0, 1) instanceof Buffer).toBe(true); +// gc(); +// }); + +// it("Buffer", () => { +// var inputs = [ +// "hello world", +// "hello world".repeat(100), +// `😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌`, +// ]; +// var good = inputs.map((a) => new TextEncoder().encode(a)); +// for (let i = 0; i < inputs.length; i++) { +// var input = inputs[i]; +// expect(new Buffer(input).toString("utf8")).toBe(inputs[i]); +// gc(); +// expect(Array.from(new Buffer(input)).join(",")).toBe(good[i].join(",")); +// gc(); +// expect(Buffer.byteLength(input)).toBe(good[i].length); +// gc(); +// expect(Buffer.from(input).byteLength).toBe(Buffer.byteLength(input)); +// } +// }); + +// it("Buffer.byteLength", () => { +// expect(Buffer.byteLength("😀😃😄😁😆😅😂🤣☺️😊😊😇")).toBe( +// new TextEncoder().encode("😀😃😄😁😆😅😂🤣☺️😊😊😇").byteLength +// ); +// }); + +// it("Buffer.isBuffer", () => { +// expect(Buffer.isBuffer(new Buffer(1))).toBe(true); +// gc(); +// expect(Buffer.isBuffer(new Buffer(0))).toBe(true); +// gc(); +// expect(Buffer.isBuffer(new Uint8Array(0))).toBe(false); +// gc(); +// expect(Buffer.isBuffer(new Uint8Array(1))).toBe(false); +// gc(); +// var a = new Uint8Array(1); +// gc(); +// expect(Buffer.isBuffer(a)).toBe(false); +// gc(); +// Buffer.toBuffer(a); +// gc(); +// expect(Buffer.isBuffer(a)).toBe(true); +// gc(); +// }); + +// it("Buffer.toBuffer throws", () => { +// const checks = [ +// [], +// {}, +// "foo", +// new Uint16Array(), +// new DataView(new Uint8Array(14).buffer), +// ]; +// for (let i = 0; i < checks.length; i++) { +// try { +// Buffer.toBuffer(checks[i]); +// expect(false).toBe(true); +// } catch (exception) { +// expect(exception.message).toBe("Expected Uint8Array"); +// } +// } +// expect(true).toBe(true); +// }); + +// it("Buffer.toBuffer works", () => { +// var array = new Uint8Array(20); +// expect(array instanceof Buffer).toBe(false); +// var buf = Buffer.toBuffer(array); +// expect(array instanceof Buffer).toBe(true); +// // if this fails or infinitely loops, it means there is a memory issue with the JSC::Structure object +// expect(Object.keys(buf).length > 0).toBe(true); + +// expect(buf.write("hello world ")).toBe(12); +// gc(); +// expect(buf.toString("utf8", 0, "hello world ".length)).toBe("hello world "); +// gc(); +// expect(buf.toString("base64url", 0, "hello world ".length)).toBe( +// btoa("hello world ") +// ); +// gc(); + +// expect(buf instanceof Uint8Array).toBe(true); +// expect(buf instanceof Buffer).toBe(true); +// expect(buf.slice() instanceof Uint8Array).toBe(true); +// expect(buf.slice(0, 1) instanceof Buffer).toBe(true); +// expect(buf.slice(0, 1) instanceof Uint8Array).toBe(true); +// expect(buf.slice(0, 1) instanceof Buffer).toBe(true); +// expect(new Buffer(buf) instanceof Buffer).toBe(true); +// expect(new Buffer(buf.buffer) instanceof Buffer).toBe(true); +// }); + +// it("writeInt", () => { +// var buf = new Buffer(1024); +// var data = new DataView(buf.buffer); +// buf.writeInt32BE(100); +// expect(data.getInt32(0, false)).toBe(100); +// buf.writeInt32BE(100); +// expect(data.getInt32(0, false)).toBe(100); +// var childBuf = buf.subarray(0, 4); +// expect(data.getInt32(0, false)).toBe(100); +// expect(childBuf.readInt32BE(0, false)).toBe(100); +// }); + +// it("Buffer.from", () => { +// expect(Buffer.from("hello world").toString("utf8")).toBe("hello world"); +// expect(Buffer.from("hello world", "ascii").toString("utf8")).toBe( +// "hello world" +// ); +// expect(Buffer.from("hello world", "latin1").toString("utf8")).toBe( +// "hello world" +// ); +// gc(); +// expect(Buffer.from([254]).join(",")).toBe("254"); +// expect(Buffer.from(123).join(",")).toBe(Uint8Array.from(123).join(",")); +// expect(Buffer.from({ length: 124 }).join(",")).toBe( +// Uint8Array.from({ length: 124 }).join(",") +// ); + +// expect(Buffer.from(new ArrayBuffer(1024), 0, 512).join(",")).toBe( +// new Uint8Array(512).join(",") +// ); + +// expect(Buffer.from(new Buffer(new ArrayBuffer(1024), 0, 512)).join(",")).toBe( +// new Uint8Array(512).join(",") +// ); +// gc(); +// }); + +// it("Buffer.equals", () => { +// var a = new Uint8Array(10); +// a[2] = 1; +// var b = new Uint8Array(10); +// b[2] = 1; +// Buffer.toBuffer(a); +// Buffer.toBuffer(b); +// expect(a.equals(b)).toBe(true); +// b[2] = 0; +// expect(a.equals(b)).toBe(false); +// }); + +// it("Buffer.compare", () => { +// var a = new Uint8Array(10); +// a[2] = 1; +// var b = new Uint8Array(10); +// b[2] = 1; +// Buffer.toBuffer(a); +// Buffer.toBuffer(b); +// expect(a.compare(b)).toBe(0); +// b[2] = 0; +// expect(a.compare(b)).toBe(1); +// expect(b.compare(a)).toBe(-1); +// }); + +// it("Buffer.copy", () => { +// var array1 = new Uint8Array(128); +// array1.fill(100); +// Buffer.toBuffer(array1); +// var array2 = new Uint8Array(128); +// array2.fill(200); +// Buffer.toBuffer(array2); +// var array3 = new Uint8Array(128); +// Buffer.toBuffer(array3); +// gc(); +// expect(array1.copy(array2)).toBe(128); +// expect(array1.join("")).toBe(array2.join("")); +// }); + +// it("Buffer.concat", () => { +// var array1 = new Uint8Array(128); +// array1.fill(100); +// var array2 = new Uint8Array(128); +// array2.fill(200); +// var array3 = new Uint8Array(128); +// array3.fill(300); +// gc(); +// expect(Buffer.concat([array1, array2, array3]).join("")).toBe( +// array1.join("") + array2.join("") + array3.join("") +// ); +// expect(Buffer.concat([array1, array2, array3], 222).length).toBe(222); +// expect( +// Buffer.concat([array1, array2, array3], 222).subarray(0, 128).join("") +// ).toBe("100".repeat(128)); +// expect( +// Buffer.concat([array1, array2, array3], 222).subarray(129, 222).join("") +// ).toBe("200".repeat(222 - 129)); +// }); + +// it("read", () => { +// var buf = new Buffer(1024); +// var data = new DataView(buf.buffer); +// function reset() { +// new Uint8Array(buf.buffer).fill(0); +// } +// data.setBigInt64(0, BigInt(1000), false); +// expect(buf.readBigInt64BE(0)).toBe(BigInt(1000)); +// reset(); + +// data.setBigInt64(0, BigInt(1000), false); +// expect(buf.readBigInt64LE(0)).toBe(BigInt(1000)); +// reset(); + +// data.setBigUint64(0, BigInt(1000), false); +// expect(buf.readBigUInt64BE(0)).toBe(BigInt(1000)); +// reset(); + +// data.setBigUint64(0, BigInt(1000), false); +// expect(buf.readBigUInt64LE(0)).toBe(BigInt(1000)); +// reset(); + +// data.setFloat64(0, 1000, false); +// expect(buf.readDoubleBE(0)).toBe(1000); +// reset(); + +// data.setFloat64(0, 1000, true); +// expect(buf.readDoubleLE(0)).toBe(1000); +// reset(); + +// data.setFloat32(0, 1000, false); +// expect(buf.readFloatBE(0)).toBe(1000); +// reset(); + +// data.setFloat32(0, 1000, true); +// expect(buf.readFloatLE(0)).toBe(1000); +// reset(); + +// data.setInt16(0, 1000, false); +// expect(buf.readInt16BE(0)).toBe(1000); +// reset(); + +// data.setInt16(0, 1000, true); +// expect(buf.readInt16LE(0)).toBe(1000); +// reset(); + +// data.setInt32(0, 1000, false); +// expect(buf.readInt32BE(0)).toBe(1000); +// reset(); + +// data.setInt32(0, 1000, true); +// expect(buf.readInt32LE(0)).toBe(1000); +// reset(); + +// data.setInt8(0, 100, false); +// expect(buf.readInt8(0)).toBe(100); +// reset(); + +// data.setUint16(0, 1000, false); +// expect(buf.readUInt16BE(0)).toBe(1000); +// reset(); + +// data.setUint16(0, 1000, true); +// expect(buf.readUInt16LE(0)).toBe(1000); +// reset(); + +// data.setUint32(0, 1000, false); +// expect(buf.readUInt32BE(0)).toBe(1000); +// reset(); + +// data.setUint32(0, 1000, true); +// expect(buf.readUInt32LE(0)).toBe(1000); +// reset(); + +// data.setUint8(0, 255, false); +// expect(buf.readUInt8(0)).toBe(255); +// reset(); + +// data.setUint8(0, 255, false); +// expect(buf.readUInt8(0)).toBe(255); +// reset(); +// }); diff --git a/test/bun.js/bun-jsc.test.js b/test/bun.js/bun-jsc.test.js new file mode 100644 index 000000000..8ee0decf2 --- /dev/null +++ b/test/bun.js/bun-jsc.test.js @@ -0,0 +1,98 @@ +import { describe, expect, it } from "bun:test"; +import { + describe as jscDescribe, + describeArray, + gcAndSweep, + fullGC, + edenGC, + heapSize, + heapStats, + memoryUsage, + getRandomSeed, + setRandomSeed, + isRope, + callerSourceOrigin, + noFTL, + noOSRExitFuzzing, + optimizeNextInvocation, + numberOfDFGCompiles, + releaseWeakRefs, + totalCompileTime, + reoptimizationRetryCount, + drainMicrotasks, + startRemoteDebugger, +} from "bun:jsc"; + +describe("bun:jsc", () => { + function count() { + var j = 0; + for (var i = 0; i < 999999; i++) { + j += i + 2; + } + + return j; + } + + it("describe", () => { + jscDescribe([]); + }); + it("describeArray", () => { + describeArray([1, 2, 3]); + }); + it("gcAndSweep", () => { + gcAndSweep(); + }); + it("fullGC", () => { + fullGC(); + }); + it("edenGC", () => { + edenGC(); + }); + it("heapSize", () => { + expect(heapSize() > 0).toBe(true); + }); + it("heapStats", () => { + heapStats(); + }); + it("memoryUsage", () => { + memoryUsage(); + }); + it("getRandomSeed", () => { + getRandomSeed(2); + }); + it("setRandomSeed", () => { + setRandomSeed(2); + }); + it("isRope", () => { + expect(isRope("a" + 123 + "b")).toBe(true); + expect(isRope("abcdefgh")).toBe(false); + }); + it("callerSourceOrigin", () => { + expect(callerSourceOrigin()).toBe(import.meta.url); + }); + it("noFTL", () => {}); + it("noOSRExitFuzzing", () => {}); + it("optimizeNextInvocation", () => { + count(); + optimizeNextInvocation(count); + count(); + }); + it("numberOfDFGCompiles", () => { + expect(numberOfDFGCompiles(count) > 0).toBe(true); + }); + it("releaseWeakRefs", () => { + releaseWeakRefs(); + }); + it("totalCompileTime", () => { + totalCompileTime(count); + }); + it("reoptimizationRetryCount", () => { + reoptimizationRetryCount(count); + }); + it("drainMicrotasks", () => { + drainMicrotasks(); + }); + it("startRemoteDebugger", () => { + startRemoteDebugger(""); + }); +}); diff --git a/test/bun.js/bun.lockb b/test/bun.js/bun.lockb Binary files differnew file mode 100755 index 000000000..cff7a8ddc --- /dev/null +++ b/test/bun.js/bun.lockb diff --git a/test/bun.js/bundled/always-bundled-module/always-bundled-module b/test/bun.js/bundled/always-bundled-module/always-bundled-module new file mode 120000 index 000000000..f9a91ac4d --- /dev/null +++ b/test/bun.js/bundled/always-bundled-module/always-bundled-module @@ -0,0 +1 @@ +node_modules/always-bundled-module
\ No newline at end of file diff --git a/test/bun.js/bundled/always-bundled-module/cjs.js b/test/bun.js/bundled/always-bundled-module/cjs.js new file mode 100644 index 000000000..087697589 --- /dev/null +++ b/test/bun.js/bundled/always-bundled-module/cjs.js @@ -0,0 +1,10 @@ +module.exports = { + default: 0xdeadbeef, + default() { + return "ok"; + }, + default: true, + ok() { + return true; + }, +}; diff --git a/test/bun.js/bundled/always-bundled-module/esm.js b/test/bun.js/bundled/always-bundled-module/esm.js new file mode 100644 index 000000000..28e702881 --- /dev/null +++ b/test/bun.js/bundled/always-bundled-module/esm.js @@ -0,0 +1,5 @@ +const __esModule = true; + +export const foo = () => __esModule; + +export { __esModule, foo as default }; diff --git a/test/bun.js/bundled/always-bundled-module/package.json b/test/bun.js/bundled/always-bundled-module/package.json new file mode 100644 index 000000000..5029c1695 --- /dev/null +++ b/test/bun.js/bundled/always-bundled-module/package.json @@ -0,0 +1,4 @@ +{ + "name": "always-bundled-module", + "version": "1.0.0" +} diff --git a/test/bun.js/bundled/entrypoint.ts b/test/bun.js/bundled/entrypoint.ts new file mode 100644 index 000000000..b9a17b538 --- /dev/null +++ b/test/bun.js/bundled/entrypoint.ts @@ -0,0 +1,13 @@ +import "i-am-bundled/cjs"; +import "i-am-bundled/esm"; +import "always-bundled-module/esm"; +import "always-bundled-module/cjs"; +import { foo } from "i-am-bundled/esm"; +import { foo as foo2 } from "always-bundled-module/esm"; +import cJS from "always-bundled-module/cjs"; + +foo(); +foo2(); +cJS(); + +export default cJS(); diff --git a/test/bun.js/bundled/package.json b/test/bun.js/bundled/package.json new file mode 100644 index 000000000..cce72af9c --- /dev/null +++ b/test/bun.js/bundled/package.json @@ -0,0 +1,12 @@ +{ + "name": "to-bundle", + "scripts": { + "prebundle": "rm -rf node_modules; cp -r to_bundle_node_modules node_modules; ln -s always-bundled-module node_modules/always-bundled-module", + "bundle": "${BUN_BIN:-$(which bun)} bun ./entrypoint.ts" + }, + "bun": { + "alwaysBundle": [ + "always-bundled-module" + ] + } +} diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js new file mode 100644 index 000000000..087697589 --- /dev/null +++ b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/cjs.js @@ -0,0 +1,10 @@ +module.exports = { + default: 0xdeadbeef, + default() { + return "ok"; + }, + default: true, + ok() { + return true; + }, +}; diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js new file mode 100644 index 000000000..28e702881 --- /dev/null +++ b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/esm.js @@ -0,0 +1,5 @@ +const __esModule = true; + +export const foo = () => __esModule; + +export { __esModule, foo as default }; diff --git a/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json new file mode 100644 index 000000000..661a80b2d --- /dev/null +++ b/test/bun.js/bundled/to_bundle_node_modules/i-am-bundled/package.json @@ -0,0 +1,4 @@ +{ + "name": "i-am-bundled", + "version": "1.0.0" +} diff --git a/test/bun.js/bundled/tsconfig.json b/test/bun.js/bundled/tsconfig.json new file mode 100644 index 000000000..358cb5526 --- /dev/null +++ b/test/bun.js/bundled/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "paths": {}, + "baseUrl": "." + } +} diff --git a/test/bun.js/concat.test.js b/test/bun.js/concat.test.js new file mode 100644 index 000000000..a965fdb94 --- /dev/null +++ b/test/bun.js/concat.test.js @@ -0,0 +1,46 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "./gc"; +import { concatArrayBuffers } from "bun"; + +describe("concat", () => { + function polyfill(chunks) { + var size = 0; + for (const chunk of chunks) { + size += chunk.byteLength; + } + var buffer = new ArrayBuffer(size); + var view = new Uint8Array(buffer); + var offset = 0; + for (const chunk of chunks) { + view.set(chunk, offset); + offset += chunk.byteLength; + } + return buffer; + } + + function concatToString(chunks) { + return Array.from(new Uint8Array(concatArrayBuffers(chunks))).join(""); + } + + function polyfillToString(chunks) { + return Array.from(new Uint8Array(polyfill(chunks))).join(""); + } + + it("works with one element", () => { + expect(concatToString([new Uint8Array([123])])).toBe( + polyfillToString([new Uint8Array([123])]) + ); + }); + + it("works with two elements", () => { + expect( + concatToString([Uint8Array.from([123]), Uint8Array.from([456])]) + ).toBe(polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])])); + }); + + it("works with mix of ArrayBuffer and TypedArray elements", () => { + expect( + concatToString([Uint8Array.from([123]).buffer, Uint8Array.from([456])]) + ).toBe(polyfillToString([Uint8Array.from([123]), Uint8Array.from([456])])); + }); +}); diff --git a/test/bun.js/console-log.js b/test/bun.js/console-log.js new file mode 100644 index 000000000..e8aa200ac --- /dev/null +++ b/test/bun.js/console-log.js @@ -0,0 +1,58 @@ +console.log("Hello World!"); +console.log(123); +console.log(-123); +console.log(123.567); +console.log(-123.567); +console.log(true); +console.log(false); +console.log(null); +console.log(undefined); +console.log(Symbol("Symbol Description")); +console.log(new Date(2021, 12, 30, 666, 777, 888, 999)); +console.log([123, 456, 789]); +console.log({ a: 123, b: 456, c: 789 }); +console.log({ + a: { + b: { + c: 123, + }, + bacon: true, + }, +}); + +console.log(new Promise(() => {})); + +class Foo {} + +console.log(() => {}); +console.log(Foo); +console.log(new Foo()); +console.log(function foooo() {}); + +console.log(/FooRegex/); + +console.error("uh oh"); +console.time("Check"); + +console.log( + "Is it a bug or a feature that formatting numbers like %d is colored", + 123 +); +console.log(globalThis); + +console.log( + "String %s should be 2nd word, 456 == %s and percent s %s == %s", + "123", + "456", + "%s", + "What", + "okay" +); + +const infinteLoop = { + foo: {}, + bar: {}, +}; + +infinteLoop.bar = infinteLoop; +console.log(infinteLoop, "am"); diff --git a/test/bun.js/crypto.test.js b/test/bun.js/crypto.test.js new file mode 100644 index 000000000..c489e11c1 --- /dev/null +++ b/test/bun.js/crypto.test.js @@ -0,0 +1,70 @@ +import { + sha, + MD5, + MD4, + SHA1, + SHA256, + SHA384, + SHA512, + SHA512_256, + gc, +} from "bun"; +import { it, expect, describe } from "bun:test"; +import { readFileSync } from "fs"; + +describe("crypto", () => { + for (let Hash of [MD5, MD4, SHA1, SHA256, SHA384, SHA512, SHA512_256]) { + for (let [input, label] of [ + ["hello world", '"hello world"'], + ["hello world".repeat(20).slice(), '"hello world" x 20'], + ["", "empty string"], + ["a", '"a"'], + ]) { + describe(label, () => { + gc(true); + + it(`${Hash.name} base64`, () => { + gc(true); + const result = new Hash(); + result.update(input); + expect(typeof result.digest("base64")).toBe("string"); + gc(true); + }); + + it(`${Hash.name} hash base64`, () => { + Hash.hash(input, "base64"); + gc(true); + }); + + it(`${Hash.name} hex`, () => { + const result = new Hash(); + result.update(input); + expect(typeof result.digest("hex")).toBe("string"); + gc(true); + }); + + it(`${Hash.name} hash hex`, () => { + expect(typeof Hash.hash(input, "hex")).toBe("string"); + gc(true); + }); + + it(`${Hash.name} buffer`, () => { + var buf = new Uint8Array(256); + const result = new Hash(); + + result.update(input); + expect(result.digest(buf)).toBe(buf); + expect(buf[0] != 0).toBe(true); + gc(true); + }); + + it(`${Hash.name} buffer`, () => { + var buf = new Uint8Array(256); + + expect(Hash.hash(input, buf) instanceof Uint8Array).toBe(true); + gc(true); + }); + }); + } + } +}); diff --git a/test/bun.js/dirname.test.js b/test/bun.js/dirname.test.js new file mode 100644 index 000000000..98292dc49 --- /dev/null +++ b/test/bun.js/dirname.test.js @@ -0,0 +1,9 @@ +import { expect, it } from "bun:test"; + +it("__dirname should work", () => { + expect(import.meta.dir).toBe(__dirname); +}); + +it("__filename should work", () => { + expect(import.meta.path).toBe(__filename); +}); diff --git a/test/bun.js/escapeHTML.test.js b/test/bun.js/escapeHTML.test.js new file mode 100644 index 000000000..ecfcc5e7c --- /dev/null +++ b/test/bun.js/escapeHTML.test.js @@ -0,0 +1,105 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "./gc"; +import { escapeHTML } from "bun"; + +describe("escapeHTML", () => { + // The matrix of cases we need to test for: + // 1. Works with short strings + // 2. Works with long strings + // 3. Works with latin1 strings + // 4. Works with utf16 strings + // 5. Works when the text to escape is somewhere in the middle + // 6. Works when the text to escape is in the beginning + // 7. Works when the text to escape is in the end + // 8. Returns the same string when there's no need to escape + it("works", () => { + expect(escapeHTML("absolutely nothing to do here")).toBe( + "absolutely nothing to do here" + ); + expect(escapeHTML("<script>alert(1)</script>")).toBe( + "<script>alert(1)</script>" + ); + expect(escapeHTML("<")).toBe("<"); + expect(escapeHTML(">")).toBe(">"); + expect(escapeHTML("&")).toBe("&"); + expect(escapeHTML("'")).toBe("'"); + expect(escapeHTML('"')).toBe("""); + expect(escapeHTML("\n")).toBe("\n"); + expect(escapeHTML("\r")).toBe("\r"); + expect(escapeHTML("\t")).toBe("\t"); + expect(escapeHTML("\f")).toBe("\f"); + expect(escapeHTML("\v")).toBe("\v"); + expect(escapeHTML("\b")).toBe("\b"); + expect(escapeHTML("\u00A0")).toBe("\u00A0"); + expect(escapeHTML("<script>ab")).toBe("<script>ab"); + expect(escapeHTML("<script>")).toBe("<script>"); + expect(escapeHTML("<script><script>")).toBe("<script><script>"); + + expect(escapeHTML("lalala" + "<script>alert(1)</script>" + "lalala")).toBe( + "lalala<script>alert(1)</script>lalala" + ); + + expect(escapeHTML("<script>alert(1)</script>" + "lalala")).toBe( + "<script>alert(1)</script>lalala" + ); + expect(escapeHTML("lalala" + "<script>alert(1)</script>")).toBe( + "lalala" + "<script>alert(1)</script>" + ); + + expect(escapeHTML("What does 😊 mean?")).toBe("What does 😊 mean?"); + const output = escapeHTML("<What does 😊"); + expect(output).toBe("<What does 😊"); + expect(escapeHTML("<div>What does 😊 mean in text?")).toBe( + "<div>What does 😊 mean in text?" + ); + + expect( + escapeHTML( + ("lalala" + "<script>alert(1)</script>" + "lalala").repeat(900) + ) + ).toBe("lalala<script>alert(1)</script>lalala".repeat(900)); + expect( + escapeHTML(("<script>alert(1)</script>" + "lalala").repeat(900)) + ).toBe("<script>alert(1)</script>lalala".repeat(900)); + expect( + escapeHTML(("lalala" + "<script>alert(1)</script>").repeat(900)) + ).toBe(("lalala" + "<script>alert(1)</script>").repeat(900)); + + // the positions of the unicode codepoint are important + // our simd code for U16 is at 8 bytes, so we need to especially check the boundaries + expect( + escapeHTML("😊lalala" + "<script>alert(1)</script>" + "lalala") + ).toBe("😊lalala<script>alert(1)</script>lalala"); + expect(escapeHTML("<script>😊alert(1)</script>" + "lalala")).toBe( + "<script>😊alert(1)</script>lalala" + ); + expect(escapeHTML("<script>alert(1)😊</script>" + "lalala")).toBe( + "<script>alert(1)😊</script>lalala" + ); + expect(escapeHTML("<script>alert(1)</script>" + "😊lalala")).toBe( + "<script>alert(1)</script>😊lalala" + ); + expect(escapeHTML("<script>alert(1)</script>" + "lal😊ala")).toBe( + "<script>alert(1)</script>lal😊ala" + ); + expect( + escapeHTML("<script>alert(1)</script>" + "lal😊ala".repeat(10)) + ).toBe("<script>alert(1)</script>" + "lal😊ala".repeat(10)); + + for (let i = 1; i < 10; i++) + expect(escapeHTML("<script>alert(1)</script>" + "la😊".repeat(i))).toBe( + "<script>alert(1)</script>" + "la😊".repeat(i) + ); + + expect(escapeHTML("la😊" + "<script>alert(1)</script>")).toBe( + "la😊" + "<script>alert(1)</script>" + ); + expect( + escapeHTML(("lalala" + "<script>alert(1)</script>😊").repeat(1)) + ).toBe(("lalala" + "<script>alert(1)</script>😊").repeat(1)); + + expect(escapeHTML("😊".repeat(100))).toBe("😊".repeat(100)); + expect(escapeHTML("😊<".repeat(100))).toBe("😊<".repeat(100)); + expect(escapeHTML("<😊>".repeat(100))).toBe("<😊>".repeat(100)); + }); +}); diff --git a/test/bun.js/esm/first.mjs b/test/bun.js/esm/first.mjs new file mode 100644 index 000000000..17021c623 --- /dev/null +++ b/test/bun.js/esm/first.mjs @@ -0,0 +1,8 @@ +import { end, start } from "./startEnd.mjs"; + +start("First"); + +import "./second.mjs"; +import "./third.mjs"; + +end("First"); diff --git a/test/bun.js/esm/second-child.mjs b/test/bun.js/esm/second-child.mjs new file mode 100644 index 000000000..5fb06ed45 --- /dev/null +++ b/test/bun.js/esm/second-child.mjs @@ -0,0 +1,5 @@ +import { start, end } from "./startEnd.mjs"; + +start("Second (nested import)"); + +end("Second (nested import)"); diff --git a/test/bun.js/esm/second.mjs b/test/bun.js/esm/second.mjs new file mode 100644 index 000000000..888eb11b9 --- /dev/null +++ b/test/bun.js/esm/second.mjs @@ -0,0 +1,7 @@ +import { start, end } from "./startEnd.mjs"; + +start("Second"); + +import "./second-child.mjs"; + +end("Second"); diff --git a/test/bun.js/esm/startEnd.mjs b/test/bun.js/esm/startEnd.mjs new file mode 100644 index 000000000..8b5549802 --- /dev/null +++ b/test/bun.js/esm/startEnd.mjs @@ -0,0 +1,6 @@ +export function start(name) { + console.log(`[start] ${name}`); +} +export function end(name) { + console.log(`[end] ${name}`); +} diff --git a/test/bun.js/esm/third.mjs b/test/bun.js/esm/third.mjs new file mode 100644 index 000000000..f5ba5cc84 --- /dev/null +++ b/test/bun.js/esm/third.mjs @@ -0,0 +1,4 @@ +import { end, start } from "./startEnd.mjs"; + +start("Third"); +end("Third"); diff --git a/test/bun.js/exit.js b/test/bun.js/exit.js new file mode 100644 index 000000000..fb28b1fb4 --- /dev/null +++ b/test/bun.js/exit.js @@ -0,0 +1,2 @@ +process.exit(0); +throw new Error("Well that didn't work"); diff --git a/test/bun.js/fetch.js.txt b/test/bun.js/fetch.js.txt new file mode 100644 index 000000000..5a9b52fcf --- /dev/null +++ b/test/bun.js/fetch.js.txt @@ -0,0 +1,46 @@ +<!doctype html> +<html> +<head> + <title>Example Domain</title> + + <meta charset="utf-8" /> + <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <style type="text/css"> + body { + background-color: #f0f0f2; + margin: 0; + padding: 0; + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + + } + div { + width: 600px; + margin: 5em auto; + padding: 2em; + background-color: #fdfdff; + border-radius: 0.5em; + box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02); + } + a:link, a:visited { + color: #38488f; + text-decoration: none; + } + @media (max-width: 700px) { + div { + margin: 0 auto; + width: auto; + } + } + </style> +</head> + +<body> +<div> + <h1>Example Domain</h1> + <p>This domain is for use in illustrative examples in documents. You may use this + domain in literature without prior coordination or asking for permission.</p> + <p><a href="https://www.iana.org/domains/example">More information...</a></p> +</div> +</body> +</html> diff --git a/test/bun.js/fetch.test.js b/test/bun.js/fetch.test.js new file mode 100644 index 000000000..9b6093afd --- /dev/null +++ b/test/bun.js/fetch.test.js @@ -0,0 +1,440 @@ +import { it, describe, expect } from "bun:test"; +import fs from "fs"; +import { gc } from "./gc"; + +describe("fetch", () => { + const urls = ["https://example.com", "http://example.com"]; + for (let url of urls) { + gc(); + it(url, async () => { + gc(); + const response = await fetch(url); + gc(); + const text = await response.text(); + gc(); + expect( + fs.readFileSync( + import.meta.path.substring(0, import.meta.path.lastIndexOf("/")) + + "/fetch.js.txt", + "utf8" + ) + ).toBe(text); + }); + } +}); + +function testBlobInterface(blobbyConstructor, hasBlobFn) { + for (let withGC of [false, true]) { + for (let jsonObject of [ + { hello: true }, + { + hello: + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + }, + ]) { + it(`${jsonObject.hello === true ? "latin1" : "utf16"} json${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + var response = blobbyConstructor(JSON.stringify(jsonObject)); + if (withGC) gc(); + expect(JSON.stringify(await response.json())).toBe( + JSON.stringify(jsonObject) + ); + if (withGC) gc(); + }); + + it(`${ + jsonObject.hello === true ? "latin1" : "utf16" + } arrayBuffer -> json${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + var response = blobbyConstructor( + new TextEncoder().encode(JSON.stringify(jsonObject)) + ); + if (withGC) gc(); + expect(JSON.stringify(await response.json())).toBe( + JSON.stringify(jsonObject) + ); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} text${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + var response = blobbyConstructor(JSON.stringify(jsonObject)); + if (withGC) gc(); + expect(await response.text()).toBe(JSON.stringify(jsonObject)); + if (withGC) gc(); + }); + + it(`${ + jsonObject.hello === true ? "latin1" : "utf16" + } arrayBuffer -> text${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + var response = blobbyConstructor( + new TextEncoder().encode(JSON.stringify(jsonObject)) + ); + if (withGC) gc(); + expect(await response.text()).toBe(JSON.stringify(jsonObject)); + if (withGC) gc(); + }); + + it(`${jsonObject.hello === true ? "latin1" : "utf16"} arrayBuffer${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + + var response = blobbyConstructor(JSON.stringify(jsonObject)); + if (withGC) gc(); + + const bytes = new TextEncoder().encode(JSON.stringify(jsonObject)); + if (withGC) gc(); + + const compare = new Uint8Array(await response.arrayBuffer()); + if (withGC) gc(); + + for (let i = 0; i < compare.length; i++) { + if (withGC) gc(); + + expect(compare[i]).toBe(bytes[i]); + if (withGC) gc(); + } + if (withGC) gc(); + }); + + it(`${ + jsonObject.hello === true ? "latin1" : "utf16" + } arrayBuffer -> arrayBuffer${withGC ? " (with gc) " : ""}`, async () => { + if (withGC) gc(); + + var response = blobbyConstructor( + new TextEncoder().encode(JSON.stringify(jsonObject)) + ); + if (withGC) gc(); + + const bytes = new TextEncoder().encode(JSON.stringify(jsonObject)); + if (withGC) gc(); + + const compare = new Uint8Array(await response.arrayBuffer()); + if (withGC) gc(); + + for (let i = 0; i < compare.length; i++) { + if (withGC) gc(); + + expect(compare[i]).toBe(bytes[i]); + if (withGC) gc(); + } + if (withGC) gc(); + }); + + hasBlobFn && + it(`${jsonObject.hello === true ? "latin1" : "utf16"} blob${ + withGC ? " (with gc) " : "" + }`, async () => { + if (withGC) gc(); + const text = JSON.stringify(jsonObject); + var response = blobbyConstructor(text); + if (withGC) gc(); + const size = new TextEncoder().encode(text).byteLength; + if (withGC) gc(); + const blobed = await response.blob(); + if (withGC) gc(); + expect(blobed instanceof Blob).toBe(true); + if (withGC) gc(); + expect(blobed.size).toBe(size); + if (withGC) gc(); + expect(blobed.type).toBe(""); + if (withGC) gc(); + blobed.type = "application/json"; + if (withGC) gc(); + expect(blobed.type).toBe("application/json"); + if (withGC) gc(); + const out = await blobed.text(); + expect(out).toBe(text); + if (withGC) gc(); + await new Promise((resolve) => setTimeout(resolve, 1)); + if (withGC) gc(); + expect(out).toBe(text); + const first = await blobed.arrayBuffer(); + const initial = first[0]; + first[0] = 254; + const second = await blobed.arrayBuffer(); + expect(second[0]).toBe(initial); + expect(first[0]).toBe(254); + }); + } + } +} + +describe("Blob", () => { + testBlobInterface((data) => new Blob([data])); + + var blobConstructorValues = [ + ["123", "456"], + ["123", 456], + ["123", "456", "789"], + ["123", 456, 789], + [1, 2, 3, 4, 5, 6, 7, 8, 9], + [Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 9])], + [Uint8Array.from([1, 2, 3, 4]), "5678", 9], + [new Blob([Uint8Array.from([1, 2, 3, 4])]), "5678", 9], + [ + new Blob([ + new TextEncoder().encode( + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳" + ), + ]), + ], + [ + new TextEncoder().encode( + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳" + ), + ], + ]; + + var expected = [ + "123456", + "123456", + "123456789", + "123456789", + "123456789", + "\x01\x02\x03\x04\x05\x06\x07\t", + "\x01\x02\x03\x0456789", + "\x01\x02\x03\x0456789", + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + "😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳", + ]; + + it(`blobConstructorValues`, async () => { + for (let i = 0; i < blobConstructorValues.length; i++) { + var response = new Blob(blobConstructorValues[i]); + const res = await response.text(); + if (res !== expected[i]) { + throw new Error( + `Failed: ${expected[i] + .split("") + .map((a) => a.charCodeAt(0))}, received: ${res + .split("") + .map((a) => a.charCodeAt(0))}` + ); + } + + expect(res).toBe(expected[i]); + } + }); + + for (let withGC of [false, true]) { + it(`Blob.slice() ${withGC ? " with gc" : ""}`, async () => { + var parts = ["hello", " ", "world"]; + if (withGC) gc(); + var str = parts.join(""); + if (withGC) gc(); + var combined = new Blob(parts); + if (withGC) gc(); + for (let part of parts) { + if (withGC) gc(); + expect( + await combined + .slice(str.indexOf(part), str.indexOf(part) + part.length) + .text() + ).toBe(part); + if (withGC) gc(); + } + if (withGC) gc(); + for (let part of parts) { + if (withGC) gc(); + expect( + await combined + .slice(str.indexOf(part), str.indexOf(part) + part.length) + .text() + ).toBe(part); + if (withGC) gc(); + } + }); + } +}); + +describe("Response", () => { + describe("Response.json", () => { + it("works", async () => { + const inputs = [ + "hellooo", + [[123], 456, 789], + { hello: "world" }, + { ok: "😉 😌 😍 🥰 😘 " }, + ]; + for (let input of inputs) { + const output = JSON.stringify(input); + expect(await Response.json(input).text()).toBe(output); + } + // JSON.stringify() returns undefined + expect(await Response.json().text()).toBe(""); + // JSON.stringify("") returns '""' + expect(await Response.json("").text()).toBe('""'); + }); + it("sets the content-type header", () => { + let response = Response.json("hello"); + expect(response.type).toBe("basic"); + expect(response.headers.get("content-type")).toBe( + "application/json;charset=utf-8" + ); + expect(response.status).toBe(200); + }); + it("supports number status code", () => { + let response = Response.json("hello", 407); + expect(response.type).toBe("basic"); + expect(response.headers.get("content-type")).toBe( + "application/json;charset=utf-8" + ); + expect(response.status).toBe(407); + }); + + it("supports headers", () => { + var response = Response.json("hello", { + headers: { + "content-type": "potato", + "x-hello": "world", + }, + status: 408, + }); + + expect(response.headers.get("x-hello")).toBe("world"); + expect(response.status).toBe(408); + }); + }); + describe("Response.redirect", () => { + it("works", () => { + const inputs = [ + "http://example.com", + "http://example.com/", + "http://example.com/hello", + "http://example.com/hello/", + "http://example.com/hello/world", + "http://example.com/hello/world/", + ]; + for (let input of inputs) { + expect(Response.redirect(input).headers.get("Location")).toBe(input); + } + }); + + it("supports headers", () => { + var response = Response.redirect("https://example.com", { + headers: { + "content-type": "potato", + "x-hello": "world", + Location: "https://wrong.com", + }, + status: 408, + }); + expect(response.headers.get("x-hello")).toBe("world"); + expect(response.headers.get("Location")).toBe("https://example.com"); + expect(response.status).toBe(302); + expect(response.type).toBe("basic"); + expect(response.ok).toBe(false); + }); + }); + describe("Response.error", () => { + it("works", () => { + expect(Response.error().type).toBe("error"); + expect(Response.error().ok).toBe(false); + expect(Response.error().status).toBe(0); + }); + }); + it("clone", async () => { + gc(); + var body = new Response("<div>hello</div>", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + }); + gc(); + var clone = body.clone(); + gc(); + body.headers.set("content-type", "text/plain"); + gc(); + expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + expect(body.headers.get("content-type")).toBe("text/plain"); + gc(); + expect(await clone.text()).toBe("<div>hello</div>"); + gc(); + }); + it("invalid json", async () => { + gc(); + var body = new Response("<div>hello</div>", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + }); + try { + await body.json(); + expect(false).toBe(true); + } catch (exception) { + expect(exception instanceof SyntaxError); + } + }); + + testBlobInterface((data) => new Response(data), true); +}); + +describe("Request", () => { + it("clone", async () => { + gc(); + var body = new Request("https://hello.com", { + headers: { + "content-type": "text/html; charset=utf-8", + }, + body: "<div>hello</div>", + }); + gc(); + expect(body.headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + var clone = body.clone(); + gc(); + body.headers.set("content-type", "text/plain"); + gc(); + expect(clone.headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + expect(body.headers.get("content-type")).toBe("text/plain"); + gc(); + expect(await clone.text()).toBe("<div>hello</div>"); + gc(); + }); + + testBlobInterface( + (data) => new Request("https://hello.com", { body: data }), + true + ); +}); + +describe("Headers", () => { + it("writes", async () => { + var headers = new Headers({ + "content-type": "text/html; charset=utf-8", + }); + gc(); + expect(headers.get("content-type")).toBe("text/html; charset=utf-8"); + gc(); + headers.delete("content-type"); + gc(); + expect(headers.get("content-type")).toBe(null); + gc(); + headers.append("content-type", "text/plain"); + gc(); + expect(headers.get("content-type")).toBe("text/plain"); + gc(); + headers.append("content-type", "text/plain"); + gc(); + expect(headers.get("content-type")).toBe("text/plain, text/plain"); + gc(); + headers.set("content-type", "text/html; charset=utf-8"); + gc(); + expect(headers.get("content-type")).toBe("text/html; charset=utf-8"); + + headers.delete("content-type"); + gc(); + expect(headers.get("content-type")).toBe(null); + gc(); + }); +}); diff --git a/test/bun.js/ffi-test.c b/test/bun.js/ffi-test.c new file mode 100644 index 000000000..cc87d0528 --- /dev/null +++ b/test/bun.js/ffi-test.c @@ -0,0 +1,129 @@ +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +bool returns_true(); +bool returns_false(); +char returns_42_char(); +float returns_42_float(); +double returns_42_double(); +uint8_t returns_42_uint8_t(); +int8_t returns_neg_42_int8_t(); +uint16_t returns_42_uint16_t(); +uint32_t returns_42_uint32_t(); +uint64_t returns_42_uint64_t(); +int16_t returns_neg_42_int16_t(); +int32_t returns_neg_42_int32_t(); +int64_t returns_neg_42_int64_t(); + +bool cb_identity_true(bool (*cb)()); +bool cb_identity_false(bool (*cb)()); +char cb_identity_42_char(char (*cb)()); +float cb_identity_42_float(float (*cb)()); +double cb_identity_42_double(double (*cb)()); +uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()); +int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()); +uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()); +uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()); +uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()); +int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()); +int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()); +int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()); + +bool identity_bool_true(); +bool identity_bool_false(); +char identity_char(char a); +float identity_float(float a); +bool identity_bool(bool ident); +double identity_double(double a); +int8_t identity_int8_t(int8_t a); +int16_t identity_int16_t(int16_t a); +int32_t identity_int32_t(int32_t a); +int64_t identity_int64_t(int64_t a); +uint8_t identity_uint8_t(uint8_t a); +uint16_t identity_uint16_t(uint16_t a); +uint32_t identity_uint32_t(uint32_t a); +uint64_t identity_uint64_t(uint64_t a); + +char add_char(char a, char b); +float add_float(float a, float b); +double add_double(double a, double b); +int8_t add_int8_t(int8_t a, int8_t b); +int16_t add_int16_t(int16_t a, int16_t b); +int32_t add_int32_t(int32_t a, int32_t b); +int64_t add_int64_t(int64_t a, int64_t b); +uint8_t add_uint8_t(uint8_t a, uint8_t b); +uint16_t add_uint16_t(uint16_t a, uint16_t b); +uint32_t add_uint32_t(uint32_t a, uint32_t b); +uint64_t add_uint64_t(uint64_t a, uint64_t b); + +bool returns_false() { return false; } +bool returns_true() { return true; } +char returns_42_char() { return '*'; } +double returns_42_double() { return (double)42.42; } +float returns_42_float() { return 42.42f; } +int16_t returns_neg_42_int16_t() { return -42; } +int32_t returns_neg_42_int32_t() { return -42; } +int64_t returns_neg_42_int64_t() { return -42; } +int8_t returns_neg_42_int8_t() { return -42; } +uint16_t returns_42_uint16_t() { return 42; } +uint32_t returns_42_uint32_t() { return 42; } +uint64_t returns_42_uint64_t() { return 42; } +uint8_t returns_42_uint8_t() { return (uint8_t)42; } + +char identity_char(char a) { return a; } +float identity_float(float a) { return a; } +double identity_double(double a) { return a; } +int8_t identity_int8_t(int8_t a) { return a; } +int16_t identity_int16_t(int16_t a) { return a; } +int32_t identity_int32_t(int32_t a) { return a; } +int64_t identity_int64_t(int64_t a) { return a; } +uint8_t identity_uint8_t(uint8_t a) { return a; } +uint16_t identity_uint16_t(uint16_t a) { return a; } +uint32_t identity_uint32_t(uint32_t a) { return a; } +uint64_t identity_uint64_t(uint64_t a) { return a; } +bool identity_bool(bool ident) { return ident; } +void *identity_ptr(void *ident) { return ident; } + +char add_char(char a, char b) { return a + b; } +float add_float(float a, float b) { return a + b; } +double add_double(double a, double b) { return a + b; } +int8_t add_int8_t(int8_t a, int8_t b) { return a + b; } +int16_t add_int16_t(int16_t a, int16_t b) { return a + b; } +int32_t add_int32_t(int32_t a, int32_t b) { return a + b; } +int64_t add_int64_t(int64_t a, int64_t b) { return a + b; } +uint8_t add_uint8_t(uint8_t a, uint8_t b) { return a + b; } +uint16_t add_uint16_t(uint16_t a, uint16_t b) { return a + b; } +uint32_t add_uint32_t(uint32_t a, uint32_t b) { return a + b; } +uint64_t add_uint64_t(uint64_t a, uint64_t b) { return a + b; } + +void *ptr_should_point_to_42_as_int32_t(); +void *ptr_should_point_to_42_as_int32_t() { + int32_t *ptr = malloc(sizeof(int32_t)); + *ptr = 42; + return ptr; +} + +bool does_pointer_equal_42_as_int32_t(int32_t *ptr); +bool does_pointer_equal_42_as_int32_t(int32_t *ptr) { return *ptr == 42; } + +void *return_a_function_ptr_to_function_that_returns_true(); +void *return_a_function_ptr_to_function_that_returns_true() { + return (void *)&returns_true; +} + +bool cb_identity_true(bool (*cb)()) { return cb(); } + +bool cb_identity_false(bool (*cb)()) { return cb(); } +char cb_identity_42_char(char (*cb)()) { return cb(); } +float cb_identity_42_float(float (*cb)()) { return cb(); } +double cb_identity_42_double(double (*cb)()) { return cb(); } +uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()) { return cb(); } +int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()) { return cb(); } +uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()) { return cb(); } +uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()) { return cb(); } +uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()) { return cb(); } +int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()) { return cb(); } +int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()) { return cb(); } +int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()) { return cb(); }
\ No newline at end of file diff --git a/test/bun.js/ffi.test.fixture.callback.c b/test/bun.js/ffi.test.fixture.callback.c new file mode 100644 index 000000000..d48ef6753 --- /dev/null +++ b/test/bun.js/ffi.test.fixture.callback.c @@ -0,0 +1,270 @@ +#define IS_CALLBACK 1 +// This file is part of Bun! +// You can find the original source: +// https://github.com/Jarred-Sumner/bun/blob/main/src/bun.js/api/FFI.h#L2 +// +// clang-format off +// This file is only compatible with 64 bit CPUs +// It must be kept in sync with JSCJSValue.h +// https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 +#ifdef IS_CALLBACK +#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call +#endif +#define IS_BIG_ENDIAN 0 +#define USE_JSVALUE64 1 +#define USE_JSVALUE32_64 0 + + +// /* 7.18.1.1 Exact-width integer types */ +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; +typedef unsigned long long size_t; +typedef long intptr_t; +typedef uint64_t uintptr_t; +typedef _Bool bool; + +#define true 1 +#define false 0 + + +#ifdef INJECT_BEFORE +// #include <stdint.h> +#endif +// #include <tcclib.h> + +// This value is 2^49, used to encode doubles such that the encoded value will +// begin with a 15-bit pattern within the range 0x0002..0xFFFC. +#define DoubleEncodeOffsetBit 49 +#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit) +#define OtherTag 0x2 +#define BoolTag 0x4 +#define UndefinedTag 0x8 +#define TagValueFalse (OtherTag | BoolTag | false) +#define TagValueTrue (OtherTag | BoolTag | true) +#define TagValueUndefined (OtherTag | UndefinedTag) +#define TagValueNull (OtherTag) +#define NotCellMask NumberTag | OtherTag + +#define MAX_INT32 2147483648 +#define MAX_INT52 9007199254740991 + +// If all bits in the mask are set, this indicates an integer number, +// if any but not all are set this value is a double precision number. +#define NumberTag 0xfffe000000000000ll + +typedef void* JSCell; + +typedef union EncodedJSValue { + int64_t asInt64; + +#if USE_JSVALUE64 + JSCell *ptr; +#endif + +#if IS_BIG_ENDIAN + struct { + int32_t tag; + int32_t payload; + } asBits; +#else + struct { + int32_t payload; + int32_t tag; + } asBits; +#endif + + void* asPtr; + double asDouble; +} EncodedJSValue; + +EncodedJSValue ValueUndefined = { TagValueUndefined }; +EncodedJSValue ValueTrue = { TagValueTrue }; + +typedef void* JSContext; + +// Bun_FFI_PointerOffsetToArgumentsList is injected into the build +// The value is generated in `make sizegen` +// The value is 6. +// On ARM64_32, the value is something else but it really doesn't matter for our case +// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore +#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ + int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) + + +#ifdef IS_CALLBACK +extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception); +JSContext cachedJSContext; +void* cachedCallbackFunction; +#endif + +static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) __attribute__((__always_inline__)); +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); +uint64_t JSVALUE_TO_UINT64_SLOW(void* globalObject, EncodedJSValue value); +int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); + +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); + + +static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); +static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); +static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__)); +static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__)); +static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__)); + +static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__)); +static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__)); +static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__)); + +static bool JSVALUE_IS_CELL(EncodedJSValue val) { + return !(val.asInt64 & NotCellMask); +} + +static bool JSVALUE_IS_INT32(EncodedJSValue val) { + return (val.asInt64 & NumberTag) == NumberTag; +} + +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { + return val.asInt64 & NumberTag; +} + + +static void* JSVALUE_TO_PTR(EncodedJSValue val) { + // must be a double + return (void*)(val.asInt64 - DoubleEncodeOffset); +} + +static EncodedJSValue PTR_TO_JSVALUE(void* ptr) { + EncodedJSValue val; + val.asInt64 = (int64_t)ptr + DoubleEncodeOffset; + return val; +} + +static int32_t JSVALUE_TO_INT32(EncodedJSValue val) { + return val.asInt64; +} + +static EncodedJSValue INT32_TO_JSVALUE(int32_t val) { + EncodedJSValue res; + res.asInt64 = NumberTag | (uint32_t)val; + return res; +} + + +static EncodedJSValue DOUBLE_TO_JSVALUE(double val) { + EncodedJSValue res; + res.asDouble = val; + res.asInt64 += DoubleEncodeOffset; + return res; +} + +static EncodedJSValue FLOAT_TO_JSVALUE(float val) { + return DOUBLE_TO_JSVALUE((double)val); +} + +static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) { + EncodedJSValue res; + res.asInt64 = val ? TagValueTrue : TagValueFalse; + return res; +} + + +static double JSVALUE_TO_DOUBLE(EncodedJSValue val) { + val.asInt64 -= DoubleEncodeOffset; + return val.asDouble; +} + +static float JSVALUE_TO_FLOAT(EncodedJSValue val) { + return (float)JSVALUE_TO_DOUBLE(val); +} + +static bool JSVALUE_TO_BOOL(EncodedJSValue val) { + return val.asInt64 == TagValueTrue; +} + + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (uint64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (uint64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_UINT64_SLOW(globalObject, value); +} +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (int64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (int64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_INT64_SLOW(value); +} + +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { + if (val < MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val < MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return UINT64_TO_JSVALUE_SLOW(globalObject, val); +} + +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { + if (val >= -MAX_INT32 && val <= MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val >= -MAX_INT52 && val <= MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return INT64_TO_JSVALUE_SLOW(globalObject, val); +} + +#ifndef IS_CALLBACK +void* JSFunctionCall(void* globalObject, void* callFrame); + +#endif + + +// --- Generated Code --- + + +/* --- The Callback Function */ +/* --- The Callback Function */ +bool my_callback_function(void* arg0); + +bool my_callback_function(void* arg0) { +#ifdef INJECT_BEFORE +INJECT_BEFORE; +#endif + EncodedJSValue arguments[1] = { + PTR_TO_JSVALUE(arg0) + }; + EncodedJSValue return_value = {bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, 1, &arguments[0], (void*)0)}; + return JSVALUE_TO_BOOL(return_value); +} + diff --git a/test/bun.js/ffi.test.fixture.receiver.c b/test/bun.js/ffi.test.fixture.receiver.c new file mode 100644 index 000000000..5bb51bda5 --- /dev/null +++ b/test/bun.js/ffi.test.fixture.receiver.c @@ -0,0 +1,268 @@ +#define HAS_ARGUMENTS +#define USES_FLOAT 1 +// This file is part of Bun! +// You can find the original source: +// https://github.com/Jarred-Sumner/bun/blob/main/src/bun.js/api/FFI.h#L2 +// +// clang-format off +// This file is only compatible with 64 bit CPUs +// It must be kept in sync with JSCJSValue.h +// https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 +#ifdef IS_CALLBACK +#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call +#endif +#define IS_BIG_ENDIAN 0 +#define USE_JSVALUE64 1 +#define USE_JSVALUE32_64 0 + + +// /* 7.18.1.1 Exact-width integer types */ +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; +typedef unsigned long long size_t; +typedef long intptr_t; +typedef uint64_t uintptr_t; +typedef _Bool bool; + +#define true 1 +#define false 0 + + +#ifdef INJECT_BEFORE +// #include <stdint.h> +#endif +// #include <tcclib.h> + +// This value is 2^49, used to encode doubles such that the encoded value will +// begin with a 15-bit pattern within the range 0x0002..0xFFFC. +#define DoubleEncodeOffsetBit 49 +#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit) +#define OtherTag 0x2 +#define BoolTag 0x4 +#define UndefinedTag 0x8 +#define TagValueFalse (OtherTag | BoolTag | false) +#define TagValueTrue (OtherTag | BoolTag | true) +#define TagValueUndefined (OtherTag | UndefinedTag) +#define TagValueNull (OtherTag) +#define NotCellMask NumberTag | OtherTag + +#define MAX_INT32 2147483648 +#define MAX_INT52 9007199254740991 + +// If all bits in the mask are set, this indicates an integer number, +// if any but not all are set this value is a double precision number. +#define NumberTag 0xfffe000000000000ll + +typedef void* JSCell; + +typedef union EncodedJSValue { + int64_t asInt64; + +#if USE_JSVALUE64 + JSCell *ptr; +#endif + +#if IS_BIG_ENDIAN + struct { + int32_t tag; + int32_t payload; + } asBits; +#else + struct { + int32_t payload; + int32_t tag; + } asBits; +#endif + + void* asPtr; + double asDouble; +} EncodedJSValue; + +EncodedJSValue ValueUndefined = { TagValueUndefined }; +EncodedJSValue ValueTrue = { TagValueTrue }; + +typedef void* JSContext; + +// Bun_FFI_PointerOffsetToArgumentsList is injected into the build +// The value is generated in `make sizegen` +// The value is 6. +// On ARM64_32, the value is something else but it really doesn't matter for our case +// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore +#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ + int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) + + +#ifdef IS_CALLBACK +extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception); +JSContext cachedJSContext; +void* cachedCallbackFunction; +#endif + +static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) __attribute__((__always_inline__)); +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); +uint64_t JSVALUE_TO_UINT64_SLOW(void* globalObject, EncodedJSValue value); +int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); + +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); + + +static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); +static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); +static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline__)); +static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__)); +static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__)); + +static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__)); +static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__)); +static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__)); + +static bool JSVALUE_IS_CELL(EncodedJSValue val) { + return !(val.asInt64 & NotCellMask); +} + +static bool JSVALUE_IS_INT32(EncodedJSValue val) { + return (val.asInt64 & NumberTag) == NumberTag; +} + +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { + return val.asInt64 & NumberTag; +} + + +static void* JSVALUE_TO_PTR(EncodedJSValue val) { + // must be a double + return (void*)(val.asInt64 - DoubleEncodeOffset); +} + +static EncodedJSValue PTR_TO_JSVALUE(void* ptr) { + EncodedJSValue val; + val.asInt64 = (int64_t)ptr + DoubleEncodeOffset; + return val; +} + +static int32_t JSVALUE_TO_INT32(EncodedJSValue val) { + return val.asInt64; +} + +static EncodedJSValue INT32_TO_JSVALUE(int32_t val) { + EncodedJSValue res; + res.asInt64 = NumberTag | (uint32_t)val; + return res; +} + + +static EncodedJSValue DOUBLE_TO_JSVALUE(double val) { + EncodedJSValue res; + res.asDouble = val; + res.asInt64 += DoubleEncodeOffset; + return res; +} + +static EncodedJSValue FLOAT_TO_JSVALUE(float val) { + return DOUBLE_TO_JSVALUE((double)val); +} + +static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) { + EncodedJSValue res; + res.asInt64 = val ? TagValueTrue : TagValueFalse; + return res; +} + + +static double JSVALUE_TO_DOUBLE(EncodedJSValue val) { + val.asInt64 -= DoubleEncodeOffset; + return val.asDouble; +} + +static float JSVALUE_TO_FLOAT(EncodedJSValue val) { + return (float)JSVALUE_TO_DOUBLE(val); +} + +static bool JSVALUE_TO_BOOL(EncodedJSValue val) { + return val.asInt64 == TagValueTrue; +} + + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (uint64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (uint64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_UINT64_SLOW(globalObject, value); +} +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (int64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (int64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_INT64_SLOW(value); +} + +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { + if (val < MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val < MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return UINT64_TO_JSVALUE_SLOW(globalObject, val); +} + +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { + if (val >= -MAX_INT32 && val <= MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val >= -MAX_INT52 && val <= MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return INT64_TO_JSVALUE_SLOW(globalObject, val); +} + +#ifndef IS_CALLBACK +void* JSFunctionCall(void* globalObject, void* callFrame); + +#endif + + +// --- Generated Code --- +/* --- The Function To Call */ +float not_a_callback(float arg0); + + +/* ---- Your Wrapper Function ---- */ +void* JSFunctionCall(void* globalObject, void* callFrame) { + LOAD_ARGUMENTS_FROM_CALL_FRAME; + EncodedJSValue arg0; + arg0.asInt64 = *argsPtr; + float return_value = not_a_callback( JSVALUE_TO_FLOAT(arg0)); + + return FLOAT_TO_JSVALUE(return_value).asPtr; +} + diff --git a/test/bun.js/ffi.test.js b/test/bun.js/ffi.test.js new file mode 100644 index 000000000..db2cfb6d4 --- /dev/null +++ b/test/bun.js/ffi.test.js @@ -0,0 +1,543 @@ +// import { describe, it, expect } from "bun:test"; +// import { unsafe } from "bun"; +// // +// import { +// native, +// viewSource, +// dlopen, +// CString, +// ptr, +// toBuffer, +// toArrayBuffer, +// FFIType, +// callback, +// CFunction, +// } from "bun:ffi"; + +// it("ffi print", async () => { +// await Bun.write( +// import.meta.dir + "/ffi.test.fixture.callback.c", +// viewSource( +// { +// returns: "bool", +// args: ["ptr"], +// }, +// true +// ) +// ); +// await Bun.write( +// import.meta.dir + "/ffi.test.fixture.receiver.c", +// viewSource( +// { +// not_a_callback: { +// returns: "float", +// args: ["float"], +// }, +// }, +// false +// )[0] +// ); +// expect( +// viewSource( +// { +// returns: "int8_t", +// args: [], +// }, +// true +// ).length > 0 +// ).toBe(true); +// expect( +// viewSource( +// { +// a: { +// returns: "int8_t", +// args: [], +// }, +// }, +// false +// ).length > 0 +// ).toBe(true); +// }); + +// function getTypes(fast) { +// const int64_t = fast ? "i64_fast" : "int64_t"; +// const uint64_t = fast ? "u64_fast" : "uint64_t"; +// return { +// returns_true: { +// returns: "bool", +// args: [], +// }, +// returns_false: { +// returns: "bool", +// args: [], +// }, +// returns_42_char: { +// returns: "char", +// args: [], +// }, +// returns_42_float: { +// returns: "float", +// args: [], +// }, +// returns_42_double: { +// returns: "double", +// args: [], +// }, +// returns_42_uint8_t: { +// returns: "uint8_t", +// args: [], +// }, +// returns_neg_42_int8_t: { +// returns: "int8_t", +// args: [], +// }, +// returns_42_uint16_t: { +// returns: "uint16_t", +// args: [], +// }, +// returns_42_uint32_t: { +// returns: "uint32_t", +// args: [], +// }, +// returns_42_uint64_t: { +// returns: uint64_t, +// args: [], +// }, +// returns_neg_42_int16_t: { +// returns: "int16_t", +// args: [], +// }, +// returns_neg_42_int32_t: { +// returns: "int32_t", +// args: [], +// }, +// returns_neg_42_int64_t: { +// returns: int64_t, +// args: [], +// }, + +// identity_char: { +// returns: "char", +// args: ["char"], +// }, +// identity_float: { +// returns: "float", +// args: ["float"], +// }, +// identity_bool: { +// returns: "bool", +// args: ["bool"], +// }, +// identity_double: { +// returns: "double", +// args: ["double"], +// }, +// identity_int8_t: { +// returns: "int8_t", +// args: ["int8_t"], +// }, +// identity_int16_t: { +// returns: "int16_t", +// args: ["int16_t"], +// }, +// identity_int32_t: { +// returns: "int32_t", +// args: ["int32_t"], +// }, +// identity_int64_t: { +// returns: int64_t, +// args: [int64_t], +// }, +// identity_uint8_t: { +// returns: "uint8_t", +// args: ["uint8_t"], +// }, +// identity_uint16_t: { +// returns: "uint16_t", +// args: ["uint16_t"], +// }, +// identity_uint32_t: { +// returns: "uint32_t", +// args: ["uint32_t"], +// }, +// identity_uint64_t: { +// returns: uint64_t, +// args: [uint64_t], +// }, + +// add_char: { +// returns: "char", +// args: ["char", "char"], +// }, +// add_float: { +// returns: "float", +// args: ["float", "float"], +// }, +// add_double: { +// returns: "double", +// args: ["double", "double"], +// }, +// add_int8_t: { +// returns: "int8_t", +// args: ["int8_t", "int8_t"], +// }, +// add_int16_t: { +// returns: "int16_t", +// args: ["int16_t", "int16_t"], +// }, +// add_int32_t: { +// returns: "int32_t", +// args: ["int32_t", "int32_t"], +// }, +// add_int64_t: { +// returns: int64_t, +// args: [int64_t, int64_t], +// }, +// add_uint8_t: { +// returns: "uint8_t", +// args: ["uint8_t", "uint8_t"], +// }, +// add_uint16_t: { +// returns: "uint16_t", +// args: ["uint16_t", "uint16_t"], +// }, +// add_uint32_t: { +// returns: "uint32_t", +// args: ["uint32_t", "uint32_t"], +// }, + +// does_pointer_equal_42_as_int32_t: { +// returns: "bool", +// args: ["ptr"], +// }, + +// ptr_should_point_to_42_as_int32_t: { +// returns: "ptr", +// args: [], +// }, +// identity_ptr: { +// returns: "ptr", +// args: ["ptr"], +// }, +// add_uint64_t: { +// returns: uint64_t, +// args: [uint64_t, uint64_t], +// }, + +// cb_identity_true: { +// returns: "bool", +// args: ["ptr"], +// }, +// cb_identity_false: { +// returns: "bool", +// args: ["ptr"], +// }, +// cb_identity_42_char: { +// returns: "char", +// args: ["ptr"], +// }, +// cb_identity_42_float: { +// returns: "float", +// args: ["ptr"], +// }, +// cb_identity_42_double: { +// returns: "double", +// args: ["ptr"], +// }, +// cb_identity_42_uint8_t: { +// returns: "uint8_t", +// args: ["ptr"], +// }, +// cb_identity_neg_42_int8_t: { +// returns: "int8_t", +// args: ["ptr"], +// }, +// cb_identity_42_uint16_t: { +// returns: "uint16_t", +// args: ["ptr"], +// }, +// cb_identity_42_uint32_t: { +// returns: "uint32_t", +// args: ["ptr"], +// }, +// cb_identity_42_uint64_t: { +// returns: uint64_t, +// args: ["ptr"], +// }, +// cb_identity_neg_42_int16_t: { +// returns: "int16_t", +// args: ["ptr"], +// }, +// cb_identity_neg_42_int32_t: { +// returns: "int32_t", +// args: ["ptr"], +// }, +// cb_identity_neg_42_int64_t: { +// returns: int64_t, +// args: ["ptr"], +// }, + +// return_a_function_ptr_to_function_that_returns_true: { +// returns: "ptr", +// args: [], +// }, +// }; +// } + +// function ffiRunner(types) { +// const { +// symbols: { +// returns_true, +// returns_false, +// return_a_function_ptr_to_function_that_returns_true, +// returns_42_char, +// returns_42_float, +// returns_42_double, +// returns_42_uint8_t, +// returns_neg_42_int8_t, +// returns_42_uint16_t, +// returns_42_uint32_t, +// returns_42_uint64_t, +// returns_neg_42_int16_t, +// returns_neg_42_int32_t, +// returns_neg_42_int64_t, +// identity_char, +// identity_float, +// identity_bool, +// identity_double, +// identity_int8_t, +// identity_int16_t, +// identity_int32_t, +// identity_int64_t, +// identity_uint8_t, +// identity_uint16_t, +// identity_uint32_t, +// identity_uint64_t, +// add_char, +// add_float, +// add_double, +// add_int8_t, +// add_int16_t, +// add_int32_t, +// add_int64_t, +// add_uint8_t, +// add_uint16_t, +// identity_ptr, +// add_uint32_t, +// add_uint64_t, +// does_pointer_equal_42_as_int32_t, +// ptr_should_point_to_42_as_int32_t, +// cb_identity_true, +// cb_identity_false, +// cb_identity_42_char, +// cb_identity_42_float, +// cb_identity_42_double, +// cb_identity_42_uint8_t, +// cb_identity_neg_42_int8_t, +// cb_identity_42_uint16_t, +// cb_identity_42_uint32_t, +// cb_identity_42_uint64_t, +// cb_identity_neg_42_int16_t, +// cb_identity_neg_42_int32_t, +// cb_identity_neg_42_int64_t, +// }, +// close, +// } = dlopen("/tmp/bun-ffi-test.dylib", types); + +// expect(returns_true()).toBe(true); + +// expect(returns_false()).toBe(false); + +// expect(returns_42_char()).toBe(42); +// console.log( +// returns_42_uint64_t().valueOf(), +// returns_42_uint64_t(), +// returns_42_uint64_t().valueOf() === returns_42_uint64_t() +// ); +// expect(returns_42_uint64_t().valueOf()).toBe(42); + +// expect(Math.fround(returns_42_float())).toBe(Math.fround(42.41999804973602)); +// expect(returns_42_double()).toBe(42.42); +// expect(returns_42_uint8_t()).toBe(42); +// expect(returns_neg_42_int8_t()).toBe(-42); +// expect(returns_42_uint16_t()).toBe(42); +// expect(returns_42_uint32_t()).toBe(42); +// expect(returns_42_uint64_t()).toBe(42); +// expect(returns_neg_42_int16_t()).toBe(-42); +// expect(returns_neg_42_int32_t()).toBe(-42); +// expect(identity_int32_t(10)).toBe(10); +// expect(returns_neg_42_int64_t()).toBe(-42); + +// expect(identity_char(10)).toBe(10); + +// expect(identity_float(10.199999809265137)).toBe(10.199999809265137); + +// expect(identity_bool(true)).toBe(true); + +// expect(identity_bool(false)).toBe(false); +// expect(identity_double(10.100000000000364)).toBe(10.100000000000364); + +// expect(identity_int8_t(10)).toBe(10); +// expect(identity_int16_t(10)).toBe(10); +// expect(identity_int64_t(10)).toBe(10); +// expect(identity_uint8_t(10)).toBe(10); +// expect(identity_uint16_t(10)).toBe(10); +// expect(identity_uint32_t(10)).toBe(10); +// expect(identity_uint64_t(10)).toBe(10); + +// var bigArray = new BigUint64Array(8); +// new Uint8Array(bigArray.buffer).fill(255); +// var bigIntArray = new BigInt64Array(bigArray.buffer); +// expect(identity_uint64_t(bigArray[0])).toBe(bigArray[0]); +// expect(identity_uint64_t(bigArray[0] - BigInt(1))).toBe( +// bigArray[0] - BigInt(1) +// ); + +// expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0); +// expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe( +// 10 +// ); +// expect(identity_uint64_t(0)).toBe(0); +// expect(identity_uint64_t(100)).toBe(100); +// expect(identity_uint64_t(BigInt(100))).toBe(100); +// expect(identity_int64_t(bigIntArray[0])).toBe(bigIntArray[0]); +// expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe( +// bigIntArray[0] - BigInt(1) +// ); + +// expect(add_char(1, 1)).toBe(2); +// expect(add_float(2.4, 2.8)).toBe(Math.fround(5.2)); +// expect(add_double(4.2, 0.1)).toBe(4.3); +// expect(add_int8_t(1, 1)).toBe(2); +// expect(add_int16_t(1, 1)).toBe(2); +// expect(add_int32_t(1, 1)).toBe(2); +// expect(add_int64_t(1, 1)).toBe(2); +// expect(add_uint8_t(1, 1)).toBe(2); +// expect(add_uint16_t(1, 1)).toBe(2); +// expect(add_uint32_t(1, 1)).toBe(2); + +// const cptr = ptr_should_point_to_42_as_int32_t(); +// expect(cptr != 0).toBe(true); +// expect(typeof cptr === "number").toBe(true); +// expect(does_pointer_equal_42_as_int32_t(cptr)).toBe(true); +// const buffer = toBuffer(cptr, 0, 4); +// expect(buffer.readInt32(0)).toBe(42); +// expect(new DataView(toArrayBuffer(cptr, 0, 4), 0, 4).getInt32(0, true)).toBe( +// 42 +// ); +// expect(ptr(buffer)).toBe(cptr); +// expect(new CString(cptr, 0, 1).toString()).toBe("*"); +// expect(identity_ptr(cptr)).toBe(cptr); +// const second_ptr = ptr(new Buffer(8)); +// expect(identity_ptr(second_ptr)).toBe(second_ptr); + +// var myCFunction = new CFunction({ +// ptr: return_a_function_ptr_to_function_that_returns_true(), +// returns: "bool", +// }); +// expect(myCFunction()).toBe(true); + +// // function identityBool() { +// // return true; +// // } +// // globalThis.identityBool = identityBool; + +// // const first = native.callback( +// // { +// // returns: "bool", +// // }, +// // identityBool +// // ); +// // expect( +// // cb_identity_true() +// // ).toBe(true); + +// // expect(cb_identity_true(first)).toBe(true); + +// // expect( +// // cb_identity_false( +// // callback( +// // { +// // returns: "bool", +// // }, +// // () => false +// // ) +// // ) +// // ).toBe(false); + +// // expect( +// // cb_identity_42_char( +// // callback( +// // { +// // returns: "char", +// // }, +// // () => 42 +// // ) +// // ) +// // ).toBe(42); +// // expect( +// // cb_identity_42_uint8_t( +// // callback( +// // { +// // returns: "uint8_t", +// // }, +// // () => 42 +// // ) +// // ) +// // ).toBe(42); + +// // cb_identity_neg_42_int8_t( +// // callback( +// // { +// // returns: "int8_t", +// // }, +// // () => -42 +// // ) +// // ).toBe(-42); + +// // cb_identity_42_uint16_t( +// // callback( +// // { +// // returns: "uint16_t", +// // }, +// // () => 42 +// // ) +// // ).toBe(42); + +// // cb_identity_42_uint32_t( +// // callback( +// // { +// // returns: "uint32_t", +// // }, +// // () => 42 +// // ) +// // ).toBe(42); + +// // cb_identity_neg_42_int16_t( +// // callback( +// // { +// // returns: "int16_t", +// // }, +// // () => -42 +// // ) +// // ).toBe(-42); + +// // cb_identity_neg_42_int32_t( +// // callback( +// // { +// // returns: "int32_t", +// // }, +// // () => -42 +// // ) +// // ).toBe(-42); + +// close(); +// } + +// it("run ffi fast", () => { +// ffiRunner(getTypes(true)); +// }); + +// it("run ffi", () => { +// ffiRunner(getTypes(false)); +// }); diff --git a/test/bun.js/fs-stream.js b/test/bun.js/fs-stream.js new file mode 100644 index 000000000..4b71c95b7 --- /dev/null +++ b/test/bun.js/fs-stream.js @@ -0,0 +1,23 @@ +import { createReadStream, createWriteStream, readFileSync } from "fs"; + +await new Promise((resolve, reject) => { + createReadStream("fs-stream.js") + .pipe(createWriteStream("/tmp/fs-stream.copy.js")) + .once("error", (err) => reject(err)) + .once("finish", () => { + try { + const copied = readFileSync("/tmp/fs-stream.copy.js", "utf8"); + const real = readFileSync("/tmp/fs-stream.js", "utf8"); + if (copied !== real) { + reject( + new Error("fs-stream.js is not the same as fs-stream.copy.js") + ); + return; + } + + resolve(true); + } catch (err) { + reject(err); + } + }); +}); diff --git a/test/bun.js/fs.test.js b/test/bun.js/fs.test.js new file mode 100644 index 000000000..79ac60eaa --- /dev/null +++ b/test/bun.js/fs.test.js @@ -0,0 +1,244 @@ +import { gc } from "bun"; +import { describe, expect, it } from "bun:test"; +import { + closeSync, + existsSync, + mkdirSync, + openSync, + readdirSync, + readFile, + readFileSync, + readSync, + writeFileSync, + writeSync, +} from "node:fs"; + +const Buffer = globalThis.Buffer || Uint8Array; + +if (!import.meta.dir) { + import.meta.dir = "."; +} + +describe("mkdirSync", () => { + it("should create a directory", () => { + const tempdir = `/tmp/fs.test.js/${Date.now()}/1234/hi`; + expect(existsSync(tempdir)).toBe(false); + expect(tempdir.includes(mkdirSync(tempdir, { recursive: true }))).toBe( + true + ); + expect(existsSync(tempdir)).toBe(true); + }); +}); + +it("readdirSync on import.meta.dir", () => { + const dirs = readdirSync(import.meta.dir); + expect(dirs.length > 0).toBe(true); + var match = false; + gc(true); + for (let i = 0; i < dirs.length; i++) { + if (dirs[i] === import.meta.file) { + match = true; + } + } + gc(true); + expect(match).toBe(true); +}); + +it("readdirSync on import.meta.dir with trailing slash", () => { + const dirs = readdirSync(import.meta.dir + "/"); + expect(dirs.length > 0).toBe(true); + // this file should exist in it + var match = false; + for (let i = 0; i < dirs.length; i++) { + if (dirs[i] === import.meta.file) { + match = true; + } + } + expect(match).toBe(true); +}); + +it("readdirSync works on empty directories", () => { + const path = `/tmp/fs-test-empty-dir-${( + Math.random() * 100000 + + 100 + ).toString(32)}`; + mkdirSync(path, { recursive: true }); + expect(readdirSync(path).length).toBe(0); +}); + +it("readdirSync works on directories with under 32 files", () => { + const path = `/tmp/fs-test-one-dir-${(Math.random() * 100000 + 100).toString( + 32 + )}`; + mkdirSync(path, { recursive: true }); + writeFileSync(`${path}/a`, "a"); + const results = readdirSync(path); + expect(results.length).toBe(1); + expect(results[0]).toBe("a"); +}); + +it("readdirSync throws when given a file path", () => { + try { + readdirSync(import.meta.path); + throw new Error("should not get here"); + } catch (exception) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +it("readdirSync throws when given a path that doesn't exist", () => { + try { + readdirSync(import.meta.path + "/does-not-exist/really"); + throw new Error("should not get here"); + } catch (exception) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +it("readdirSync throws when given a file path with trailing slash", () => { + try { + readdirSync(import.meta.path + "/"); + throw new Error("should not get here"); + } catch (exception) { + expect(exception.name).toBe("ENOTDIR"); + } +}); + +describe("readSync", () => { + const firstFourBytes = new Uint32Array( + new TextEncoder().encode("File").buffer + )[0]; + it("works with a position set to 0", () => { + const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); + const four = new Uint8Array(4); + + { + const count = readSync(fd, four, 0, 4, 0); + const u32 = new Uint32Array(four.buffer)[0]; + expect(u32).toBe(firstFourBytes); + expect(count).toBe(4); + } + closeSync(fd); + }); + it("works without position set", () => { + const fd = openSync(import.meta.dir + "/readFileSync.txt", "r"); + const four = new Uint8Array(4); + { + const count = readSync(fd, four); + const u32 = new Uint32Array(four.buffer)[0]; + expect(u32).toBe(firstFourBytes); + expect(count).toBe(4); + } + closeSync(fd); + }); +}); + +describe("writeSync", () => { + it("works with a position set to 0", () => { + const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); + const four = new Uint8Array(4); + + { + const count = writeSync(fd, new TextEncoder().encode("File"), 0, 4, 0); + expect(count).toBe(4); + } + closeSync(fd); + }); + it("works without position set", () => { + const fd = openSync(import.meta.dir + "/writeFileSync.txt", "w+"); + const four = new Uint8Array(4); + { + const count = writeSync(fd, new TextEncoder().encode("File")); + expect(count).toBe(4); + } + closeSync(fd); + }); +}); + +describe("readFileSync", () => { + it("works", () => { + const text = readFileSync(import.meta.dir + "/readFileSync.txt", "utf8"); + expect(text).toBe("File read successfully"); + }); + + it("works with a file url", () => { + const text = readFileSync( + new URL("file://" + import.meta.dir + "/readFileSync.txt"), + "utf8" + ); + expect(text).toBe("File read successfully"); + }); + + it("returning Buffer works", () => { + const text = readFileSync(import.meta.dir + "/readFileSync.txt"); + const encoded = [ + 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, 115, + 115, 102, 117, 108, 108, 121, + ]; + for (let i = 0; i < encoded.length; i++) { + expect(text[i]).toBe(encoded[i]); + } + }); +}); + +describe("readFile", () => { + it("works", async () => { + await new Promise((resolve, reject) => { + readFile(import.meta.dir + "/readFileSync.txt", "utf8", (err, text) => { + expect(text).toBe("File read successfully"); + resolve(true); + }); + }); + }); + + it("returning Buffer works", async () => { + await new Promise((resolve, reject) => { + readFile(import.meta.dir + "/readFileSync.txt", (err, text) => { + const encoded = [ + 70, 105, 108, 101, 32, 114, 101, 97, 100, 32, 115, 117, 99, 99, 101, + 115, 115, 102, 117, 108, 108, 121, + ]; + for (let i = 0; i < encoded.length; i++) { + expect(text[i]).toBe(encoded[i]); + } + resolve(true); + }); + }); + }); +}); + +describe("writeFileSync", () => { + it("works", () => { + const path = `/tmp/${Date.now()}.writeFileSync.txt`; + writeFileSync(path, "File written successfully", "utf8"); + + expect(readFileSync(path, "utf8")).toBe("File written successfully"); + }); + + it("returning Buffer works", () => { + const buffer = new Buffer([ + 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, + 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, + ]); + const path = `/tmp/${Date.now()}.blob.writeFileSync.txt`; + writeFileSync(path, buffer); + const out = readFileSync(path); + + for (let i = 0; i < buffer.length; i++) { + expect(buffer[i]).toBe(out[i]); + } + }); + it("returning ArrayBuffer works", () => { + const buffer = new Buffer([ + 70, 105, 108, 101, 32, 119, 114, 105, 116, 116, 101, 110, 32, 115, 117, + 99, 99, 101, 115, 115, 102, 117, 108, 108, 121, + ]); + const path = `/tmp/${Date.now()}.blob2.writeFileSync.txt`; + writeFileSync(path, buffer); + const out = readFileSync(path); + + for (let i = 0; i < buffer.length; i++) { + expect(buffer[i]).toBe(out[i]); + } + }); +}); diff --git a/test/bun.js/gc.js b/test/bun.js/gc.js new file mode 100644 index 000000000..9212e8b76 --- /dev/null +++ b/test/bun.js/gc.js @@ -0,0 +1,15 @@ +export function gc() { + // console.trace("GC"); + Bun.gc(true); +} + +// we must ensure that finalizers are run +// so that the reference-counting logic is exercised +export function gcTick(trace = false) { + trace && console.trace(""); + // console.trace("hello"); + gc(); + return new Promise((resolve) => { + setTimeout(resolve, 0); + }); +} diff --git a/test/bun.js/globals.test.js b/test/bun.js/globals.test.js new file mode 100644 index 000000000..b498e0e8e --- /dev/null +++ b/test/bun.js/globals.test.js @@ -0,0 +1,39 @@ +import { it, describe, expect } from "bun:test"; + +it("extendable", () => { + const classes = [ + Blob, + TextDecoder, + TextEncoder, + Request, + Response, + Headers, + HTMLRewriter, + Bun.Transpiler, + ]; + // None of these should error + for (let Class of classes) { + var Foo = class extends Class {}; + var bar = new Foo(); + expect(bar instanceof Class).toBe(true); + expect(!!Class.prototype).toBe(true); + expect(typeof Class.prototype).toBe("object"); + } + expect(true).toBe(true); +}); + +it("name", () => { + const classes = [ + ["Blob", Blob], + ["TextDecoder", TextDecoder], + ["TextEncoder", TextEncoder], + ["Request", Request], + ["Response", Response], + ["Headers", Headers], + ["HTMLRewriter", HTMLRewriter], + ["Transpiler", Bun.Transpiler], + ]; + for (let [name, Class] of classes) { + expect(Class.name).toBe(name); + } +}); diff --git a/test/bun.js/hash.test.js b/test/bun.js/hash.test.js new file mode 100644 index 000000000..71ad5a229 --- /dev/null +++ b/test/bun.js/hash.test.js @@ -0,0 +1,36 @@ +import fs from "fs"; +import { it, expect } from "bun:test"; +import path from "path"; + +it(`Bun.hash()`, () => { + Bun.hash("hello world"); + Bun.hash(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.wyhash()`, () => { + Bun.hash.wyhash("hello world"); + Bun.hash.wyhash(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.adler32()`, () => { + Bun.hash.adler32("hello world"); + Bun.hash.adler32(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.crc32()`, () => { + Bun.hash.crc32("hello world"); + Bun.hash.crc32(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.cityHash32()`, () => { + Bun.hash.cityHash32("hello world"); + Bun.hash.cityHash32(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.cityHash64()`, () => { + Bun.hash.cityHash64("hello world"); + Bun.hash.cityHash64(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.murmur32v3()`, () => { + Bun.hash.murmur32v3("hello world"); + Bun.hash.murmur32v3(new TextEncoder().encode("hello world")); +}); +it(`Bun.hash.murmur64v2()`, () => { + Bun.hash.murmur64v2("hello world"); + Bun.hash.murmur64v2(new TextEncoder().encode("hello world")); +}); diff --git a/test/bun.js/html-rewriter.test.js b/test/bun.js/html-rewriter.test.js new file mode 100644 index 000000000..29b765c2f --- /dev/null +++ b/test/bun.js/html-rewriter.test.js @@ -0,0 +1,293 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "./gc"; + +var setTimeoutAsync = (fn, delay) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + try { + resolve(fn()); + } catch (e) { + reject(e); + } + }, delay); + }); +}; + +describe("HTMLRewriter", () => { + it("HTMLRewriter: async replacement", async () => { + await gcTick(); + const res = new HTMLRewriter() + .on("div", { + async element(element) { + await setTimeoutAsync(() => { + element.setInnerContent("<span>replace</span>", { html: true }); + }, 5); + }, + }) + .transform(new Response("<div>example.com</div>")); + await gcTick(); + expect(await res.text()).toBe("<div><span>replace</span></div>"); + await gcTick(); + }); + + it("supports element handlers", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("<blink>it worked!</blink>", { html: true }); + }, + }); + var input = new Response("<div>hello</div>"); + var output = rewriter.transform(input); + expect(await output.text()).toBe("<div><blink>it worked!</blink></div>"); + }); + + it("(from file) supports element handlers", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("<blink>it worked!</blink>", { html: true }); + }, + }); + await Bun.write("/tmp/html-rewriter.txt.js", "<div>hello</div>"); + var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); + var output = rewriter.transform(input); + expect(await output.text()).toBe("<div><blink>it worked!</blink></div>"); + }); + + it("supports attribute iterator", async () => { + var rewriter = new HTMLRewriter(); + var expected = [ + ["first", ""], + ["second", "alrihgt"], + ["third", "123"], + ["fourth", "5"], + ["fifth", "helloooo"], + ]; + rewriter.on("div", { + element(element2) { + for (let attr of element2.attributes) { + const stack = expected.shift(); + expect(stack[0]).toBe(attr[0]); + expect(stack[1]).toBe(attr[1]); + } + }, + }); + var input = new Response( + '<div first second="alrihgt" third="123" fourth=5 fifth=helloooo>hello</div>' + ); + var output = rewriter.transform(input); + expect(await output.text()).toBe( + '<div first second="alrihgt" third="123" fourth=5 fifth=helloooo>hello</div>' + ); + expect(expected.length).toBe(0); + }); + + it("handles element specific mutations", async () => { + // prepend/append + let res = new HTMLRewriter() + .on("p", { + element(element) { + element.prepend("<span>prepend</span>"); + element.prepend("<span>prepend html</span>", { html: true }); + element.append("<span>append</span>"); + element.append("<span>append html</span>", { html: true }); + }, + }) + .transform(new Response("<p>test</p>")); + expect(await res.text()).toBe( + [ + "<p>", + "<span>prepend html</span>", + "<span>prepend</span>", + "test", + "<span>append</span>", + "<span>append html</span>", + "</p>", + ].join("") + ); + + // setInnerContent + res = new HTMLRewriter() + .on("p", { + element(element) { + element.setInnerContent("<span>replace</span>"); + }, + }) + .transform(new Response("<p>test</p>")); + expect(await res.text()).toBe("<p><span>replace</span></p>"); + res = new HTMLRewriter() + .on("p", { + element(element) { + element.setInnerContent("<span>replace</span>", { html: true }); + }, + }) + .transform(new Response("<p>test</p>")); + expect(await res.text()).toBe("<p><span>replace</span></p>"); + + // removeAndKeepContent + res = new HTMLRewriter() + .on("p", { + element(element) { + element.removeAndKeepContent(); + }, + }) + .transform(new Response("<p>test</p>")); + expect(await res.text()).toBe("test"); + }); + + it("handles element class properties", async () => { + class Handler { + constructor(content) { + this.content = content; + } + + // noinspection JSUnusedGlobalSymbols + element(element) { + element.setInnerContent(this.content); + } + } + const res = new HTMLRewriter() + .on("p", new Handler("new")) + .transform(new Response("<p>test</p>")); + expect(await res.text()).toBe("<p>new</p>"); + }); + + const commentsMutationsInput = "<p><!--test--></p>"; + const commentsMutationsExpected = { + beforeAfter: [ + "<p>", + "<span>before</span>", + "<span>before html</span>", + "<!--test-->", + "<span>after html</span>", + "<span>after</span>", + "</p>", + ].join(""), + replace: "<p><span>replace</span></p>", + replaceHtml: "<p><span>replace</span></p>", + remove: "<p></p>", + }; + + const commentPropertiesMacro = async (func) => { + const res = func(new HTMLRewriter(), (comment) => { + expect(comment.removed).toBe(false); + expect(comment.text).toBe("test"); + comment.text = "new"; + expect(comment.text).toBe("new"); + }).transform(new Response("<p><!--test--></p>")); + expect(await res.text()).toBe("<p><!--new--></p>"); + }; + + it("HTMLRewriter: handles comment properties", () => + commentPropertiesMacro((rw, comments) => { + rw.on("p", { comments }); + return rw; + })); + + it("selector tests", async () => { + const checkSelector = async (selector, input, expected) => { + const res = new HTMLRewriter() + .on(selector, { + element(element) { + element.setInnerContent("new"); + }, + }) + .transform(new Response(input)); + expect(await res.text()).toBe(expected); + }; + + await checkSelector("*", "<h1>1</h1><p>2</p>", "<h1>new</h1><p>new</p>"); + await checkSelector("p", "<h1>1</h1><p>2</p>", "<h1>1</h1><p>new</p>"); + await checkSelector( + "p:nth-child(2)", + "<div><p>1</p><p>2</p><p>3</p></div>", + "<div><p>1</p><p>new</p><p>3</p></div>" + ); + await checkSelector( + "p:first-child", + "<div><p>1</p><p>2</p><p>3</p></div>", + "<div><p>new</p><p>2</p><p>3</p></div>" + ); + await checkSelector( + "p:nth-of-type(2)", + "<div><p>1</p><h1>2</h1><p>3</p><h1>4</h1><p>5</p></div>", + "<div><p>1</p><h1>2</h1><p>new</p><h1>4</h1><p>5</p></div>" + ); + await checkSelector( + "p:first-of-type", + "<div><h1>1</h1><p>2</p><p>3</p></div>", + "<div><h1>1</h1><p>new</p><p>3</p></div>" + ); + await checkSelector( + "p:not(:first-child)", + "<div><p>1</p><p>2</p><p>3</p></div>", + "<div><p>1</p><p>new</p><p>new</p></div>" + ); + await checkSelector( + "p.red", + '<p class="red">1</p><p>2</p>', + '<p class="red">new</p><p>2</p>' + ); + await checkSelector( + "h1#header", + '<h1 id="header">1</h1><h1>2</h1>', + '<h1 id="header">new</h1><h1>2</h1>' + ); + await checkSelector( + "p[data-test]", + "<p data-test>1</p><p>2</p>", + "<p data-test>new</p><p>2</p>" + ); + await checkSelector( + 'p[data-test="one"]', + '<p data-test="one">1</p><p data-test="two">2</p>', + '<p data-test="one">new</p><p data-test="two">2</p>' + ); + await checkSelector( + 'p[data-test="one" i]', + '<p data-test="one">1</p><p data-test="OnE">2</p><p data-test="two">3</p>', + '<p data-test="one">new</p><p data-test="OnE">new</p><p data-test="two">3</p>' + ); + await checkSelector( + 'p[data-test="one" s]', + '<p data-test="one">1</p><p data-test="OnE">2</p><p data-test="two">3</p>', + '<p data-test="one">new</p><p data-test="OnE">2</p><p data-test="two">3</p>' + ); + await checkSelector( + 'p[data-test~="two"]', + '<p data-test="one two three">1</p><p data-test="one two">2</p><p data-test="one">3</p>', + '<p data-test="one two three">new</p><p data-test="one two">new</p><p data-test="one">3</p>' + ); + await checkSelector( + 'p[data-test^="a"]', + '<p data-test="a1">1</p><p data-test="a2">2</p><p data-test="b1">3</p>', + '<p data-test="a1">new</p><p data-test="a2">new</p><p data-test="b1">3</p>' + ); + await checkSelector( + 'p[data-test$="1"]', + '<p data-test="a1">1</p><p data-test="a2">2</p><p data-test="b1">3</p>', + '<p data-test="a1">new</p><p data-test="a2">2</p><p data-test="b1">new</p>' + ); + await checkSelector( + 'p[data-test*="b"]', + '<p data-test="abc">1</p><p data-test="ab">2</p><p data-test="a">3</p>', + '<p data-test="abc">new</p><p data-test="ab">new</p><p data-test="a">3</p>' + ); + await checkSelector( + 'p[data-test|="a"]', + '<p data-test="a">1</p><p data-test="a-1">2</p><p data-test="a2">3</p>', + '<p data-test="a">new</p><p data-test="a-1">new</p><p data-test="a2">3</p>' + ); + await checkSelector( + "div span", + "<div><h1><span>1</span></h1><span>2</span><b>3</b></div>", + "<div><h1><span>new</span></h1><span>new</span><b>3</b></div>" + ); + await checkSelector( + "div > span", + "<div><h1><span>1</span></h1><span>2</span><b>3</b></div>", + "<div><h1><span>1</span></h1><span>new</span><b>3</b></div>" + ); + }); +}); diff --git a/test/bun.js/import-meta.test.js b/test/bun.js/import-meta.test.js new file mode 100644 index 000000000..0e2faa903 --- /dev/null +++ b/test/bun.js/import-meta.test.js @@ -0,0 +1,33 @@ +import { it, expect } from "bun:test"; +import * as Module from "node:module"; +import sync from "./require-json.json"; + +const { path, dir } = import.meta; + +it("import.meta.resolveSync", () => { + expect( + import.meta.resolveSync("./" + import.meta.file, import.meta.path) + ).toBe(path); + const require = Module.createRequire(import.meta.path); + expect(require.resolve(import.meta.path)).toBe(path); + expect(require.resolve("./" + import.meta.file)).toBe(path); + + // check it works with URL objects + expect( + Module.createRequire(new URL(import.meta.url)).resolve(import.meta.path) + ).toBe(import.meta.path); +}); + +it("import.meta.require", () => { + expect(import.meta.require("./require-json.json").hello).toBe(sync.hello); + const require = Module.createRequire(import.meta.path); + expect(require("./require-json.json").hello).toBe(sync.hello); +}); + +it("import.meta.dir", () => { + expect(dir.endsWith("/bun/test/bun.js")).toBe(true); +}); + +it("import.meta.path", () => { + expect(path.endsWith("/bun/test/bun.js/import-meta.test.js")).toBe(true); +}); diff --git a/test/bun.js/inline.macro.js b/test/bun.js/inline.macro.js new file mode 100644 index 000000000..ff0292d0a --- /dev/null +++ b/test/bun.js/inline.macro.js @@ -0,0 +1,3 @@ +export function whatDidIPass(expr, ctx) { + return ctx; +} diff --git a/test/bun.js/inspect.test.js b/test/bun.js/inspect.test.js new file mode 100644 index 000000000..bf5021c33 --- /dev/null +++ b/test/bun.js/inspect.test.js @@ -0,0 +1,96 @@ +import { it, expect } from "bun:test"; + +it("jsx with two elements", () => { + const input = Bun.inspect( + <div hello="quoted"> + <input type="text" value={"123"} /> + string inside child + </div> + ); + + const output = `<div hello="quoted"> + <input type="text" value="123" /> + string inside child +</div>`; + + expect(input).toBe(output); +}); + +const Foo = () => <div hello="quoted">foo</div>; + +it("jsx with anon component", () => { + const input = Bun.inspect(<Foo />); + + const output = `<NoName />`; + + expect(input).toBe(output); +}); + +it("jsx with fragment", () => { + const input = Bun.inspect(<>foo bar</>); + + const output = `<>foo bar</>`; + + expect(input).toBe(output); +}); + +it("inspect", () => { + expect(Bun.inspect(new TypeError("what")).includes("TypeError: what")).toBe( + true + ); + expect("hi").toBe("hi"); + expect(Bun.inspect(1)).toBe("1"); + expect(Bun.inspect(1, "hi")).toBe("1 hi"); + expect(Bun.inspect([])).toBe("[]"); + expect(Bun.inspect({})).toBe("{ }"); + expect(Bun.inspect({ hello: 1 })).toBe("{ hello: 1 }"); + expect(Bun.inspect({ hello: 1, there: 2 })).toBe("{ hello: 1, there: 2 }"); + expect(Bun.inspect({ hello: "1", there: 2 })).toBe( + '{ hello: "1", there: 2 }' + ); + expect(Bun.inspect({ 'hello-"there': "1", there: 2 })).toBe( + '{ "hello-\\"there": "1", there: 2 }' + ); + var str = "123"; + while (str.length < 4096) { + str += "123"; + } + expect(Bun.inspect(str)).toBe(str); + // expect(Bun.inspect(new Headers())).toBe("Headers (0 KB) {}"); + expect(Bun.inspect(new Response()).length > 0).toBe(true); + // expect( + // JSON.stringify( + // new Headers({ + // hi: "ok", + // }) + // ) + // ).toBe('{"hi":"ok"}'); + expect(Bun.inspect(new Set())).toBe("Set {}"); + expect(Bun.inspect(new Map())).toBe("Map {}"); + expect(Bun.inspect(new Map([["foo", "bar"]]))).toBe( + 'Map(1) {\n "foo": "bar",\n}' + ); + expect(Bun.inspect(new Set(["bar"]))).toBe('Set(1) {\n "bar",\n}'); + expect(Bun.inspect(<div>foo</div>)).toBe("<div>foo</div>"); + expect(Bun.inspect(<div hello>foo</div>)).toBe("<div hello=true>foo</div>"); + expect(Bun.inspect(<div hello={1}>foo</div>)).toBe("<div hello=1>foo</div>"); + expect(Bun.inspect(<div hello={123}>hi</div>)).toBe( + "<div hello=123>hi</div>" + ); + expect(Bun.inspect(<div hello="quoted">quoted</div>)).toBe( + '<div hello="quoted">quoted</div>' + ); + expect( + Bun.inspect( + <div hello="quoted"> + <input type="text" value={"123"} /> + </div> + ) + ).toBe( + ` +<div hello="quoted"> + <input type="text" value="123" /> +</div>`.trim() + ); + expect(Bun.inspect(BigInt(32))).toBe("32n"); +}); diff --git a/test/bun.js/macro-check.js b/test/bun.js/macro-check.js new file mode 100644 index 000000000..0f494a4e7 --- /dev/null +++ b/test/bun.js/macro-check.js @@ -0,0 +1,7 @@ +export function keepSecondArgument(bacon1234) { + return bacon1234.arguments[1]; +} + +export function bacon(heloooo) { + return heloooo.arguments[1]; +} diff --git a/test/bun.js/microtask.test.js b/test/bun.js/microtask.test.js new file mode 100644 index 000000000..18956b1e5 --- /dev/null +++ b/test/bun.js/microtask.test.js @@ -0,0 +1,80 @@ +import { it } from "bun:test"; + +it("queueMicrotask", async () => { + // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. + var run = 0; + + await new Promise((resolve, reject) => { + queueMicrotask(() => { + if (run++ != 0) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 3) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + queueMicrotask(() => { + if (run++ != 1) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 4) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 6) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + }); + queueMicrotask(() => { + if (run++ != 2) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 5) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 7) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + resolve(true); + }); + }); + }); + }); + + { + var passed = false; + try { + queueMicrotask(1234); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) + throw new Error( + "queueMicrotask should throw a TypeError if the argument is not a function" + ); + } + + { + var passed = false; + try { + queueMicrotask(); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) + throw new Error( + "queueMicrotask should throw a TypeError if the argument is empty" + ); + } +}); diff --git a/test/bun.js/mmap.test.js b/test/bun.js/mmap.test.js new file mode 100644 index 000000000..2b15a4000 --- /dev/null +++ b/test/bun.js/mmap.test.js @@ -0,0 +1,69 @@ +import { describe, it, expect } from "bun:test"; +import { gcTick } from "./gc"; + +describe("Bun.mmap", async () => { + await gcTick(); + const path = `/tmp/bun-mmap-test_${Math.random()}.txt`; + await gcTick(); + await Bun.write(path, "hello"); + await gcTick(); + + it("mmap finalizer", async () => { + let map = Bun.mmap(path); + await gcTick(); + const map2 = Bun.mmap(path); + + map = null; + await gcTick(); + }); + + it("mmap passed to other syscalls", async () => { + const map = Bun.mmap(path); + await gcTick(); + await Bun.write(path + "1", map); + await gcTick(); + const text = await (await Bun.file(path + "1")).text(); + await gcTick(); + + expect(text).toBe(new TextDecoder().decode(map)); + }); + + it("mmap sync", async () => { + const map = Bun.mmap(path); + await gcTick(); + const map2 = Bun.mmap(path); + await gcTick(); + + const old = map[0]; + await gcTick(); + map[0] = 0; + await gcTick(); + expect(map2[0]).toBe(0); + + map2[0] = old; + await gcTick(); + expect(map[0]).toBe(old); + await gcTick(); + await Bun.write(path, "olleh"); + await gcTick(); + expect(new TextDecoder().decode(map)).toBe("olleh"); + await gcTick(); + }); + + it("mmap private", async () => { + await gcTick(); + const map = Bun.mmap(path, { shared: true }); + await gcTick(); + const map2 = Bun.mmap(path, { shared: false }); + await gcTick(); + const old = map[0]; + + await gcTick(); + map2[0] = 0; + await gcTick(); + expect(map2[0]).toBe(0); + await gcTick(); + expect(map[0]).toBe(old); + await gcTick(); + }); +}); diff --git a/test/bun.js/node-builtins.test.js b/test/bun.js/node-builtins.test.js new file mode 100644 index 000000000..df31c64fc --- /dev/null +++ b/test/bun.js/node-builtins.test.js @@ -0,0 +1,16 @@ +import { describe, it, expect } from "bun:test"; + +import { EventEmitter } from "events"; + +describe("EventEmitter", () => { + it("should emit events", () => { + const emitter = new EventEmitter(); + var called = false; + const listener = () => { + called = true; + }; + emitter.on("test", listener); + emitter.emit("test"); + expect(called).toBe(true); + }); +}); diff --git a/test/bun.js/path.test.js b/test/bun.js/path.test.js new file mode 100644 index 000000000..997368150 --- /dev/null +++ b/test/bun.js/path.test.js @@ -0,0 +1,457 @@ +const { file } = import.meta; + +import { describe, it, expect } from "bun:test"; +import * as path from "node:path"; +import assert from "assert"; + +const strictEqual = (...args) => { + assert.strictEqual(...args); + expect(true).toBe(true); +}; + +it("path.basename", () => { + strictEqual(path.basename(file), "path.test.js"); + strictEqual(path.basename(file, ".js"), "path.test"); + strictEqual(path.basename(".js", ".js"), ""); + strictEqual(path.basename(""), ""); + strictEqual(path.basename("/dir/basename.ext"), "basename.ext"); + strictEqual(path.basename("/basename.ext"), "basename.ext"); + strictEqual(path.basename("basename.ext"), "basename.ext"); + strictEqual(path.basename("basename.ext/"), "basename.ext"); + strictEqual(path.basename("basename.ext//"), "basename.ext"); + strictEqual(path.basename("aaa/bbb", "/bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb", "a/bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb", "bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb//", "bbb"), "bbb"); + strictEqual(path.basename("aaa/bbb", "bb"), "b"); + strictEqual(path.basename("aaa/bbb", "b"), "bb"); + strictEqual(path.basename("/aaa/bbb", "/bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb", "a/bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb", "bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb//", "bbb"), "bbb"); + strictEqual(path.basename("/aaa/bbb", "bb"), "b"); + strictEqual(path.basename("/aaa/bbb", "b"), "bb"); + strictEqual(path.basename("/aaa/bbb"), "bbb"); + strictEqual(path.basename("/aaa/"), "aaa"); + strictEqual(path.basename("/aaa/b"), "b"); + strictEqual(path.basename("/a/b"), "b"); + strictEqual(path.basename("//a"), "a"); + strictEqual(path.basename("a", "a"), ""); + + // // On Windows a backslash acts as a path separator. + strictEqual(path.win32.basename("\\dir\\basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("\\basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("basename.ext\\"), "basename.ext"); + strictEqual(path.win32.basename("basename.ext\\\\"), "basename.ext"); + strictEqual(path.win32.basename("foo"), "foo"); + strictEqual(path.win32.basename("aaa\\bbb", "\\bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb", "a\\bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb", "bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb\\\\\\\\", "bbb"), "bbb"); + strictEqual(path.win32.basename("aaa\\bbb", "bb"), "b"); + strictEqual(path.win32.basename("aaa\\bbb", "b"), "bb"); + strictEqual(path.win32.basename("C:"), ""); + strictEqual(path.win32.basename("C:."), "."); + strictEqual(path.win32.basename("C:\\"), ""); + strictEqual(path.win32.basename("C:\\dir\\base.ext"), "base.ext"); + strictEqual(path.win32.basename("C:\\basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("C:basename.ext"), "basename.ext"); + strictEqual(path.win32.basename("C:basename.ext\\"), "basename.ext"); + strictEqual(path.win32.basename("C:basename.ext\\\\"), "basename.ext"); + strictEqual(path.win32.basename("C:foo"), "foo"); + strictEqual(path.win32.basename("file:stream"), "file:stream"); + strictEqual(path.win32.basename("a", "a"), ""); + + // On unix a backslash is just treated as any other character. + strictEqual( + path.posix.basename("\\dir\\basename.ext"), + "\\dir\\basename.ext" + ); + strictEqual(path.posix.basename("\\basename.ext"), "\\basename.ext"); + strictEqual(path.posix.basename("basename.ext"), "basename.ext"); + strictEqual(path.posix.basename("basename.ext\\"), "basename.ext\\"); + strictEqual(path.posix.basename("basename.ext\\\\"), "basename.ext\\\\"); + strictEqual(path.posix.basename("foo"), "foo"); + + // POSIX filenames may include control characters + // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html + const controlCharFilename = `Icon${String.fromCharCode(13)}`; + strictEqual( + path.posix.basename(`/a/b/${controlCharFilename}`), + controlCharFilename + ); +}); + +it("path.join", () => { + const failures = []; + const backslashRE = /\\/g; + + const joinTests = [ + [ + [path.posix.join], + // Arguments result + [ + [[".", "x/b", "..", "/b/c.js"], "x/b/c.js"], + // [[], '.'], + [["/.", "x/b", "..", "/b/c.js"], "/x/b/c.js"], + [["/foo", "../../../bar"], "/bar"], + [["foo", "../../../bar"], "../../bar"], + [["foo/", "../../../bar"], "../../bar"], + [["foo/x", "../../../bar"], "../bar"], + [["foo/x", "./bar"], "foo/x/bar"], + [["foo/x/", "./bar"], "foo/x/bar"], + [["foo/x/", ".", "bar"], "foo/x/bar"], + [["./"], "./"], + [[".", "./"], "./"], + [[".", ".", "."], "."], + [[".", "./", "."], "."], + [[".", "/./", "."], "."], + [[".", "/////./", "."], "."], + [["."], "."], + [["", "."], "."], + [["", "foo"], "foo"], + [["foo", "/bar"], "foo/bar"], + [["", "/foo"], "/foo"], + [["", "", "/foo"], "/foo"], + [["", "", "foo"], "foo"], + [["foo", ""], "foo"], + [["foo/", ""], "foo/"], + [["foo", "", "/bar"], "foo/bar"], + [["./", "..", "/foo"], "../foo"], + [["./", "..", "..", "/foo"], "../../foo"], + [[".", "..", "..", "/foo"], "../../foo"], + [["", "..", "..", "/foo"], "../../foo"], + [["/"], "/"], + [["/", "."], "/"], + [["/", ".."], "/"], + [["/", "..", ".."], "/"], + [[""], "."], + [["", ""], "."], + [[" /foo"], " /foo"], + [[" ", "foo"], " /foo"], + [[" ", "."], " "], + [[" ", "/"], " /"], + [[" ", ""], " "], + [["/", "foo"], "/foo"], + [["/", "/foo"], "/foo"], + [["/", "//foo"], "/foo"], + [["/", "", "/foo"], "/foo"], + [["", "/", "foo"], "/foo"], + [["", "/", "/foo"], "/foo"], + ], + ], + ]; + + // // Windows-specific join tests + // joinTests.push([ + // path.win32.join, + // joinTests[0][1].slice(0).concat([ + // // Arguments result + // // UNC path expected + // [["//foo/bar"], "\\\\foo\\bar\\"], + // [["\\/foo/bar"], "\\\\foo\\bar\\"], + // [["\\\\foo/bar"], "\\\\foo\\bar\\"], + // // UNC path expected - server and share separate + // [["//foo", "bar"], "\\\\foo\\bar\\"], + // [["//foo/", "bar"], "\\\\foo\\bar\\"], + // [["//foo", "/bar"], "\\\\foo\\bar\\"], + // // UNC path expected - questionable + // [["//foo", "", "bar"], "\\\\foo\\bar\\"], + // [["//foo/", "", "bar"], "\\\\foo\\bar\\"], + // [["//foo/", "", "/bar"], "\\\\foo\\bar\\"], + // // UNC path expected - even more questionable + // [["", "//foo", "bar"], "\\\\foo\\bar\\"], + // [["", "//foo/", "bar"], "\\\\foo\\bar\\"], + // [["", "//foo/", "/bar"], "\\\\foo\\bar\\"], + // // No UNC path expected (no double slash in first component) + // [["\\", "foo/bar"], "\\foo\\bar"], + // [["\\", "/foo/bar"], "\\foo\\bar"], + // [["", "/", "/foo/bar"], "\\foo\\bar"], + // // No UNC path expected (no non-slashes in first component - + // // questionable) + // [["//", "foo/bar"], "\\foo\\bar"], + // [["//", "/foo/bar"], "\\foo\\bar"], + // [["\\\\", "/", "/foo/bar"], "\\foo\\bar"], + // [["//"], "\\"], + // // No UNC path expected (share name missing - questionable). + // [["//foo"], "\\foo"], + // [["//foo/"], "\\foo\\"], + // [["//foo", "/"], "\\foo\\"], + // [["//foo", "", "/"], "\\foo\\"], + // // No UNC path expected (too many leading slashes - questionable) + // [["///foo/bar"], "\\foo\\bar"], + // [["////foo", "bar"], "\\foo\\bar"], + // [["\\\\\\/foo/bar"], "\\foo\\bar"], + // // Drive-relative vs drive-absolute paths. This merely describes the + // // status quo, rather than being obviously right + // [["c:"], "c:."], + // [["c:."], "c:."], + // [["c:", ""], "c:."], + // [["", "c:"], "c:."], + // [["c:.", "/"], "c:.\\"], + // [["c:.", "file"], "c:file"], + // [["c:", "/"], "c:\\"], + // [["c:", "file"], "c:\\file"], + // ]), + // ]); + joinTests.forEach((test) => { + if (!Array.isArray(test[0])) test[0] = [test[0]]; + test[0].forEach((join) => { + test[1].forEach((test) => { + const actual = join.apply(null, test[0]); + const expected = test[1]; + // For non-Windows specific tests with the Windows join(), we need to try + // replacing the slashes since the non-Windows specific tests' `expected` + // use forward slashes + let actualAlt; + let os; + if (join === path.win32.join) { + actualAlt = actual.replace(backslashRE, "/"); + os = "win32"; + } else { + os = "posix"; + } + if (actual !== expected && actualAlt !== expected) { + const delimiter = test[0].map(JSON.stringify).join(","); + const message = `path.${os}.join(${delimiter})\n expect=${JSON.stringify( + expected + )}\n actual=${JSON.stringify(actual)}`; + failures.push(`\n${message}`); + } + }); + }); + }); + strictEqual(failures.length, 0, failures.join("")); +}); + +it("path.relative", () => { + const failures = []; + + const relativeTests = [ + // [ + // path.win32.relative, + // // Arguments result + // [ + // ["c:/blah\\blah", "d:/games", "d:\\games"], + // ["c:/aaaa/bbbb", "c:/aaaa", ".."], + // ["c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"], + // ["c:/aaaa/bbbb", "c:/aaaa/bbbb", ""], + // ["c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"], + // ["c:/aaaa/", "c:/aaaa/cccc", "cccc"], + // ["c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"], + // ["c:/aaaa/bbbb", "d:\\", "d:\\"], + // ["c:/AaAa/bbbb", "c:/aaaa/bbbb", ""], + // ["c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"], + // ["C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."], + // [ + // "C:\\foo\\test", + // "C:\\foo\\test\\bar\\package.json", + // "bar\\package.json", + // ], + // ["C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"], + // ["C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"], + // ["\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"], + // ["\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."], + // ["\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"], + // ["\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"], + // ["C:\\baz-quux", "C:\\baz", "..\\baz"], + // ["C:\\baz", "C:\\baz-quux", "..\\baz-quux"], + // ["\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"], + // ["\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"], + // ["C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"], + // ["\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"], + // ], + // ], + [ + path.posix.relative, + // Arguments result + [ + ["/var/lib", "/var", ".."], + ["/var/lib", "/bin", "../../bin"], + ["/var/lib", "/var/lib", ""], + ["/var/lib", "/var/apache", "../apache"], + ["/var/", "/var/lib", "lib"], + ["/", "/var/lib", "var/lib"], + ["/foo/test", "/foo/test/bar/package.json", "bar/package.json"], + ["/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."], + ["/foo/bar/baz-quux", "/foo/bar/baz", "../baz"], + ["/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"], + ["/baz-quux", "/baz", "../baz"], + ["/baz", "/baz-quux", "../baz-quux"], + ["/page1/page2/foo", "/", "../../.."], + ], + ], + ]; + + relativeTests.forEach((test) => { + const relative = test[0]; + test[1].forEach((test) => { + const actual = relative(test[0], test[1]); + const expected = test[2]; + if (actual !== expected) { + const os = relative === path.win32.relative ? "win32" : "posix"; + const message = `path.${os}.relative(${test + .slice(0, 2) + .map(JSON.stringify) + .join(",")})\n expect=${JSON.stringify( + expected + )}\n actual=${JSON.stringify(actual)}`; + failures.push(`\n${message}`); + } + }); + }); + + strictEqual(failures.length, 0, failures.join("")); + expect(true).toBe(true); +}); + +it("path.normalize", () => { + // strictEqual( + // path.win32.normalize("./fixtures///b/../b/c.js"), + // "fixtures\\b\\c.js" + // ); + // strictEqual(path.win32.normalize("/foo/../../../bar"), "\\bar"); + // strictEqual(path.win32.normalize("a//b//../b"), "a\\b"); + // strictEqual(path.win32.normalize("a//b//./c"), "a\\b\\c"); + // strictEqual(path.win32.normalize("a//b//."), "a\\b"); + // strictEqual( + // path.win32.normalize("//server/share/dir/file.ext"), + // "\\\\server\\share\\dir\\file.ext" + // ); + // strictEqual(path.win32.normalize("/a/b/c/../../../x/y/z"), "\\x\\y\\z"); + // strictEqual(path.win32.normalize("C:"), "C:."); + // strictEqual(path.win32.normalize("C:..\\abc"), "C:..\\abc"); + // strictEqual(path.win32.normalize("C:..\\..\\abc\\..\\def"), "C:..\\..\\def"); + // strictEqual(path.win32.normalize("C:\\."), "C:\\"); + // strictEqual(path.win32.normalize("file:stream"), "file:stream"); + // strictEqual(path.win32.normalize("bar\\foo..\\..\\"), "bar\\"); + // strictEqual(path.win32.normalize("bar\\foo..\\.."), "bar"); + // strictEqual(path.win32.normalize("bar\\foo..\\..\\baz"), "bar\\baz"); + // strictEqual(path.win32.normalize("bar\\foo..\\"), "bar\\foo..\\"); + // strictEqual(path.win32.normalize("bar\\foo.."), "bar\\foo.."); + // strictEqual(path.win32.normalize("..\\foo..\\..\\..\\bar"), "..\\..\\bar"); + // strictEqual( + // path.win32.normalize("..\\...\\..\\.\\...\\..\\..\\bar"), + // "..\\..\\bar" + // ); + // strictEqual( + // path.win32.normalize("../../../foo/../../../bar"), + // "..\\..\\..\\..\\..\\bar" + // ); + // strictEqual( + // path.win32.normalize("../../../foo/../../../bar/../../"), + // "..\\..\\..\\..\\..\\..\\" + // ); + // strictEqual( + // path.win32.normalize("../foobar/barfoo/foo/../../../bar/../../"), + // "..\\..\\" + // ); + // strictEqual( + // path.win32.normalize("../.../../foobar/../../../bar/../../baz"), + // "..\\..\\..\\..\\baz" + // ); + // strictEqual(path.win32.normalize("foo/bar\\baz"), "foo\\bar\\baz"); + + strictEqual( + path.posix.normalize("./fixtures///b/../b/c.js"), + "fixtures/b/c.js" + ); + strictEqual(path.posix.normalize("/foo/../../../bar"), "/bar"); + strictEqual(path.posix.normalize("a//b//../b"), "a/b"); + strictEqual(path.posix.normalize("a//b//./c"), "a/b/c"); + strictEqual(path.posix.normalize("a//b//."), "a/b"); + strictEqual(path.posix.normalize("/a/b/c/../../../x/y/z"), "/x/y/z"); + strictEqual(path.posix.normalize("///..//./foo/.//bar"), "/foo/bar"); + strictEqual(path.posix.normalize("bar/foo../../"), "bar/"); + strictEqual(path.posix.normalize("bar/foo../.."), "bar"); + strictEqual(path.posix.normalize("bar/foo../../baz"), "bar/baz"); + strictEqual(path.posix.normalize("bar/foo../"), "bar/foo../"); + strictEqual(path.posix.normalize("bar/foo.."), "bar/foo.."); + console.log("A"); + strictEqual(path.posix.normalize("../foo../../../bar"), "../../bar"); + console.log("B"); + strictEqual(path.posix.normalize("../.../.././.../../../bar"), "../../bar"); + strictEqual( + path.posix.normalize("../../../foo/../../../bar"), + "../../../../../bar" + ); + strictEqual( + path.posix.normalize("../../../foo/../../../bar/../../"), + "../../../../../../" + ); + strictEqual( + path.posix.normalize("../foobar/barfoo/foo/../../../bar/../../"), + "../../" + ); + strictEqual( + path.posix.normalize("../.../../foobar/../../../bar/../../baz"), + "../../../../baz" + ); + strictEqual(path.posix.normalize("foo/bar\\baz"), "foo/bar\\baz"); +}); + +it("path.resolve", () => { + const failures = []; + const slashRE = /\//g; + const backslashRE = /\\/g; + + const resolveTests = [ + // [ + // path.win32.resolve, + // // Arguments result + // [ + // [["c:/blah\\blah", "d:/games", "c:../a"], "c:\\blah\\a"], + // [["c:/ignore", "d:\\a/b\\c/d", "\\e.exe"], "d:\\e.exe"], + // [["c:/ignore", "c:/some/file"], "c:\\some\\file"], + // [["d:/ignore", "d:some/dir//"], "d:\\ignore\\some\\dir"], + // [["."], process.cwd()], + // [["//server/share", "..", "relative\\"], "\\\\server\\share\\relative"], + // [["c:/", "//"], "c:\\"], + // [["c:/", "//dir"], "c:\\dir"], + // [["c:/", "//server/share"], "\\\\server\\share\\"], + // [["c:/", "//server//share"], "\\\\server\\share\\"], + // [["c:/", "///some//dir"], "c:\\some\\dir"], + // [ + // ["C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"], + // "C:\\foo\\tmp.3\\cycles\\root.js", + // ], + // ], + // ], + [ + path.posix.resolve, + // Arguments result + [ + [["/var/lib", "../", "file/"], "/var/file"], + [["/var/lib", "/../", "file/"], "/file"], + [["a/b/c/", "../../.."], process.cwd()], + [["."], process.cwd()], + [["/some/dir", ".", "/absolute/"], "/absolute"], + [ + ["/foo/tmp.3/", "../tmp.3/cycles/root.js"], + "/foo/tmp.3/cycles/root.js", + ], + ], + ], + ]; + const isWindows = false; + resolveTests.forEach(([resolve, tests]) => { + tests.forEach(([test, expected]) => { + const actual = resolve.apply(null, test); + let actualAlt; + const os = resolve === path.win32.resolve ? "win32" : "posix"; + if (resolve === path.win32.resolve && !isWindows) + actualAlt = actual.replace(backslashRE, "/"); + else if (resolve !== path.win32.resolve && isWindows) + actualAlt = actual.replace(slashRE, "\\"); + + const message = `path.${os}.resolve(${test + .map(JSON.stringify) + .join(",")})\n expect=${JSON.stringify( + expected + )}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) failures.push(message); + }); + }); + strictEqual(failures.length, 0, failures.join("\n")); +}); diff --git a/test/bun.js/performance.test.js b/test/bun.js/performance.test.js new file mode 100644 index 000000000..5e8520638 --- /dev/null +++ b/test/bun.js/performance.test.js @@ -0,0 +1,18 @@ +import { expect, it } from "bun:test"; + +it("performance.now() should be monotonic", () => { + const first = performance.now(); + const second = performance.now(); + const third = performance.now(); + const fourth = performance.now(); + const fifth = performance.now(); + const sixth = performance.now(); + expect(first < second).toBe(true); + expect(second < third).toBe(true); + expect(third < fourth).toBe(true); + expect(fourth < fifth).toBe(true); + expect(fifth < sixth).toBe(true); + expect(Bun.nanoseconds() > 0).toBe(true); + expect(Bun.nanoseconds() > sixth).toBe(true); + expect(typeof Bun.nanoseconds() === "number").toBe(true); +}); diff --git a/test/bun.js/process-nexttick.js b/test/bun.js/process-nexttick.js new file mode 100644 index 000000000..337977c0a --- /dev/null +++ b/test/bun.js/process-nexttick.js @@ -0,0 +1,91 @@ +// You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. +var run = 0; + +var queueMicrotask = process.nextTick; + +await new Promise((resolve, reject) => { + queueMicrotask(() => { + if (run++ != 0) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 3) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + queueMicrotask(() => { + if (run++ != 1) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 4) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 6) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + }); + queueMicrotask(() => { + if (run++ != 2) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 5) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 7) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + resolve(true); + }); + }); + }); +}); + +{ + var passed = false; + try { + queueMicrotask(1234); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) + throw new Error( + "queueMicrotask should throw a TypeError if the argument is not a function" + ); +} + +{ + var passed = false; + try { + queueMicrotask(); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) + throw new Error( + "queueMicrotask should throw a TypeError if the argument is empty" + ); +} + +await new Promise((resolve, reject) => { + process.nextTick( + (first, second) => { + console.log(first, second); + if (first !== 12345 || second !== "hello") + reject(new Error("process.nextTick called with wrong arguments")); + resolve(true); + }, + 12345, + "hello" + ); +}); diff --git a/test/bun.js/process-nexttick.test.js b/test/bun.js/process-nexttick.test.js new file mode 100644 index 000000000..ac53399c0 --- /dev/null +++ b/test/bun.js/process-nexttick.test.js @@ -0,0 +1,93 @@ +import { it } from "bun:test"; + +it("process.nextTick", async () => { + // You can verify this test is correct by copy pasting this into a browser's console and checking it doesn't throw an error. + var run = 0; + var queueMicrotask = process.nextTick; + + await new Promise((resolve, reject) => { + queueMicrotask(() => { + if (run++ != 0) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 3) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + queueMicrotask(() => { + if (run++ != 1) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 4) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 6) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + }); + }); + }); + queueMicrotask(() => { + if (run++ != 2) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + queueMicrotask(() => { + if (run++ != 5) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + + queueMicrotask(() => { + if (run++ != 7) { + reject(new Error("Microtask execution order is wrong: " + run)); + } + resolve(true); + }); + }); + }); + }); + + { + var passed = false; + try { + queueMicrotask(1234); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) + throw new Error( + "queueMicrotask should throw a TypeError if the argument is not a function" + ); + } + + { + var passed = false; + try { + queueMicrotask(); + } catch (exception) { + passed = exception instanceof TypeError; + } + + if (!passed) + throw new Error( + "queueMicrotask should throw a TypeError if the argument is empty" + ); + } + + await new Promise((resolve, reject) => { + process.nextTick( + (first, second) => { + if (first !== 12345 || second !== "hello") + reject(new Error("process.nextTick called with wrong arguments")); + resolve(true); + }, + 12345, + "hello" + ); + }); +}); diff --git a/test/bun.js/process.test.js b/test/bun.js/process.test.js new file mode 100644 index 000000000..f82834a04 --- /dev/null +++ b/test/bun.js/process.test.js @@ -0,0 +1,54 @@ +import { describe, it } from "bun:test"; + +it("process", () => { + // this property isn't implemented yet but it should at least return a string + const isNode = !process.isBun; + + if (!isNode && process.title !== "bun") + throw new Error("process.title is not 'bun'"); + + if (typeof process.env.USER !== "string") + throw new Error("process.env is not an object"); + + if (process.env.USER.length === 0) + throw new Error("process.env is missing a USER property"); + + if (process.platform !== "darwin" && process.platform !== "linux") + throw new Error("process.platform is invalid"); + + if (isNode) throw new Error("process.isBun is invalid"); + + // partially to test it doesn't crash due to various strange types + process.env.BACON = "yummy"; + if (process.env.BACON !== "yummy") { + throw new Error("process.env is not writable"); + } + + delete process.env.BACON; + if (typeof process.env.BACON !== "undefined") { + throw new Error("process.env is not deletable"); + } + + process.env.BACON = "yummy"; + if (process.env.BACON !== "yummy") { + throw new Error("process.env is not re-writable"); + } + + if (JSON.parse(JSON.stringify(process.env)).BACON !== "yummy") { + throw new Error("process.env is not serializable"); + } + + if (typeof JSON.parse(JSON.stringify(process.env)).toJSON !== "undefined") { + throw new Error( + "process.env should call toJSON to hide its internal state" + ); + } + + var { env, ...proces } = process; + console.log(JSON.stringify(proces, null, 2)); + console.log(proces); + + console.log("CWD", process.cwd()); + console.log("SET CWD", process.chdir("../")); + console.log("CWD", process.cwd()); +}); diff --git a/test/bun.js/readFileSync.txt b/test/bun.js/readFileSync.txt new file mode 100644 index 000000000..ddc94b988 --- /dev/null +++ b/test/bun.js/readFileSync.txt @@ -0,0 +1 @@ +File read successfully
\ No newline at end of file diff --git a/test/bun.js/readdir.js b/test/bun.js/readdir.js new file mode 100644 index 000000000..18c111d0a --- /dev/null +++ b/test/bun.js/readdir.js @@ -0,0 +1,9 @@ +const { readdirSync } = require("fs"); + +const count = parseInt(process.env.ITERATIONS || "1", 10) || 1; + +for (let i = 0; i < count; i++) { + readdirSync("."); +} + +console.log(readdirSync(".")); diff --git a/test/bun.js/reportError.test.js b/test/bun.js/reportError.test.js new file mode 100644 index 000000000..e51f93309 --- /dev/null +++ b/test/bun.js/reportError.test.js @@ -0,0 +1,25 @@ +import { it } from "bun:test"; + +it("reportError", () => { + console.log("---BEGIN REPORT ERROR TEST--"); + // make sure we don't crash when given non-sensical types + reportError(new Error("reportError Test!")); + reportError(true); + reportError(false); + reportError(null); + reportError(123); + reportError(Infinity); + reportError(NaN); + reportError(-NaN); + reportError(""); + reportError(new Uint8Array(1)); + reportError(new Uint8Array(0)); + reportError(new ArrayBuffer(0)); + reportError(new ArrayBuffer(1)); + reportError("string"); + reportError([]); + reportError([123, null]); + reportError({}); + reportError([{}]); + console.log("---END REPORT ERROR TEST--"); +}); diff --git a/test/bun.js/require-json.json b/test/bun.js/require-json.json new file mode 100644 index 000000000..6414edc0e --- /dev/null +++ b/test/bun.js/require-json.json @@ -0,0 +1,3 @@ +{ + "hello": -123 +} diff --git a/test/bun.js/resolve-typescript-file.tsx b/test/bun.js/resolve-typescript-file.tsx new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/test/bun.js/resolve-typescript-file.tsx @@ -0,0 +1 @@ +export default {}; diff --git a/test/bun.js/resolve.test.js b/test/bun.js/resolve.test.js new file mode 100644 index 000000000..56162de4f --- /dev/null +++ b/test/bun.js/resolve.test.js @@ -0,0 +1,100 @@ +import { it, expect } from "bun:test"; +import { mkdirSync, writeFileSync } from "fs"; +import { join } from "path"; + +it("import.meta.resolve", async () => { + expect(await import.meta.resolve("./resolve.test.js")).toBe(import.meta.path); + + expect(await import.meta.resolve("./resolve.test.js", import.meta.path)).toBe( + import.meta.path + ); + + expect( + // optional second param can be any path, including a dir + await import.meta.resolve( + "./bun.js/resolve.test.js", + join(import.meta.path, "../") + ) + ).toBe(import.meta.path); + + // can be a package path + expect( + (await import.meta.resolve("react", import.meta.path)).length > 0 + ).toBe(true); + + // file extensions are optional + expect(await import.meta.resolve("./resolve.test")).toBe(import.meta.path); + + // works with tsconfig.json "paths" + expect(await import.meta.resolve("foo/bar")).toBe( + join(import.meta.path, "../baz.js") + ); + + // works with package.json "exports" + writePackageJSONExportsFixture(); + expect(await import.meta.resolve("package-json-exports/baz")).toBe( + join(import.meta.path, "../node_modules/package-json-exports/foo/bar.js") + ); + + // works with TypeScript compiler edgecases like: + // - If the file ends with .js and it doesn't exist, try again with .ts and .tsx + expect(await import.meta.resolve("./resolve-typescript-file.js")).toBe( + join(import.meta.path, "../resolve-typescript-file.tsx") + ); + expect(await import.meta.resolve("./resolve-typescript-file.tsx")).toBe( + join(import.meta.path, "../resolve-typescript-file.tsx") + ); + + // throws a ResolveError on failure + try { + await import.meta.resolve("THIS FILE DOESNT EXIST"); + throw new Error("Test failed"); + } catch (exception) { + expect(exception instanceof ResolveError).toBe(true); + expect(exception.referrer).toBe(import.meta.path); + expect(exception.name).toBe("ResolveError"); + } +}); + +// the slightly lower level API, which doesn't prefill the second param +// and expects a directory instead of a filepath +it("Bun.resolve", async () => { + expect(await Bun.resolve("./resolve.test.js", import.meta.dir)).toBe( + import.meta.path + ); +}); + +// synchronous +it("Bun.resolveSync", () => { + expect(Bun.resolveSync("./resolve.test.js", import.meta.dir)).toBe( + import.meta.path + ); +}); + +function writePackageJSONExportsFixture() { + try { + mkdirSync( + join(import.meta.dir, "./node_modules/package-json-exports/foo"), + { + recursive: true, + } + ); + } catch (exception) {} + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-exports/foo/bar.js"), + "export const bar = 1;" + ); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-exports/package.json"), + JSON.stringify( + { + name: "package-json-exports", + exports: { + "./baz": "./foo/bar.js", + }, + }, + null, + 2 + ) + ); +} diff --git a/test/bun.js/response.file.test.js b/test/bun.js/response.file.test.js new file mode 100644 index 000000000..2d0b6506e --- /dev/null +++ b/test/bun.js/response.file.test.js @@ -0,0 +1,218 @@ +import fs from "fs"; +import { it, expect } from "bun:test"; +import path from "path"; +import { gcTick } from "./gc"; + +it("Bun.file not found returns ENOENT", async () => { + try { + await gcTick(); + await Bun.file("/does/not/exist.txt").text(); + await gcTick(); + } catch (exception) { + expect(exception.code).toBe("ENOENT"); + } + await gcTick(); +}); + +it("Bun.write('out.txt', 'string')", async () => { + for (let erase of [true, false]) { + if (erase) { + try { + fs.unlinkSync(path.join("/tmp", "out.txt")); + } catch (e) {} + } + await gcTick(); + expect(await Bun.write("/tmp/out.txt", "string")).toBe("string".length); + await gcTick(); + const out = Bun.file("/tmp/out.txt"); + await gcTick(); + expect(await out.text()).toBe("string"); + await gcTick(); + expect(await out.text()).toBe(fs.readFileSync("/tmp/out.txt", "utf8")); + await gcTick(); + } +}); + +it("Bun.write blob", async () => { + await Bun.write( + Bun.file("/tmp/response-file.test.txt"), + Bun.file(path.join(import.meta.dir, "fetch.js.txt")) + ); + await gcTick(); + await Bun.write(Bun.file("/tmp/response-file.test.txt"), "blah blah blha"); + await gcTick(); + await Bun.write( + Bun.file("/tmp/response-file.test.txt"), + new Uint32Array(1024) + ); + await gcTick(); + await Bun.write("/tmp/response-file.test.txt", new Uint32Array(1024)); + await gcTick(); + expect( + await Bun.write( + new TextEncoder().encode("/tmp/response-file.test.txt"), + new Uint32Array(1024) + ) + ).toBe(new Uint32Array(1024).byteLength); + await gcTick(); +}); + +it("Bun.file -> Bun.file", async () => { + try { + fs.unlinkSync(path.join("/tmp", "fetch.js.in")); + } catch (e) {} + await gcTick(); + try { + fs.unlinkSync(path.join("/tmp", "fetch.js.out")); + } catch (e) {} + await gcTick(); + const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); + const text = fs.readFileSync(file, "utf8"); + fs.writeFileSync("/tmp/fetch.js.in", text); + await gcTick(); + { + const result = await Bun.write( + Bun.file("/tmp/fetch.js.out"), + Bun.file("/tmp/fetch.js.in") + ); + await gcTick(); + expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text); + await gcTick(); + } + + { + await Bun.write( + Bun.file("/tmp/fetch.js.in").slice(0, (text.length / 2) | 0), + Bun.file("/tmp/fetch.js.out") + ); + expect(await Bun.file("/tmp/fetch.js.in").text()).toBe( + text.substring(0, (text.length / 2) | 0) + ); + } + + { + await gcTick(); + await Bun.write("/tmp/fetch.js.in", Bun.file("/tmp/fetch.js.out")); + await gcTick(); + expect(await Bun.file("/tmp/fetch.js.in").text()).toBe(text); + } +}); + +it("Bun.file", async () => { + const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); + expect(await Bun.file(file).text()).toBe(fs.readFileSync(file, "utf8")); + await gcTick(); +}); + +it("Bun.file as a Blob", async () => { + const filePath = path.join(import.meta.path, "../fetch.js.txt"); + const fixture = fs.readFileSync(filePath, "utf8"); + // this is a Blob object with the same interface as the one returned by fetch + // internally, instead of a byte array, it stores the file path! + // this enables several performance optimizations + var blob = Bun.file(filePath); + await gcTick(); + + // no size because we haven't read it from disk yet + expect(blob.size).toBe(0); + await gcTick(); + // now it reads "./fetch.js.txt" from the filesystem + // it's lazy, only loads once we ask for it + // if it fails, the promise will reject at this point + expect(await blob.text()).toBe(fixture); + await gcTick(); + // now that it's loaded, the size updates + expect(blob.size).toBe(fixture.length); + await gcTick(); + // and it only loads once for _all_ blobs pointing to that file path + // until all references are released + expect((await blob.arrayBuffer()).byteLength).toBe(fixture.length); + await gcTick(); + + const array = new Uint8Array(await blob.arrayBuffer()); + await gcTick(); + const text = fixture; + for (let i = 0; i < text.length; i++) { + expect(array[i]).toBe(text.charCodeAt(i)); + } + await gcTick(); + expect(blob.size).toBe(fixture.length); + blob = null; + await gcTick(); + await new Promise((resolve) => setTimeout(resolve, 1)); + // now we're back + var blob = Bun.file(filePath); + expect(blob.size).toBe(0); +}); + +it("Response -> Bun.file", async () => { + const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); + const text = fs.readFileSync(file, "utf8"); + await gcTick(); + const response = new Response(Bun.file(file)); + await gcTick(); + expect(await response.text()).toBe(text); + await gcTick(); +}); + +it("Bun.file -> Response", async () => { + // ensure the file doesn't already exist + try { + fs.unlinkSync("/tmp/fetch.js.out"); + } catch {} + await gcTick(); + const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); + const text = fs.readFileSync(file, "utf8"); + await gcTick(); + const resp = await fetch("https://example.com"); + await gcTick(); + + expect(await Bun.write("/tmp/fetch.js.out", resp)).toBe(text.length); + await gcTick(); + expect(await Bun.file("/tmp/fetch.js.out").text()).toBe(text); + await gcTick(); +}); + +it("Response -> Bun.file -> Response -> text", async () => { + await gcTick(); + const file = path.join(import.meta.dir, "fetch.js.txt"); + await gcTick(); + const text = fs.readFileSync(file, "utf8"); + await gcTick(); + const response = new Response(Bun.file(file)); + await gcTick(); + const response2 = response.clone(); + await gcTick(); + expect(await response2.text()).toBe(text); + await gcTick(); +}); + +// If you write nothing to a file, it shouldn't modify it +// If you want to truncate a file, it should be more explicit +it("Bun.write('output.html', '')", async () => { + await Bun.write("/tmp/output.html", "lalalala"); + expect(await Bun.write("/tmp/output.html", "")).toBe(0); + expect(await Bun.file("/tmp/output.html").text()).toBe("lalalala"); +}); + +// Since Bun.file is resolved lazily, this needs to specifically be checked +it("Bun.write('output.html', HTMLRewriter.transform(Bun.file)))", async () => { + var rewriter = new HTMLRewriter(); + rewriter.on("div", { + element(element) { + element.setInnerContent("<blink>it worked!</blink>", { html: true }); + }, + }); + await Bun.write("/tmp/html-rewriter.txt.js", "<div>hello</div>"); + var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); + var output = rewriter.transform(input); + const outpath = `/tmp/html-rewriter.${Date.now()}.html`; + await Bun.write(outpath, output); + expect(await Bun.file(outpath).text()).toBe( + "<div><blink>it worked!</blink></div>" + ); +}); diff --git a/test/bun.js/serve.test.ts b/test/bun.js/serve.test.ts new file mode 100644 index 000000000..8b785dd25 --- /dev/null +++ b/test/bun.js/serve.test.ts @@ -0,0 +1,82 @@ +import { file, serve } from "bun"; +import { expect, it } from "bun:test"; +import { readFileSync } from "fs"; +import { resolve } from "path"; + +var port = 40000; + +it("should work for a hello world", async () => { + const server = serve({ + port: port++, + fetch(req) { + return new Response(`Hello, world!`); + }, + }); + const response = await fetch(`http://localhost:${server.port}`); + expect(await response.text()).toBe("Hello, world!"); + server.stop(); +}); + +it("should work for a file", async () => { + const fixture = resolve(import.meta.dir, "./fetch.js.txt"); + const textToExpect = readFileSync(fixture, "utf-8"); + + const server = serve({ + port: port++, + fetch(req) { + return new Response(file(fixture)); + }, + }); + const response = await fetch(`http://localhost:${server.port}`); + expect(await response.text()).toBe(textToExpect); + server.stop(); +}); + +it("fetch should work with headers", async () => { + const fixture = resolve(import.meta.dir, "./fetch.js.txt"); + + const server = serve({ + port: port++, + fetch(req) { + if (req.headers.get("X-Foo") !== "bar") { + return new Response("X-Foo header not set", { status: 500 }); + } + return new Response(file(fixture), { + headers: { "X-Both-Ways": "1" }, + }); + }, + }); + const response = await fetch(`http://localhost:${server.port}`, { + headers: { + "X-Foo": "bar", + }, + }); + + expect(response.status).toBe(200); + expect(response.headers.get("X-Both-Ways")).toBe("1"); + server.stop(); +}); + +var count = 200; +it(`should work for a file ${count} times`, async () => { + const fixture = resolve(import.meta.dir, "./fetch.js.txt"); + const textToExpect = readFileSync(fixture, "utf-8"); + var ran = 0; + const server = serve({ + port: port++, + async fetch(req) { + return new Response(file(fixture)); + }, + }); + + // this gets stuck if run about 200 times awaiting all the promises + // when the promises are run altogether, instead of one at a time + // it's hard to say if this only happens here due to some weird stuff with the test runner + // or if it's "real" issue + for (let i = 0; i < count; i++) { + const response = await fetch(`http://localhost:${server.port}`); + expect(await response.text()).toBe(textToExpect); + } + + server.stop(); +}); diff --git a/test/bun.js/setInterval.test.js b/test/bun.js/setInterval.test.js new file mode 100644 index 000000000..f633998cd --- /dev/null +++ b/test/bun.js/setInterval.test.js @@ -0,0 +1,35 @@ +import { it, expect } from "bun:test"; + +it("setInterval", async () => { + var counter = 0; + var start; + const result = await new Promise((resolve, reject) => { + start = performance.now(); + + var id = setInterval(() => { + counter++; + if (counter === 10) { + resolve(counter); + clearInterval(id); + } + }, 1); + }); + + expect(result).toBe(10); + expect(performance.now() - start >= 10).toBe(true); +}); + +it("clearInterval", async () => { + var called = false; + const id = setInterval(() => { + called = true; + expect(false).toBe(true); + }, 1); + clearInterval(id); + await new Promise((resolve, reject) => { + setInterval(() => { + resolve(); + }, 10); + }); + expect(called).toBe(false); +}); diff --git a/test/bun.js/setTimeout.test.js b/test/bun.js/setTimeout.test.js new file mode 100644 index 000000000..55f71712c --- /dev/null +++ b/test/bun.js/setTimeout.test.js @@ -0,0 +1,39 @@ +import { it, expect } from "bun:test"; + +it("setTimeout", async () => { + var lastID = -1; + const result = await new Promise((resolve, reject) => { + var numbers = []; + + for (let i = 1; i < 100; i++) { + const id = setTimeout(() => { + numbers.push(i); + if (i === 99) { + resolve(numbers); + } + }, i); + expect(id > lastID).toBe(true); + lastID = id; + } + }); + + for (let j = 0; j < result.length; j++) { + expect(result[j]).toBe(j + 1); + } + expect(result.length).toBe(99); +}); + +it("clearTimeout", async () => { + var called = false; + const id = setTimeout(() => { + called = true; + expect(false).toBe(true); + }, 1); + clearTimeout(id); + await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, 10); + }); + expect(called).toBe(false); +}); diff --git a/test/bun.js/shadow.test.js b/test/bun.js/shadow.test.js new file mode 100644 index 000000000..3fffcac90 --- /dev/null +++ b/test/bun.js/shadow.test.js @@ -0,0 +1,10 @@ +import { describe, it, expect } from "bun:test"; + +it("shadow realm works", () => { + const red = new ShadowRealm(); + globalThis.someValue = 1; + // Affects only the ShadowRealm's global + const result = red.evaluate("globalThis.someValue = 2;"); + expect(globalThis.someValue).toBe(1); + expect(result).toBe(2); +}); diff --git a/test/bun.js/sleep.js b/test/bun.js/sleep.js new file mode 100644 index 000000000..080597424 --- /dev/null +++ b/test/bun.js/sleep.js @@ -0,0 +1,10 @@ +const interval = 0.01; +const now = performance.now(); +console.time("Slept"); +Bun.sleepSync(interval); +const elapsed = performance.now() - now; +if (elapsed < interval) { + throw new Error("Didn't sleep"); +} + +console.timeEnd("Slept"); diff --git a/test/bun.js/solid-dom-fixtures/SVG/code.js b/test/bun.js/solid-dom-fixtures/SVG/code.js new file mode 100644 index 000000000..0ffded054 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/SVG/code.js @@ -0,0 +1,74 @@ +const template = ( + <svg width="400" height="180"> + <rect + stroke-width="2" + x="50" + y="20" + rx="20" + ry="20" + width="150" + height="150" + style="fill:red;stroke:black;stroke-width:5;opacity:0.5" + /> + <linearGradient gradientTransform="rotate(25)"> + <stop offset="0%"></stop> + </linearGradient> + </svg> +); + +const template2 = ( + <svg width="400" height="180"> + <rect + className={state.name} + stroke-width={state.width} + x={state.x} + y={state.y} + rx="20" + ry="20" + width="150" + height="150" + style={{ + fill: "red", + stroke: "black", + "stroke-width": props.stroke, + opacity: 0.5, + }} + /> + </svg> +); + +const template3 = ( + <svg width="400" height="180"> + <rect {...props} /> + </svg> +); + +const template4 = <rect x="50" y="20" width="150" height="150" />; + +const template5 = ( + <> + <rect x="50" y="20" width="150" height="150" /> + </> +); + +const template6 = ( + <Component> + <rect x="50" y="20" width="150" height="150" /> + </Component> +); + +const template7 = ( + <svg viewBox={"0 0 160 40"} xmlns="http://www.w3.org/2000/svg"> + <a xlink:href={url}> + <text x="10" y="25"> + MDN Web Docs + </text> + </a> + </svg> +); + +const template8 = ( + <svg viewBox={"0 0 160 40"} xmlns="http://www.w3.org/2000/svg"> + <text x="10" y="25" textContent={text} /> + </svg> +); diff --git a/test/bun.js/solid-dom-fixtures/SVG/output.bun.js b/test/bun.js/solid-dom-fixtures/SVG/output.bun.js new file mode 100644 index 000000000..44d092f15 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/SVG/output.bun.js @@ -0,0 +1,33 @@ +var _tmpl$1 = _template$('<svg width="400" height="180"><rect stroke-width="2" x="50" y="20" rx="20" ry="20" width="150" height="150"/><linearGradient gradientTransform="rotate(25)"><stop offset="0%"/></linearGradient></svg>', 4), _tmpl$2 = _template$('<svg width="400" height="180"><rect rx="20" ry="20" width="150" height="150"/></svg>', 2), _tmpl$3 = _template$('<svg width="400" height="180"><rect/></svg>', 2), _tmpl$4 = _template$('<rect x="50" y="20" width="150" height="150"/>', 0), _tmpl$5 = _template$('<svg viewBox="0 0 160 40" xmlns="http://www.w3.org/2000/svg"><a><text x="10" y="25">MDN Web Docs</text></a></svg>', 6), _tmpl$6 = _template$('<svg viewBox="0 0 160 40" xmlns="http://www.w3.org/2000/svg"><text x="10" y="25"/></svg>', 2); +const template = _tmpl$1.cloneNode(true); +const template2 = () => { + var _el = _tmpl$1.cloneNode(true); + effect(() => { + return setAttribute(_el, "className", state.name); + }); + effect(() => { + return setAttribute(_el, "stroke-width", state.width); + }); + effect(() => { + return setAttribute(_el, "x", state.x); + }); + effect(() => { + return setAttribute(_el, "y", state.y); + }); + ; + return _el; +}; +const template3 = _tmpl$3.cloneNode(true); +const template4 = _tmpl$4.cloneNode(true); +const template5 = ; +const template6 = createComponent(Component, {}); +const template7 = () => { + var _el = _tmpl$4.cloneNode(true); + setAttribute(_el, "xlink:href", url); + return _el; +}; +const template8 = () => { + var _el = _tmpl$5.cloneNode(true); + setAttribute(_el, "textContent", text); + return _el; +}; diff --git a/test/bun.js/solid-dom-fixtures/SVG/output.js b/test/bun.js/solid-dom-fixtures/SVG/output.js new file mode 100644 index 000000000..edac460af --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/SVG/output.js @@ -0,0 +1,108 @@ +import { template as _$template } from "r-dom"; +import { setAttributeNS as _$setAttributeNS } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { spread as _$spread } from "r-dom"; +import { setAttribute as _$setAttribute } from "r-dom"; +import { effect as _$effect } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template( + `<svg width="400" height="180"><rect stroke-width="2" x="50" y="20" rx="20" ry="20" width="150" height="150" style="fill:red;stroke:black;stroke-width:5;opacity:0.5"></rect><linearGradient gradientTransform="rotate(25)"><stop offset="0%"></stop></linearGradient></svg>`, + 8 + ), + _tmpl$2 = /*#__PURE__*/ _$template( + `<svg width="400" height="180"><rect rx="20" ry="20" width="150" height="150"></rect></svg>`, + 4 + ), + _tmpl$3 = /*#__PURE__*/ _$template( + `<svg width="400" height="180"><rect></rect></svg>`, + 4 + ), + _tmpl$4 = /*#__PURE__*/ _$template( + `<svg><rect x="50" y="20" width="150" height="150"></rect></svg>`, + 4, + true + ), + _tmpl$5 = /*#__PURE__*/ _$template( + `<svg viewBox="0 0 160 40" xmlns="http://www.w3.org/2000/svg"><a><text x="10" y="25">MDN Web Docs</text></a></svg>`, + 6 + ), + _tmpl$6 = /*#__PURE__*/ _$template( + `<svg viewBox="0 0 160 40" xmlns="http://www.w3.org/2000/svg"><text x="10" y="25"></text></svg>`, + 4 + ); + +const template = _tmpl$.cloneNode(true); + +const template2 = (() => { + const _el$2 = _tmpl$2.cloneNode(true), + _el$3 = _el$2.firstChild; + + _el$3.style.setProperty("fill", "red"); + + _el$3.style.setProperty("stroke", "black"); + + _el$3.style.setProperty("opacity", "0.5"); + + _$effect( + (_p$) => { + const _v$ = state.name, + _v$2 = state.width, + _v$3 = state.x, + _v$4 = state.y, + _v$5 = props.stroke; + _v$ !== _p$._v$ && _$setAttribute(_el$3, "class", (_p$._v$ = _v$)); + _v$2 !== _p$._v$2 && + _$setAttribute(_el$3, "stroke-width", (_p$._v$2 = _v$2)); + _v$3 !== _p$._v$3 && _$setAttribute(_el$3, "x", (_p$._v$3 = _v$3)); + _v$4 !== _p$._v$4 && _$setAttribute(_el$3, "y", (_p$._v$4 = _v$4)); + _v$5 !== _p$._v$5 && + _el$3.style.setProperty("stroke-width", (_p$._v$5 = _v$5)); + return _p$; + }, + { + _v$: undefined, + _v$2: undefined, + _v$3: undefined, + _v$4: undefined, + _v$5: undefined, + } + ); + + return _el$2; +})(); + +const template3 = (() => { + const _el$4 = _tmpl$3.cloneNode(true), + _el$5 = _el$4.firstChild; + + _$spread(_el$5, props, true, false); + + return _el$4; +})(); + +const template4 = _tmpl$4.cloneNode(true); + +const template5 = _tmpl$4.cloneNode(true); + +const template6 = _$createComponent(Component, { + get children() { + return _tmpl$4.cloneNode(true); + }, +}); + +const template7 = (() => { + const _el$9 = _tmpl$5.cloneNode(true), + _el$10 = _el$9.firstChild; + + _$setAttributeNS(_el$10, "http://www.w3.org/1999/xlink", "xlink:href", url); + + return _el$9; +})(); + +const template8 = (() => { + const _el$11 = _tmpl$6.cloneNode(true), + _el$12 = _el$11.firstChild; + + _el$12.textContent = text; + return _el$11; +})(); diff --git a/test/bun.js/solid-dom-fixtures/attributeExpressions/code.js b/test/bun.js/solid-dom-fixtures/attributeExpressions/code.js new file mode 100644 index 000000000..b64949434 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/attributeExpressions/code.js @@ -0,0 +1,115 @@ +const selected = true; +let id = "my-h1"; +let link; +const template = ( + <div + id="main" + {...results} + classList={{ selected: unknown }} + style={{ color }} + > + <h1 + class="base" + id={id} + {...results()} + disabled + readonly="" + title={welcoming()} + style={{ "background-color": color(), "margin-right": "40px" }} + classList={{ dynamic: dynamic(), selected }} + > + <a href={"/"} ref={link} classList={{ "ccc ddd": true }} readonly={value}> + Welcome + </a> + </h1> + </div> +); + +const template2 = ( + <div {...getProps("test")}> + <div textContent={rowId} /> + <div textContent={row.label} /> + <div innerHTML={"<div/>"} /> + </div> +); + +const template3 = ( + <div + id={/*@once*/ state.id} + style={/*@once*/ { "background-color": state.color }} + name={state.name} + textContent={/*@once*/ state.content} + /> +); + +const template4 = ( + <div class="hi" className={state.class} classList={{ "ccc:ddd": true }} /> +); + +const template5 = <div class="a" className="b"></div>; + +const template6 = <div style={someStyle()} textContent="Hi" />; + +const template7 = ( + <div + style={{ + "background-color": color(), + "margin-right": "40px", + ...props.style, + }} + style:padding-top={props.top} + class:my-class={props.active} + /> +); + +let refTarget; +const template8 = <div ref={refTarget} />; + +const template9 = <div ref={(e) => console.log(e)} />; + +const template10 = <div ref={refFactory()} />; + +const template11 = <div use:something use:another={thing} use:zero={0} />; + +const template12 = <div prop:htmlFor={thing} />; + +const template13 = <input type="checkbox" checked={true} />; + +const template14 = <input type="checkbox" checked={state.visible} />; + +const template15 = <div class="`a">`$`</div>; + +const template16 = ( + <button + class="static" + classList={{ + hi: "k", + }} + type="button" + > + Write + </button> +); + +const template17 = ( + <button + classList={{ + a: true, + b: true, + c: true, + }} + onClick={increment} + > + Hi + </button> +); + +const template18 = ( + <div + {...{ + get [key()]() { + return props.value; + }, + }} + /> +); diff --git a/test/bun.js/solid-dom-fixtures/attributeExpressions/output.bun.js b/test/bun.js/solid-dom-fixtures/attributeExpressions/output.bun.js new file mode 100644 index 000000000..4bb3e1b39 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/attributeExpressions/output.bun.js @@ -0,0 +1,155 @@ +var _tmpl = _template$( + '<div id="main"><h1 class="base" disabled readonly><a href="/">Welcome</a></h1></div>', + 6 + ), + _tmpl$2 = _template$( + '<div><div/><div/><div innerHTML="<div/>"/></div>', + 2 + ), + _tmpl$2 = _template$("<div/>", 0), + _tmpl$3 = _template$('<div class="hi"/>', 0), + _tmpl$5 = _template$('<div class="a" class="b"/>', 0), + _tmpl$5 = _template$('<div textContent="Hi"/>', 0), + _tmpl$6 = _template$("<div use:something use:zero=0/>", 0), + _tmpl$8 = _template$('<input type="checkbox" checked/>', 0), + _tmpl$8 = _template$('<input type="checkbox"/>', 0), + _tmpl$10 = _template$('<div class="`a">`$`</div>', 2), + _tmpl$10 = _template$( + '<button class="static" type="button">Write</button>', + 2 + ), + _tmpl$11 = _template$("<button>Hi</button>", 2); +const selected = true; +let id = "my-h1"; +let link; +const template = () => { + var _el = _tmpl.cloneNode(true), + _el$1 = _el.firstChild, + _el$2 = _el$1.nextSibling; + effect(() => { + return setAttribute(_el, "classList", { selected: unknown }); + }); + setAttribute(_el$1, "id", id); + effect(() => { + return setAttribute(_el$1, "title", welcoming()); + }); + effect(() => { + return setAttribute(_el$1, "classList", { dynamic: dynamic(), selected }); + }); + setAttribute(_el$2, "ref", link); + effect(() => { + return setAttribute(_el$2, "classList", { "ccc ddd": true }); + }); + setAttribute(_el$2, "readonly", value); + return _el; +}; +const template2 = () => { + var _el = _tmpl$1.cloneNode(true), + _el$1 = _el.firstChild; + setAttribute(_el, "textContent", rowId); + effect(() => { + return setAttribute(_el$1, "textContent", row.label); + }); + return _el; +}; +const template3 = () => { + var _el = _tmpl$2.cloneNode(true); + effect(() => { + return setAttribute(_el, "id", state.id); + }); + effect(() => { + return setAttribute(_el, "name", state.name); + }); + effect(() => { + return setAttribute(_el, "textContent", state.content); + }); + return _el; +}; +const template4 = () => { + var _el = _tmpl$3.cloneNode(true); + effect(() => { + return setAttribute(_el, "className", state.class); + }); + effect(() => { + return setAttribute(_el, "classList", { "ccc:ddd": true }); + }); + return _el; +}; +const template5 = _tmpl$5.cloneNode(true); +const template6 = () => { + var _el = _tmpl$5.cloneNode(true); + return _el; +}; +const template7 = () => { + var _el = _tmpl$2.cloneNode(true); + effect(() => { + return setAttribute(_el, "style:padding-top", props.top); + }); + effect(() => { + return setAttribute(_el, "class:my-class", props.active); + }); + return _el; +}; +let refTarget; +const template8 = () => { + var _el = _tmpl$2.cloneNode(true); + setAttribute(_el, "ref", refTarget); + return _el; +}; +const template9 = () => { + var _el = _tmpl$2.cloneNode(true); + effect(() => { + return setAttribute(_el, "ref", (e) => console.log(e)); + }); + return _el; +}; +const template10 = () => { + var _el = _tmpl$2.cloneNode(true); + effect(() => { + return setAttribute(_el, "ref", refFactory()); + }); + return _el; +}; +const template11 = () => { + var _el = _tmpl$6.cloneNode(true); + setAttribute(_el, "use:another", thing); + return _el; +}; +const template12 = () => { + var _el = _tmpl$2.cloneNode(true); + setAttribute(_el, "prop:htmlFor", thing); + return _el; +}; +const template13 = _tmpl$8.cloneNode(true); +const template14 = () => { + var _el = _tmpl$8.cloneNode(true); + effect(() => { + return setAttribute(_el, "checked", state.visible); + }); + return _el; +}; +const template15 = _tmpl$10.cloneNode(true); +const template16 = () => { + var _el = _tmpl$10.cloneNode(true); + effect(() => { + return setAttribute(_el, "classList", { + hi: "k", + }); + }); + return _el; +}; +const template17 = () => { + var _el = _tmpl$11.cloneNode(true); + effect(() => { + return setAttribute(_el, "classList", { + a: true, + b: true, + c: true, + }); + }); + effect(() => { + return (_el.$$click = increment); + }); + return _el; +}; +const template18 = _tmpl$2.cloneNode(true); diff --git a/test/bun.js/solid-dom-fixtures/attributeExpressions/output.js b/test/bun.js/solid-dom-fixtures/attributeExpressions/output.js new file mode 100644 index 000000000..14f700218 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/attributeExpressions/output.js @@ -0,0 +1,241 @@ +const _tmpl$ = /*#__PURE__*/ _$template( + `<div id="main"><h1 class="base selected" id="my-h1" disabled readonly=""><a href="/">Welcome</a></h1></div>`, + 6 + ), + _tmpl$2 = /*#__PURE__*/ _$template( + `<div><div></div><div> </div><div></div></div>`, + 8 + ), + _tmpl$3 = /*#__PURE__*/ _$template(`<div></div>`, 2), + _tmpl$4 = /*#__PURE__*/ _$template(`<div class="a b"></div>`, 2), + _tmpl$5 = /*#__PURE__*/ _$template(`<input type="checkbox">`, 1), + _tmpl$6 = /*#__PURE__*/ _$template(`<div class="\`a">\`$\`</div>`, 2), + _tmpl$7 = /*#__PURE__*/ _$template( + `<button class="static hi" type="button">Write</button>`, + 2 + ), + _tmpl$8 = /*#__PURE__*/ _$template(`<button class="a b c">Hi</button>`, 2); + +const selected = true; +let id = "my-h1"; +let link; + +const template = (() => { + const _el$ = _tmpl$.cloneNode(true), + _el$2 = _el$.firstChild, + _el$3 = _el$2.firstChild; + + _$spread(_el$, results, false, true); + + _el$.classList.toggle("selected", unknown); + + _el$.style.setProperty("color", color); + + _$spread(_el$2, results, false, true); + + _el$2.style.setProperty("margin-right", "40px"); + + const _ref$ = link; + typeof _ref$ === "function" ? _ref$(_el$3) : (link = _el$3); + + _$classList(_el$3, { + "ccc ddd": true, + }); + + _el$3.readOnly = value; + + _$effect( + (_p$) => { + const _v$ = welcoming(), + _v$2 = color(), + _v$3 = !!dynamic(); + + _v$ !== _p$._v$ && _$setAttribute(_el$2, "title", (_p$._v$ = _v$)); + _v$2 !== _p$._v$2 && + _el$2.style.setProperty("background-color", (_p$._v$2 = _v$2)); + _v$3 !== _p$._v$3 && _el$2.classList.toggle("dynamic", (_p$._v$3 = _v$3)); + return _p$; + }, + { + _v$: undefined, + _v$2: undefined, + _v$3: undefined, + } + ); + + return _el$; +})(); + +const template2 = (() => { + const _el$4 = _tmpl$2.cloneNode(true), + _el$5 = _el$4.firstChild, + _el$6 = _el$5.nextSibling, + _el$7 = _el$6.firstChild, + _el$8 = _el$6.nextSibling; + + _$spread(_el$4, () => getProps("test"), false, true); + + _el$5.textContent = rowId; + _el$8.innerHTML = "<div/>"; + + _$effect(() => (_el$7.data = row.label)); + + return _el$4; +})(); + +const template3 = (() => { + const _el$9 = _tmpl$3.cloneNode(true); + + _$setAttribute(_el$9, "id", state.id); + + _el$9.style.setProperty("background-color", state.color); + + _el$9.textContent = state.content; + + _$effect(() => _$setAttribute(_el$9, "name", state.name)); + + return _el$9; +})(); + +const template4 = (() => { + const _el$10 = _tmpl$3.cloneNode(true); + + _$classList(_el$10, { + "ccc:ddd": true, + }); + + _$effect(() => _$className(_el$10, `hi ${state.class || ""}`)); + + return _el$10; +})(); + +const template5 = _tmpl$4.cloneNode(true); + +const template6 = (() => { + const _el$12 = _tmpl$3.cloneNode(true); + + _el$12.textContent = "Hi"; + + _$effect((_$p) => _$style(_el$12, someStyle(), _$p)); + + return _el$12; +})(); + +const template7 = (() => { + const _el$13 = _tmpl$3.cloneNode(true); + + _$effect( + (_p$) => { + const _v$4 = { + "background-color": color(), + "margin-right": "40px", + ...props.style, + }, + _v$5 = props.top, + _v$6 = !!props.active; + + _p$._v$4 = _$style(_el$13, _v$4, _p$._v$4); + _v$5 !== _p$._v$5 && + _el$13.style.setProperty("padding-top", (_p$._v$5 = _v$5)); + _v$6 !== _p$._v$6 && + _el$13.classList.toggle("my-class", (_p$._v$6 = _v$6)); + return _p$; + }, + { + _v$4: undefined, + _v$5: undefined, + _v$6: undefined, + } + ); + + return _el$13; +})(); + +let refTarget; + +const template8 = (() => { + const _el$14 = _tmpl$3.cloneNode(true); + + const _ref$2 = refTarget; + typeof _ref$2 === "function" ? _ref$2(_el$14) : (refTarget = _el$14); + return _el$14; +})(); + +const template9 = (() => { + const _el$15 = _tmpl$3.cloneNode(true); + + ((e) => console.log(e))(_el$15); + + return _el$15; +})(); + +const template10 = (() => { + const _el$16 = _tmpl$3.cloneNode(true); + + const _ref$3 = refFactory(); + + typeof _ref$3 === "function" && _ref$3(_el$16); + return _el$16; +})(); + +const template11 = (() => { + const _el$17 = _tmpl$3.cloneNode(true); + + zero(_el$17, () => 0); + another(_el$17, () => thing); + something(_el$17, () => true); + return _el$17; +})(); + +const template12 = (() => { + const _el$18 = _tmpl$3.cloneNode(true); + + _el$18.htmlFor = thing; + return _el$18; +})(); + +const template13 = (() => { + const _el$19 = _tmpl$5.cloneNode(true); + + _el$19.checked = true; + return _el$19; +})(); + +const template14 = (() => { + const _el$20 = _tmpl$5.cloneNode(true); + + _$effect(() => (_el$20.checked = state.visible)); + + return _el$20; +})(); + +const template15 = _tmpl$6.cloneNode(true); + +const template16 = _tmpl$7.cloneNode(true); + +const template17 = (() => { + const _el$23 = _tmpl$8.cloneNode(true); + + _$addEventListener(_el$23, "click", increment, true); + + return _el$23; +})(); + +const template18 = (() => { + const _el$24 = _tmpl$3.cloneNode(true); + + _$spread( + _el$24, + () => ({ + get [key()]() { + return props.value; + }, + }), + false, + false + ); + + return _el$24; +})(); + +_$delegateEvents(["click"]); diff --git a/test/bun.js/solid-dom-fixtures/components/code.js b/test/bun.js/solid-dom-fixtures/components/code.js new file mode 100644 index 000000000..f3bd159d6 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/components/code.js @@ -0,0 +1,161 @@ +import { Show } from "somewhere"; + +const Child = (props) => { + const [s, set] = createSignal(); + return ( + <> + <div ref={props.ref}>Hello {props.name}</div> + <div ref={set}>{props.children}</div> + </> + ); +}; + +const template = (props) => { + let childRef; + const { content } = props; + return ( + <div> + <Child name="John" {...props} ref={childRef} booleanProperty> + <div>From Parent</div> + </Child> + <Child name="Jason" {...dynamicSpread()} ref={props.ref}> + {/* Comment Node */} + <div>{content}</div> + </Child> + <Context.Consumer ref={props.consumerRef()}> + {(context) => context} + </Context.Consumer> + </div> + ); +}; + +const template2 = ( + <Child + name="Jake" + dynamic={state.data} + stale={/*@once*/ state.data} + handleClick={clickHandler} + hyphen-ated={state.data} + ref={(el) => (e = el)} + /> +); + +const template3 = ( + <Child> + <div /> + <div /> + <div /> + After + </Child> +); + +const [s, set] = createSignal(); +const template4 = <Child ref={set}>{<div />}</Child>; + +const template5 = <Child dynamic={state.dynamic}>{state.dynamic}</Child>; + +// builtIns +const template6 = ( + <For each={state.list} fallback={<Loading />}> + {(item) => <Show when={state.condition}>{item}</Show>} + </For> +); + +const template7 = ( + <Child> + <div /> + {state.dynamic} + </Child> +); + +const template8 = ( + <Child> + {(item) => item} + {(item) => item} + </Child> +); + +const template9 = <_garbage>Hi</_garbage>; + +const template10 = ( + <div> + <Link>new</Link> + {" | "} + <Link>comments</Link> + {" | "} + <Link>show</Link> + {" | "} + <Link>ask</Link> + {" | "} + <Link>jobs</Link> + {" | "} + <Link>submit</Link> + </div> +); + +const template11 = ( + <div> + <Link>new</Link> + {" | "} + <Link>comments</Link> + <Link>show</Link> + {" | "} + <Link>ask</Link> + <Link>jobs</Link> + {" | "} + <Link>submit</Link> + </div> +); + +const template12 = ( + <div> + {" | "} + <Link>comments</Link> + {" | "} + {" | "} + {" | "} + <Link>show</Link> + {" | "} + </div> +); + +class Template13 { + render() { + <Component prop={this.something} onClick={() => this.shouldStay}> + <Nested prop={this.data}>{this.content}</Nested> + </Component>; + } +} + +const Template14 = <Component>{data()}</Component>; + +const Template15 = <Component {...props} />; + +const Template16 = <Component something={something} {...props} />; + +const Template17 = ( + <Pre> + <span>1</span> <span>2</span> <span>3</span> + </Pre> +); +const Template18 = ( + <Pre> + <span>1</span> + <span>2</span> + <span>3</span> + </Pre> +); + +const Template19 = <Component {...s.dynamic()} />; + +const Template20 = <Component class={prop.red ? "red" : "green"} />; + +const template21 = ( + <Component + {...{ + get [key()]() { + return props.value; + }, + }} + /> +); diff --git a/test/bun.js/solid-dom-fixtures/components/output.bun.js b/test/bun.js/solid-dom-fixtures/components/output.bun.js new file mode 100644 index 000000000..5ab4d5614 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/components/output.bun.js @@ -0,0 +1,205 @@ +var _tmpl = _template$("<div><div>From Parent</div><div</div></div>", 9), _tmpl$1 = _template$("<div> | | | | | </div>", 8), _tmpl$2 = _template$("<div> | | | </div>", 8); +import {Show} from "somewhere"; +const Child = (props) => { + const [s, set] = createSignal(); + return ; +}; +const template = (props) => { + let childRef; + const { content } = props; + return () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, createComponent(Child, { + name: "John", + ref: childRef, + booleanProperty: true + }), null); + insert(_tmpl, content, null); + insert(_tmpl, createComponent(Child, { + name: "Jason", + ref: props.ref + }), null); + insert(_tmpl, createComponent(Context.Consumer, { + ref: props.consumerRef(), + get children: [ + (context) => context + ] + }), null); + return _tmpl; + }; +}; +const template2 = createComponent(Child, { + name: "Jake", + dynamic: state.data, + stale: state.data, + handleClick: clickHandler, + "hyphen-ated": state.data, + get ref: () => { + return (el) => e = el; + } +}); +const template3 = createComponent(Child, { + get children: [ + "After" + ] +}); +const [s, set] = createSignal(); +const template4 = createComponent(Child, { + ref: set +}); +const template5 = createComponent(Child, { + dynamic: state.dynamic, + get children: [ + state.dynamic + ] +}); +const template6 = createComponent(For, { + each: state.list, + fallback: , + get children: [ + (item) => createComponent(Show, { + when: state.condition, + get children: [ + item + ] + }) + ] +}); +const template7 = createComponent(Child, { + get children: [ + state.dynamic + ] +}); +const template8 = createComponent(Child, { + get children: [ + (item) => item, + (item) => item + ] +}); +const template9 = createComponent(_garbage, { + get children: [ + "Hi" + ] +}); +const template10 = () => { + var _tmpl$1 = _tmpl$1.cloneNode(true); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "new" + ] + }), null); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "comments" + ] + }), null); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "show" + ] + }), null); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "ask" + ] + }), null); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "jobs" + ] + }), null); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "submit" + ] + }), null); + return _tmpl$1; +}; +const template11 = () => { + var _tmpl$2 = _tmpl$2.cloneNode(true); + insert(_tmpl$2, createComponent(Link, { + get children: [ + "new" + ] + }), null); + insert(_tmpl$2, createComponent(Link, { + get children: [ + "comments" + ] + }), null); + insert(_tmpl$2, createComponent(Link, { + get children: [ + "show" + ] + }), null); + insert(_tmpl$2, createComponent(Link, { + get children: [ + "ask" + ] + }), null); + insert(_tmpl$2, createComponent(Link, { + get children: [ + "jobs" + ] + }), null); + insert(_tmpl$2, createComponent(Link, { + get children: [ + "submit" + ] + }), null); + return _tmpl$2; +}; +const template12 = () => { + var _tmpl$1 = _tmpl$1.cloneNode(true); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "comments" + ] + }), null); + insert(_tmpl$1, createComponent(Link, { + get children: [ + "show" + ] + }), null); + return _tmpl$1; +}; + +class Template13 { + render() { + createComponent(Component, { + prop: this.something, + get onClick: () => { + return () => this.shouldStay; + }, + get children: [ + createComponent(Nested, { + prop: this.data, + get children: [ + this.content + ] + }) + ] + }); + } +} +const Template14 = createComponent(Component, { + get children: [ + data() + ] +}); +const Template15 = createComponent(Component, {}); +const Template16 = createComponent(Component, { + something +}); +const Template17 = createComponent(Pre, { + get children: [ + " ", + " " + ] +}); +const Template18 = createComponent(Pre, {}); +const Template19 = createComponent(Component, {}); +const Template20 = createComponent(Component, { + class: prop.red ? "red" : "green" +}); +const template21 = createComponent(Component, {}); diff --git a/test/bun.js/solid-dom-fixtures/components/output.js b/test/bun.js/solid-dom-fixtures/components/output.js new file mode 100644 index 000000000..0c49d60a3 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/components/output.js @@ -0,0 +1,443 @@ +import { template as _$template } from "r-dom"; +import { memo as _$memo } from "r-dom"; +import { For as _$For } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { mergeProps as _$mergeProps } from "r-dom"; +import { insert as _$insert } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<div>Hello </div>`, 2), + _tmpl$2 = /*#__PURE__*/ _$template(`<div></div>`, 2), + _tmpl$3 = /*#__PURE__*/ _$template(`<div>From Parent</div>`, 2), + _tmpl$4 = /*#__PURE__*/ _$template( + `<div> | <!> | <!> | <!> | <!> | </div>`, + 6 + ), + _tmpl$5 = /*#__PURE__*/ _$template(`<div> | <!> | <!> | </div>`, 4), + _tmpl$6 = /*#__PURE__*/ _$template(`<div> | <!> | | | <!> | </div>`, 4), + _tmpl$7 = /*#__PURE__*/ _$template(`<span>1</span>`, 2), + _tmpl$8 = /*#__PURE__*/ _$template(`<span>2</span>`, 2), + _tmpl$9 = /*#__PURE__*/ _$template(`<span>3</span>`, 2); + +import { Show } from "somewhere"; + +const Child = (props) => { + const [s, set] = createSignal(); + return [ + (() => { + const _el$ = _tmpl$.cloneNode(true), + _el$2 = _el$.firstChild; + + const _ref$ = props.ref; + typeof _ref$ === "function" ? _ref$(_el$) : (props.ref = _el$); + + _$insert(_el$, () => props.name, null); + + return _el$; + })(), + (() => { + const _el$3 = _tmpl$2.cloneNode(true); + + set(_el$3); + + _$insert(_el$3, () => props.children); + + return _el$3; + })(), + ]; +}; + +const template = (props) => { + let childRef; + const { content } = props; + return (() => { + const _el$4 = _tmpl$2.cloneNode(true); + + _$insert( + _el$4, + _$createComponent( + Child, + _$mergeProps( + { + name: "John", + }, + props, + { + ref(r$) { + const _ref$2 = childRef; + typeof _ref$2 === "function" ? _ref$2(r$) : (childRef = r$); + }, + + booleanProperty: true, + + get children() { + return _tmpl$3.cloneNode(true); + }, + } + ) + ), + null + ); + + _$insert( + _el$4, + _$createComponent( + Child, + _$mergeProps( + { + name: "Jason", + }, + dynamicSpread, + { + ref(r$) { + const _ref$3 = props.ref; + typeof _ref$3 === "function" ? _ref$3(r$) : (props.ref = r$); + }, + + get children() { + const _el$6 = _tmpl$2.cloneNode(true); + + _$insert(_el$6, content); + + return _el$6; + }, + } + ) + ), + null + ); + + _$insert( + _el$4, + _$createComponent(Context.Consumer, { + ref(r$) { + const _ref$4 = props.consumerRef(); + + typeof _ref$4 === "function" && _ref$4(r$); + }, + + children: (context) => context, + }), + null + ); + + return _el$4; + })(); +}; + +const template2 = _$createComponent(Child, { + name: "Jake", + + get dynamic() { + return state.data; + }, + + stale: state.data, + handleClick: clickHandler, + + get ["hyphen-ated"]() { + return state.data; + }, + + ref: (el) => (e = el), +}); + +const template3 = _$createComponent(Child, { + get children() { + return [ + _tmpl$2.cloneNode(true), + _tmpl$2.cloneNode(true), + _tmpl$2.cloneNode(true), + "After", + ]; + }, +}); + +const [s, set] = createSignal(); + +const template4 = _$createComponent(Child, { + ref: set, + + get children() { + return _tmpl$2.cloneNode(true); + }, +}); + +const template5 = _$createComponent(Child, { + get dynamic() { + return state.dynamic; + }, + + get children() { + return state.dynamic; + }, +}); // builtIns + +const template6 = _$createComponent(_$For, { + get each() { + return state.list; + }, + + get fallback() { + return _$createComponent(Loading, {}); + }, + + children: (item) => + _$createComponent(Show, { + get when() { + return state.condition; + }, + + children: item, + }), +}); + +const template7 = _$createComponent(Child, { + get children() { + return [_tmpl$2.cloneNode(true), _$memo(() => state.dynamic)]; + }, +}); + +const template8 = _$createComponent(Child, { + get children() { + return [(item) => item, (item) => item]; + }, +}); + +const template9 = _$createComponent(_garbage, { + children: "Hi", +}); + +const template10 = (() => { + const _el$12 = _tmpl$4.cloneNode(true), + _el$13 = _el$12.firstChild, + _el$18 = _el$13.nextSibling, + _el$14 = _el$18.nextSibling, + _el$19 = _el$14.nextSibling, + _el$15 = _el$19.nextSibling, + _el$20 = _el$15.nextSibling, + _el$16 = _el$20.nextSibling, + _el$21 = _el$16.nextSibling, + _el$17 = _el$21.nextSibling; + + _$insert( + _el$12, + _$createComponent(Link, { + children: "new", + }), + _el$13 + ); + + _$insert( + _el$12, + _$createComponent(Link, { + children: "comments", + }), + _el$18 + ); + + _$insert( + _el$12, + _$createComponent(Link, { + children: "show", + }), + _el$19 + ); + + _$insert( + _el$12, + _$createComponent(Link, { + children: "ask", + }), + _el$20 + ); + + _$insert( + _el$12, + _$createComponent(Link, { + children: "jobs", + }), + _el$21 + ); + + _$insert( + _el$12, + _$createComponent(Link, { + children: "submit", + }), + null + ); + + return _el$12; +})(); + +const template11 = (() => { + const _el$22 = _tmpl$5.cloneNode(true), + _el$23 = _el$22.firstChild, + _el$26 = _el$23.nextSibling, + _el$24 = _el$26.nextSibling, + _el$27 = _el$24.nextSibling, + _el$25 = _el$27.nextSibling; + + _$insert( + _el$22, + _$createComponent(Link, { + children: "new", + }), + _el$23 + ); + + _$insert( + _el$22, + _$createComponent(Link, { + children: "comments", + }), + _el$26 + ); + + _$insert( + _el$22, + _$createComponent(Link, { + children: "show", + }), + _el$26 + ); + + _$insert( + _el$22, + _$createComponent(Link, { + children: "ask", + }), + _el$27 + ); + + _$insert( + _el$22, + _$createComponent(Link, { + children: "jobs", + }), + _el$27 + ); + + _$insert( + _el$22, + _$createComponent(Link, { + children: "submit", + }), + null + ); + + return _el$22; +})(); + +const template12 = (() => { + const _el$28 = _tmpl$6.cloneNode(true), + _el$29 = _el$28.firstChild, + _el$34 = _el$29.nextSibling, + _el$30 = _el$34.nextSibling, + _el$35 = _el$30.nextSibling, + _el$33 = _el$35.nextSibling; + + _$insert( + _el$28, + _$createComponent(Link, { + children: "comments", + }), + _el$34 + ); + + _$insert( + _el$28, + _$createComponent(Link, { + children: "show", + }), + _el$35 + ); + + return _el$28; +})(); + +class Template13 { + render() { + const _self$ = this; + + _$createComponent(Component, { + get prop() { + return _self$.something; + }, + + onClick: () => _self$.shouldStay, + + get children() { + return _$createComponent(Nested, { + get prop() { + return _self$.data; + }, + + get children() { + return _self$.content; + }, + }); + }, + }); + } +} + +const Template14 = _$createComponent(Component, { + get children() { + return data(); + }, +}); + +const Template15 = _$createComponent(Component, props); + +const Template16 = _$createComponent( + Component, + _$mergeProps( + { + something: something, + }, + props + ) +); + +const Template17 = _$createComponent(Pre, { + get children() { + return [ + _tmpl$7.cloneNode(true), + " ", + _tmpl$8.cloneNode(true), + " ", + _tmpl$9.cloneNode(true), + ]; + }, +}); + +const Template18 = _$createComponent(Pre, { + get children() { + return [ + _tmpl$7.cloneNode(true), + _tmpl$8.cloneNode(true), + _tmpl$9.cloneNode(true), + ]; + }, +}); + +const Template19 = _$createComponent( + Component, + _$mergeProps(() => s.dynamic()) +); + +const Template20 = _$createComponent(Component, { + get ["class"]() { + return prop.red ? "red" : "green"; + }, +}); + +const template21 = _$createComponent( + Component, + _$mergeProps(() => ({ + get [key()]() { + return props.value; + }, + })) +); diff --git a/test/bun.js/solid-dom-fixtures/conditionalExpressions/code.js b/test/bun.js/solid-dom-fixtures/conditionalExpressions/code.js new file mode 100644 index 000000000..80f1a6a4f --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/conditionalExpressions/code.js @@ -0,0 +1,71 @@ +const template1 = <div>{simple}</div>; + +const template2 = <div>{state.dynamic}</div>; + +const template3 = <div>{simple ? good : bad}</div>; + +const template4 = <div>{simple ? good() : bad}</div>; + +const template5 = <div>{state.dynamic ? good() : bad}</div>; + +const template6 = <div>{state.dynamic && good()}</div>; + +const template7 = ( + <div>{state.count > 5 ? (state.dynamic ? best : good()) : bad}</div> +); + +const template8 = <div>{state.dynamic && state.something && good()}</div>; + +const template9 = <div>{(state.dynamic && good()) || bad}</div>; + +const template10 = ( + <div>{state.a ? "a" : state.b ? "b" : state.c ? "c" : "fallback"}</div> +); + +const template11 = ( + <div>{state.a ? a() : state.b ? b() : state.c ? "c" : "fallback"}</div> +); + +const template12 = <Comp render={state.dynamic ? good() : bad} />; + +// no dynamic predicate +const template13 = <Comp render={state.dynamic ? good : bad} />; + +const template14 = <Comp render={state.dynamic && good()} />; + +// no dynamic predicate +const template15 = <Comp render={state.dynamic && good} />; + +const template16 = <Comp render={state.dynamic || good()} />; + +const template17 = <Comp render={state.dynamic ? <Comp /> : <Comp />} />; + +const template18 = <Comp>{state.dynamic ? <Comp /> : <Comp />}</Comp>; + +const template19 = <div innerHTML={state.dynamic ? <Comp /> : <Comp />} />; + +const template20 = <div>{state.dynamic ? <Comp /> : <Comp />}</div>; + +const template21 = <Comp render={state?.dynamic ? "a" : "b"} />; + +const template22 = <Comp>{state?.dynamic ? "a" : "b"}</Comp>; + +const template23 = <div innerHTML={state?.dynamic ? "a" : "b"} />; + +const template24 = <div>{state?.dynamic ? "a" : "b"}</div>; + +const template25 = <Comp render={state.dynamic ?? <Comp />} />; + +const template26 = <Comp>{state.dynamic ?? <Comp />}</Comp>; + +const template27 = <div innerHTML={state.dynamic ?? <Comp />} />; + +const template28 = <div>{state.dynamic ?? <Comp />}</div>; + +const template29 = <div>{(thing() && thing1()) ?? thing2() ?? thing3()}</div>; + +const template30 = <div>{thing() || thing1() || thing2()}</div>; + +const template31 = ( + <Comp value={count() ? (count() ? count() : count()) : count()} /> +); diff --git a/test/bun.js/solid-dom-fixtures/conditionalExpressions/output.bun.js b/test/bun.js/solid-dom-fixtures/conditionalExpressions/output.bun.js new file mode 100644 index 000000000..36c3f649b --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/conditionalExpressions/output.bun.js @@ -0,0 +1,144 @@ +var _tmpl = template("<div</div>", 2), _tmpl$1 = template("<div/>", 0); +const template1 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, simple, null); + return _tmpl; +}; +const template2 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic, null); + return _tmpl; +}; +const template3 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, simple ? good : bad, null); + return _tmpl; +}; +const template4 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, simple ? good() : bad, null); + return _tmpl; +}; +const template5 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic ? good() : bad, null); + return _tmpl; +}; +const template6 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic && good(), null); + return _tmpl; +}; +const template7 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.count > 5 ? state.dynamic ? best : good() : bad, null); + return _tmpl; +}; +const template8 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic && state.something && good(), null); + return _tmpl; +}; +const template9 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic && good() || bad, null); + return _tmpl; +}; +const template10 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.a ? "a" : state.b ? "b" : state.c ? "c" : "fallback", null); + return _tmpl; +}; +const template11 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.a ? a() : state.b ? b() : state.c ? "c" : "fallback", null); + return _tmpl; +}; +const template12 = createComponent(Comp, { + render: state.dynamic ? good() : bad +}); +const template13 = createComponent(Comp, { + render: state.dynamic ? good : bad +}); +const template14 = createComponent(Comp, { + render: state.dynamic && good() +}); +const template15 = createComponent(Comp, { + render: state.dynamic && good +}); +const template16 = createComponent(Comp, { + render: state.dynamic || good() +}); +const template17 = createComponent(Comp, { + render: state.dynamic ? createComponent(Comp, {}) : createComponent(Comp, {}) +}); +const template18 = createComponent(Comp, { + get children: [ + state.dynamic ? createComponent(Comp, {}) : createComponent(Comp, {}) + ] +}); +const template19 = () => { + var _el = _tmpl$1.cloneNode(true); + effect(() => { + return setAttribute(_el, "innerHTML", state.dynamic ? createComponent(Comp, {}) : createComponent(Comp, {})); + }); + return _el; +}; +const template20 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic ? createComponent(Comp, {}) : createComponent(Comp, {}), null); + return _tmpl; +}; +const template21 = createComponent(Comp, { + render: state?.dynamic ? "a" : "b" +}); +const template22 = createComponent(Comp, { + get children: [ + state?.dynamic ? "a" : "b" + ] +}); +const template23 = () => { + var _el = _tmpl$1.cloneNode(true); + effect(() => { + return setAttribute(_el, "innerHTML", state?.dynamic ? "a" : "b"); + }); + return _el; +}; +const template24 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state?.dynamic ? "a" : "b", null); + return _tmpl; +}; +const template25 = createComponent(Comp, { + render: state.dynamic ?? createComponent(Comp, {}) +}); +const template26 = createComponent(Comp, { + get children: [ + state.dynamic ?? createComponent(Comp, {}) + ] +}); +const template27 = () => { + var _el = _tmpl$1.cloneNode(true); + effect(() => { + return setAttribute(_el, "innerHTML", state.dynamic ?? createComponent(Comp, {})); + }); + return _el; +}; +const template28 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, state.dynamic ?? createComponent(Comp, {}), null); + return _tmpl; +}; +const template29 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, (thing() && thing1()) ?? thing2() ?? thing3(), null); + return _tmpl; +}; +const template30 = () => { + var _tmpl = _tmpl.cloneNode(true); + insert(_tmpl, thing() || thing1() || thing2(), null); + return _tmpl; +}; +const template31 = createComponent(Comp, { + value: count() ? count() ? count() : count() : count() +}); diff --git a/test/bun.js/solid-dom-fixtures/conditionalExpressions/output.js b/test/bun.js/solid-dom-fixtures/conditionalExpressions/output.js new file mode 100644 index 000000000..1511f4222 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/conditionalExpressions/output.js @@ -0,0 +1,319 @@ +import { template as _$template } from "r-dom"; +import { effect as _$effect } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { memo as _$memo } from "r-dom"; +import { insert as _$insert } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<div></div>`, 2); + +const template1 = (() => { + const _el$ = _tmpl$.cloneNode(true); + + _$insert(_el$, simple); + + return _el$; +})(); + +const template2 = (() => { + const _el$2 = _tmpl$.cloneNode(true); + + _$insert(_el$2, () => state.dynamic); + + return _el$2; +})(); + +const template3 = (() => { + const _el$3 = _tmpl$.cloneNode(true); + + _$insert(_el$3, simple ? good : bad); + + return _el$3; +})(); + +const template4 = (() => { + const _el$4 = _tmpl$.cloneNode(true); + + _$insert(_el$4, () => (simple ? good() : bad)); + + return _el$4; +})(); + +const template5 = (() => { + const _el$5 = _tmpl$.cloneNode(true); + + _$insert( + _el$5, + (() => { + const _c$ = _$memo(() => !!state.dynamic, true); + + return () => (_c$() ? good() : bad); + })() + ); + + return _el$5; +})(); + +const template6 = (() => { + const _el$6 = _tmpl$.cloneNode(true); + + _$insert( + _el$6, + (() => { + const _c$2 = _$memo(() => !!state.dynamic, true); + + return () => _c$2() && good(); + })() + ); + + return _el$6; +})(); + +const template7 = (() => { + const _el$7 = _tmpl$.cloneNode(true); + + _$insert( + _el$7, + (() => { + const _c$3 = _$memo(() => state.count > 5, true); + + return () => + _c$3() + ? (() => { + const _c$4 = _$memo(() => !!state.dynamic, true); + + return () => (_c$4() ? best : good()); + })() + : bad; + })() + ); + + return _el$7; +})(); + +const template8 = (() => { + const _el$8 = _tmpl$.cloneNode(true); + + _$insert( + _el$8, + (() => { + const _c$5 = _$memo(() => !!(state.dynamic && state.something), true); + + return () => _c$5() && good(); + })() + ); + + return _el$8; +})(); + +const template9 = (() => { + const _el$9 = _tmpl$.cloneNode(true); + + _$insert( + _el$9, + (() => { + const _c$6 = _$memo(() => !!state.dynamic, true); + + return () => (_c$6() && good()) || bad; + })() + ); + + return _el$9; +})(); + +const template10 = (() => { + const _el$10 = _tmpl$.cloneNode(true); + + _$insert(_el$10, () => + state.a ? "a" : state.b ? "b" : state.c ? "c" : "fallback" + ); + + return _el$10; +})(); + +const template11 = (() => { + const _el$11 = _tmpl$.cloneNode(true); + + _$insert( + _el$11, + (() => { + const _c$7 = _$memo(() => !!state.a, true); + + return () => + _c$7() + ? a() + : (() => { + const _c$8 = _$memo(() => !!state.b, true); + + return () => (_c$8() ? b() : state.c ? "c" : "fallback"); + })(); + })() + ); + + return _el$11; +})(); + +const template12 = _$createComponent(Comp, { + get render() { + return _$memo(() => !!state.dynamic, true)() ? good() : bad; + }, +}); // no dynamic predicate + +const template13 = _$createComponent(Comp, { + get render() { + return state.dynamic ? good : bad; + }, +}); + +const template14 = _$createComponent(Comp, { + get render() { + return _$memo(() => !!state.dynamic, true)() && good(); + }, +}); // no dynamic predicate + +const template15 = _$createComponent(Comp, { + get render() { + return state.dynamic && good; + }, +}); + +const template16 = _$createComponent(Comp, { + get render() { + return state.dynamic || good(); + }, +}); + +const template17 = _$createComponent(Comp, { + get render() { + return _$memo(() => !!state.dynamic, true)() + ? _$createComponent(Comp, {}) + : _$createComponent(Comp, {}); + }, +}); + +const template18 = _$createComponent(Comp, { + get children() { + return _$memo(() => !!state.dynamic, true)() + ? _$createComponent(Comp, {}) + : _$createComponent(Comp, {}); + }, +}); + +const template19 = (() => { + const _el$12 = _tmpl$.cloneNode(true); + + _$effect( + () => + (_el$12.innerHTML = state.dynamic + ? _$createComponent(Comp, {}) + : _$createComponent(Comp, {})) + ); + + return _el$12; +})(); + +const template20 = (() => { + const _el$13 = _tmpl$.cloneNode(true); + + _$insert( + _el$13, + (() => { + const _c$9 = _$memo(() => !!state.dynamic, true); + + return () => + _c$9() ? _$createComponent(Comp, {}) : _$createComponent(Comp, {}); + })() + ); + + return _el$13; +})(); + +const template21 = _$createComponent(Comp, { + get render() { + return state?.dynamic ? "a" : "b"; + }, +}); + +const template22 = _$createComponent(Comp, { + get children() { + return state?.dynamic ? "a" : "b"; + }, +}); + +const template23 = (() => { + const _el$14 = _tmpl$.cloneNode(true); + + _$effect(() => (_el$14.innerHTML = state?.dynamic ? "a" : "b")); + + return _el$14; +})(); + +const template24 = (() => { + const _el$15 = _tmpl$.cloneNode(true); + + _$insert(_el$15, () => (state?.dynamic ? "a" : "b")); + + return _el$15; +})(); + +const template25 = _$createComponent(Comp, { + get render() { + return state.dynamic ?? _$createComponent(Comp, {}); + }, +}); + +const template26 = _$createComponent(Comp, { + get children() { + return state.dynamic ?? _$createComponent(Comp, {}); + }, +}); + +const template27 = (() => { + const _el$16 = _tmpl$.cloneNode(true); + + _$effect( + () => (_el$16.innerHTML = state.dynamic ?? _$createComponent(Comp, {})) + ); + + return _el$16; +})(); + +const template28 = (() => { + const _el$17 = _tmpl$.cloneNode(true); + + _$insert(_el$17, () => state.dynamic ?? _$createComponent(Comp, {})); + + return _el$17; +})(); + +const template29 = (() => { + const _el$18 = _tmpl$.cloneNode(true); + + _$insert( + _el$18, + (() => { + const _c$10 = _$memo(() => !!thing(), true); + + return () => (_c$10() && thing1()) ?? thing2() ?? thing3(); + })() + ); + + return _el$18; +})(); + +const template30 = (() => { + const _el$19 = _tmpl$.cloneNode(true); + + _$insert(_el$19, () => thing() || thing1() || thing2()); + + return _el$19; +})(); + +const template31 = _$createComponent(Comp, { + get value() { + return _$memo(() => !!count(), true)() + ? _$memo(() => !!count(), true)() + ? count() + : count() + : count(); + }, +}); diff --git a/test/bun.js/solid-dom-fixtures/customElements/code.js b/test/bun.js/solid-dom-fixtures/customElements/code.js new file mode 100644 index 000000000..f2e2bd02d --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/customElements/code.js @@ -0,0 +1,29 @@ +const template = ( + <my-element + some-attr={name} + notProp={data} + attr:my-attr={data} + prop:someProp={data} + /> +); + +const template2 = ( + <my-element + some-attr={state.name} + notProp={state.data} + attr:my-attr={state.data} + prop:someProp={state.data} + /> +); + +const template3 = ( + <my-element> + <header slot="head">Title</header> + </my-element> +); + +const template4 = ( + <> + <slot name="head"></slot> + </> +); diff --git a/test/bun.js/solid-dom-fixtures/customElements/output.bun.js b/test/bun.js/solid-dom-fixtures/customElements/output.bun.js new file mode 100644 index 000000000..79c46280c --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/customElements/output.bun.js @@ -0,0 +1,27 @@ +var _tmpl = _template$("<my-element/>", 0), _tmpl$2 = _template$('<my-element><header slot="head">Title</header></my-element>', 4); +const template = () => { + var _el = _tmpl.cloneNode(true); + setAttribute(_el, "some-attr", name); + setAttribute(_el, "notProp", data); + setAttribute(_el, "attr:my-attr", data); + setAttribute(_el, "prop:someProp", data); + return _el; +}; +const template2 = () => { + var _el = _tmpl.cloneNode(true); + effect(() => { + return setAttribute(_el, "some-attr", state.name); + }); + effect(() => { + return setAttribute(_el, "notProp", state.data); + }); + effect(() => { + return setAttribute(_el, "attr:my-attr", state.data); + }); + effect(() => { + return setAttribute(_el, "prop:someProp", state.data); + }); + return _el; +}; +const template3 = _tmpl$2.cloneNode(true); +const template4 = ; diff --git a/test/bun.js/solid-dom-fixtures/customElements/output.js b/test/bun.js/solid-dom-fixtures/customElements/output.js new file mode 100644 index 000000000..79274ce2c --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/customElements/output.js @@ -0,0 +1,66 @@ +import { template as _$template } from "r-dom"; +import { effect as _$effect } from "r-dom"; +import { getOwner as _$getOwner } from "r-dom"; +import { setAttribute as _$setAttribute } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<my-element></my-element>`, 2), + _tmpl$2 = /*#__PURE__*/ _$template( + `<my-element><header slot="head">Title</header></my-element>`, + 4 + ), + _tmpl$3 = /*#__PURE__*/ _$template(`<slot name="head"></slot>`, 2); + +const template = (() => { + const _el$ = document.importNode(_tmpl$, true); + + _el$.someAttr = name; + _el$.notprop = data; + + _$setAttribute(_el$, "my-attr", data); + + _el$.someProp = data; + _el$._$owner = _$getOwner(); + return _el$; +})(); + +const template2 = (() => { + const _el$2 = document.importNode(_tmpl$, true); + + _el$2._$owner = _$getOwner(); + + _$effect( + (_p$) => { + const _v$ = state.name, + _v$2 = state.data, + _v$3 = state.data, + _v$4 = state.data; + _v$ !== _p$._v$ && (_el$2.someAttr = _p$._v$ = _v$); + _v$2 !== _p$._v$2 && (_el$2.notprop = _p$._v$2 = _v$2); + _v$3 !== _p$._v$3 && _$setAttribute(_el$2, "my-attr", (_p$._v$3 = _v$3)); + _v$4 !== _p$._v$4 && (_el$2.someProp = _p$._v$4 = _v$4); + return _p$; + }, + { + _v$: undefined, + _v$2: undefined, + _v$3: undefined, + _v$4: undefined, + } + ); + + return _el$2; +})(); + +const template3 = (() => { + const _el$3 = document.importNode(_tmpl$2, true); + + _el$3._$owner = _$getOwner(); + return _el$3; +})(); + +const template4 = (() => { + const _el$4 = _tmpl$3.cloneNode(true); + + _el$4._$owner = _$getOwner(); + return _el$4; +})(); diff --git a/test/bun.js/solid-dom-fixtures/eventExpressions/code.js b/test/bun.js/solid-dom-fixtures/eventExpressions/code.js new file mode 100644 index 000000000..78bc5e199 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/eventExpressions/code.js @@ -0,0 +1,32 @@ +function hoisted1() { + console.log("hoisted"); +} +const hoisted2 = () => console.log("hoisted delegated"); + +const template = ( + <div id="main"> + <button onchange={() => console.log("bound")}>Change Bound</button> + <button onChange={[(id) => console.log("bound", id), id]}> + Change Bound + </button> + <button onchange={handler}>Change Bound</button> + <button onchange={[handler]}>Change Bound</button> + <button onchange={hoisted1}>Change Bound</button> + <button onclick={() => console.log("delegated")}>Click Delegated</button> + <button onClick={[(id) => console.log("delegated", id), rowId]}> + Click Delegated + </button> + <button onClick={handler}>Click Delegated</button> + <button onClick={[handler]}>Click Delegated</button> + <button onClick={hoisted2}>Click Delegated</button> + <button + on:click={() => console.log("listener")} + on:CAPS-ev={() => console.log("custom")} + > + Click Listener + </button> + <button oncapture:camelClick={() => console.log("listener")}> + Click Capture + </button> + </div> +); diff --git a/test/bun.js/solid-dom-fixtures/eventExpressions/output.bun.js b/test/bun.js/solid-dom-fixtures/eventExpressions/output.bun.js new file mode 100644 index 000000000..5d90654f9 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/eventExpressions/output.bun.js @@ -0,0 +1,57 @@ +var _tmpl$1 = _template$( + '<div id="main"><button>Change Bound</button><button>Change Bound</button><button>Change Bound</button><button>Change Bound</button><button>Change Bound</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Listener</button><button>Click Capture</button></div>', + 26 +); +function hoisted1() { + console.log("hoisted"); +} +const hoisted2 = () => console.log("hoisted delegated"); +const template = () => { + var _el = _tmpl.cloneNode(true), + _el$1 = _el.firstChild, + _el$2 = _el$1.nextSibling, + _el$3 = _el$2.nextSibling, + _el$4 = _el$3.nextSibling, + _el$5 = _el$4.nextSibling, + _el$6 = _el$5.nextSibling, + _el$7 = _el$6.nextSibling, + _el$8 = _el$7.nextSibling, + _el$9 = _el$8.nextSibling, + _el$10 = _el$9.nextSibling, + _el$11 = _el$10.nextSibling; + effect(() => { + return setAttribute(_el, "onchange", () => console.log("bound")); + }); + effect(() => { + return setAttribute(_el$1, "onChange", [ + (id) => console.log("bound", id), + id, + ]); + }); + setAttribute(_el$2, "onchange", handler); + effect(() => { + return setAttribute(_el$3, "onchange", [handler]); + }); + setAttribute(_el$4, "onchange", hoisted1); + _el$5.$$click = () => console.log("delegated"); + effect(() => { + return (_el$6.$$click = [(id) => console.log("delegated", id), rowId]); + }); + effect(() => { + return (_el$7.$$click = handler); + }); + effect(() => { + return (_el$8.$$click = [handler]); + }); + effect(() => { + return (_el$9.$$click = hoisted2); + }); + _el$10.addEventListener("click", () => console.log("listener")); + _el$10.addEventListener("CAPS-ev", () => console.log("custom")); + _el$11.addEventListener( + "apture:camelClick", + () => console.log("listener"), + true + ); + return _el; +}; diff --git a/test/bun.js/solid-dom-fixtures/eventExpressions/output.js b/test/bun.js/solid-dom-fixtures/eventExpressions/output.js new file mode 100644 index 000000000..c24a1f89f --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/eventExpressions/output.js @@ -0,0 +1,63 @@ +import { template as _$template } from "r-dom"; +import { delegateEvents as _$delegateEvents } from "r-dom"; +import { addEventListener as _$addEventListener } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template( + `<div id="main"><button>Change Bound</button><button>Change Bound</button><button>Change Bound</button><button>Change Bound</button><button>Change Bound</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Delegated</button><button>Click Listener</button><button>Click Capture</button></div>`, + 26 +); + +function hoisted1() { + console.log("hoisted"); +} + +const hoisted2 = () => console.log("hoisted delegated"); + +const template = (() => { + const _el$ = _tmpl$.cloneNode(true), + _el$2 = _el$.firstChild, + _el$3 = _el$2.nextSibling, + _el$4 = _el$3.nextSibling, + _el$5 = _el$4.nextSibling, + _el$6 = _el$5.nextSibling, + _el$7 = _el$6.nextSibling, + _el$8 = _el$7.nextSibling, + _el$9 = _el$8.nextSibling, + _el$10 = _el$9.nextSibling, + _el$11 = _el$10.nextSibling, + _el$12 = _el$11.nextSibling, + _el$13 = _el$12.nextSibling; + + _el$2.addEventListener("change", () => console.log("bound")); + + _el$3.addEventListener("change", (e) => + ((id) => console.log("bound", id))(id, e) + ); + + _$addEventListener(_el$4, "change", handler); + + _el$5.addEventListener("change", handler); + + _el$6.addEventListener("change", hoisted1); + + _el$7.$$click = () => console.log("delegated"); + + _el$8.$$click = (id) => console.log("delegated", id); + + _el$8.$$clickData = rowId; + + _$addEventListener(_el$9, "click", handler, true); + + _el$10.$$click = handler; + _el$11.$$click = hoisted2; + + _el$12.addEventListener("click", () => console.log("listener")); + + _el$12.addEventListener("CAPS-ev", () => console.log("custom")); + + _el$13.addEventListener("camelClick", () => console.log("listener"), true); + + return _el$; +})(); + +_$delegateEvents(["click"]); diff --git a/test/bun.js/solid-dom-fixtures/fragments/code.js b/test/bun.js/solid-dom-fixtures/fragments/code.js new file mode 100644 index 000000000..0b6021e44 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/fragments/code.js @@ -0,0 +1,83 @@ +const multiStatic = ( + <> + <div>First</div> + <div>Last</div> + </> +); + +const multiExpression = ( + <> + <div>First</div> + {inserted} + <div>Last</div> + After + </> +); + +const multiDynamic = ( + <> + <div id={state.first}>First</div> + {state.inserted} + <div id={state.last}>Last</div> + After + </> +); + +const singleExpression = <>{inserted}</>; + +const singleDynamic = <>{inserted()}</>; + +const firstStatic = ( + <> + {inserted} + <div /> + </> +); + +const firstDynamic = ( + <> + {inserted()} + <div /> + </> +); + +const firstComponent = ( + <> + <Component /> + <div /> + </> +); + +const lastStatic = ( + <> + <div /> + {inserted} + </> +); + +const lastDynamic = ( + <> + <div /> + {inserted()} + </> +); + +const lastComponent = ( + <> + <div /> + <Component /> + </> +); + +const spaces = ( + <> + <span>1</span> <span>2</span> <span>3</span> + </> +); +const multiLineTrailing = ( + <> + <span>1</span> + <span>2</span> + <span>3</span> + </> +); diff --git a/test/bun.js/solid-dom-fixtures/fragments/output.bun.js b/test/bun.js/solid-dom-fixtures/fragments/output.bun.js new file mode 100644 index 000000000..54d980cee --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/fragments/output.bun.js @@ -0,0 +1,13 @@ +const multiStatic = ; +const multiExpression = ; +const multiDynamic = ; +const singleExpression = ; +const singleDynamic = ; +const firstStatic = ; +const firstDynamic = ; +const firstComponent = ; +const lastStatic = ; +const lastDynamic = ; +const lastComponent = ; +const spaces = ; +const multiLineTrailing = ; diff --git a/test/bun.js/solid-dom-fixtures/fragments/output.js b/test/bun.js/solid-dom-fixtures/fragments/output.js new file mode 100644 index 000000000..5fe0c767c --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/fragments/output.js @@ -0,0 +1,66 @@ +import { template as _$template } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { memo as _$memo } from "r-dom"; +import { setAttribute as _$setAttribute } from "r-dom"; +import { effect as _$effect } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<div>First</div>`, 2), + _tmpl$2 = /*#__PURE__*/ _$template(`<div>Last</div>`, 2), + _tmpl$3 = /*#__PURE__*/ _$template(`<div></div>`, 2), + _tmpl$4 = /*#__PURE__*/ _$template(`<span>1</span>`, 2), + _tmpl$5 = /*#__PURE__*/ _$template(`<span>2</span>`, 2), + _tmpl$6 = /*#__PURE__*/ _$template(`<span>3</span>`, 2); + +const multiStatic = [_tmpl$.cloneNode(true), _tmpl$2.cloneNode(true)]; +const multiExpression = [ + _tmpl$.cloneNode(true), + inserted, + _tmpl$2.cloneNode(true), + "After", +]; +const multiDynamic = [ + (() => { + const _el$5 = _tmpl$.cloneNode(true); + + _$effect(() => _$setAttribute(_el$5, "id", state.first)); + + return _el$5; + })(), + _$memo(() => state.inserted), + (() => { + const _el$6 = _tmpl$2.cloneNode(true); + + _$effect(() => _$setAttribute(_el$6, "id", state.last)); + + return _el$6; + })(), + "After", +]; +const singleExpression = inserted; + +const singleDynamic = _$memo(inserted); + +const firstStatic = [inserted, _tmpl$3.cloneNode(true)]; +const firstDynamic = [_$memo(inserted), _tmpl$3.cloneNode(true)]; +const firstComponent = [ + _$createComponent(Component, {}), + _tmpl$3.cloneNode(true), +]; +const lastStatic = [_tmpl$3.cloneNode(true), inserted]; +const lastDynamic = [_tmpl$3.cloneNode(true), _$memo(inserted)]; +const lastComponent = [ + _tmpl$3.cloneNode(true), + _$createComponent(Component, {}), +]; +const spaces = [ + _tmpl$4.cloneNode(true), + " ", + _tmpl$5.cloneNode(true), + " ", + _tmpl$6.cloneNode(true), +]; +const multiLineTrailing = [ + _tmpl$4.cloneNode(true), + _tmpl$5.cloneNode(true), + _tmpl$6.cloneNode(true), +]; diff --git a/test/bun.js/solid-dom-fixtures/insertChildren/code.js b/test/bun.js/solid-dom-fixtures/insertChildren/code.js new file mode 100644 index 000000000..41d3d017e --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/insertChildren/code.js @@ -0,0 +1,36 @@ +const children = <div />; +const dynamic = { + children, +}; +const template = <Module children={children} />; +const template2 = <module children={children} />; +const template3 = <module children={children}>Hello</module>; +const template4 = ( + <module children={children}> + <Hello /> + </module> +); +const template5 = <module children={dynamic.children} />; +const template6 = <Module children={dynamic.children} />; +const template7 = <module {...dynamic} />; +const template8 = <module {...dynamic}>Hello</module>; +const template9 = <module {...dynamic}>{dynamic.children}</module>; +const template10 = <Module {...dynamic}>Hello</Module>; +const template11 = <module children={/*@once*/ state.children} />; +const template12 = <Module children={/*@once*/ state.children} />; +const template13 = <module>{...children}</module>; +const template14 = <Module>{...children}</Module>; +const template15 = <module>{...dynamic.children}</module>; +const template16 = <Module>{...dynamic.children}</Module>; +const template18 = <module>Hi {...children}</module>; +const template19 = <Module>Hi {...children}</Module>; +const template20 = <module>{children()}</module>; +const template21 = <Module>{children()}</Module>; +const template22 = <module>{state.children()}</module>; +const template23 = <Module>{state.children()}</Module>; + +const tiles = []; +tiles.push(<div>Test 1</div>); +const template24 = <div>{tiles}</div>; + +const comma = <div>{(expression(), "static")}</div>; diff --git a/test/bun.js/solid-dom-fixtures/insertChildren/output.js b/test/bun.js/solid-dom-fixtures/insertChildren/output.js new file mode 100644 index 000000000..9ad937742 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/insertChildren/output.js @@ -0,0 +1,185 @@ +import { template as _$template } from "r-dom"; +import { mergeProps as _$mergeProps } from "r-dom"; +import { spread as _$spread } from "r-dom"; +import { insert as _$insert } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<div></div>`, 2), + _tmpl$2 = /*#__PURE__*/ _$template(`<module></module>`, 2), + _tmpl$3 = /*#__PURE__*/ _$template(`<module>Hello</module>`, 2), + _tmpl$4 = /*#__PURE__*/ _$template(`<module>Hi </module>`, 2), + _tmpl$5 = /*#__PURE__*/ _$template(`<div>Test 1</div>`, 2); + +const children = _tmpl$.cloneNode(true); + +const dynamic = { + children, +}; + +const template = _$createComponent(Module, { + children: children, +}); + +const template2 = (() => { + const _el$2 = _tmpl$2.cloneNode(true); + + _$insert(_el$2, children); + + return _el$2; +})(); + +const template3 = _tmpl$3.cloneNode(true); + +const template4 = (() => { + const _el$4 = _tmpl$2.cloneNode(true); + + _$insert(_el$4, _$createComponent(Hello, {})); + + return _el$4; +})(); + +const template5 = (() => { + const _el$5 = _tmpl$2.cloneNode(true); + + _$insert(_el$5, () => dynamic.children); + + return _el$5; +})(); + +const template6 = _$createComponent(Module, { + get children() { + return dynamic.children; + }, +}); + +const template7 = (() => { + const _el$6 = _tmpl$2.cloneNode(true); + + _$spread(_el$6, dynamic, false, false); + + return _el$6; +})(); + +const template8 = (() => { + const _el$7 = _tmpl$3.cloneNode(true); + + _$spread(_el$7, dynamic, false, true); + + return _el$7; +})(); + +const template9 = (() => { + const _el$8 = _tmpl$2.cloneNode(true); + + _$spread(_el$8, dynamic, false, true); + + _$insert(_el$8, () => dynamic.children); + + return _el$8; +})(); + +const template10 = _$createComponent( + Module, + _$mergeProps(dynamic, { + children: "Hello", + }) +); + +const template11 = (() => { + const _el$9 = _tmpl$2.cloneNode(true); + + _$insert(_el$9, state.children); + + return _el$9; +})(); + +const template12 = _$createComponent(Module, { + children: state.children, +}); + +const template13 = (() => { + const _el$10 = _tmpl$2.cloneNode(true); + + _$insert(_el$10, children); + + return _el$10; +})(); + +const template14 = _$createComponent(Module, { + children: children, +}); + +const template15 = (() => { + const _el$11 = _tmpl$2.cloneNode(true); + + _$insert(_el$11, () => dynamic.children); + + return _el$11; +})(); + +const template16 = _$createComponent(Module, { + get children() { + return dynamic.children; + }, +}); + +const template18 = (() => { + const _el$12 = _tmpl$4.cloneNode(true); + + _$insert(_el$12, children, null); + + return _el$12; +})(); + +const template19 = _$createComponent(Module, { + get children() { + return ["Hi ", children]; + }, +}); + +const template20 = (() => { + const _el$13 = _tmpl$2.cloneNode(true); + + _$insert(_el$13, children); + + return _el$13; +})(); + +const template21 = _$createComponent(Module, { + get children() { + return children(); + }, +}); + +const template22 = (() => { + const _el$14 = _tmpl$2.cloneNode(true); + + _$insert(_el$14, () => state.children()); + + return _el$14; +})(); + +const template23 = _$createComponent(Module, { + get children() { + return state.children(); + }, +}); + +const tiles = []; +tiles.push(_tmpl$5.cloneNode(true)); + +const template24 = (() => { + const _el$16 = _tmpl$.cloneNode(true); + + _$insert(_el$16, tiles); + + return _el$16; +})(); + +const comma = (() => { + const _el$17 = _tmpl$.cloneNode(true); + + _$insert(_el$17, () => (expression(), "static")); + + return _el$17; +})(); diff --git a/test/bun.js/solid-dom-fixtures/namespaceElements/code.js b/test/bun.js/solid-dom-fixtures/namespaceElements/code.js new file mode 100644 index 000000000..7ad410329 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/namespaceElements/code.js @@ -0,0 +1,6 @@ +const template = <module.A />; +const template2 = <module.a.B />; +const template3 = <module.A.B />; +const template4 = <module.a-b />; +const template5 = <module.a-b.c-d />; +const template6 = <namespace:tag />; diff --git a/test/bun.js/solid-dom-fixtures/namespaceElements/output.js b/test/bun.js/solid-dom-fixtures/namespaceElements/output.js new file mode 100644 index 000000000..162ffb140 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/namespaceElements/output.js @@ -0,0 +1,16 @@ +import { template as _$template } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<namespace:tag></namespace:tag>`, 2); + +const template = _$createComponent(module.A, {}); + +const template2 = _$createComponent(module.a.B, {}); + +const template3 = _$createComponent(module.A.B, {}); + +const template4 = _$createComponent(module["a-b"], {}); + +const template5 = _$createComponent(module["a-b"]["c-d"], {}); + +const template6 = _tmpl$.cloneNode(true); diff --git a/test/bun.js/solid-dom-fixtures/simpleElements/code.js b/test/bun.js/solid-dom-fixtures/simpleElements/code.js new file mode 100644 index 000000000..c3537ee7d --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/simpleElements/code.js @@ -0,0 +1,9 @@ +const template = ( + <div id="main"> + <style>{"div { color: red; }"}</style> + <h1>Welcome</h1> + <label for={"entry"}>Edit:</label> + <input id="entry" type="text" /> + {/* Comment Node */} + </div> +); diff --git a/test/bun.js/solid-dom-fixtures/simpleElements/output.bun.js b/test/bun.js/solid-dom-fixtures/simpleElements/output.bun.js new file mode 100644 index 000000000..72d61c1e3 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/simpleElements/output.bun.js @@ -0,0 +1,5 @@ +var _tmpl$1 = _template$( + '<div id="main"><style>div { color: red; }</style><h1>Welcome</h1><label for="entry">Edit:</label><input id="entry" type="text"/></div>', + 8 +); +const template = _tmpl$1.cloneNode(true); diff --git a/test/bun.js/solid-dom-fixtures/simpleElements/output.js b/test/bun.js/solid-dom-fixtures/simpleElements/output.js new file mode 100644 index 000000000..5d16f6767 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/simpleElements/output.js @@ -0,0 +1,8 @@ +import { template as _$template } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template( + `<div id="main"><style>div { color: red; }</style><h1>Welcome</h1><label for="entry">Edit:</label><input id="entry" type="text"></div>`, + 9 +); + +const template = _tmpl$.cloneNode(true); diff --git a/test/bun.js/solid-dom-fixtures/textInterpolation/code.js b/test/bun.js/solid-dom-fixtures/textInterpolation/code.js new file mode 100644 index 000000000..21698ea89 --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/textInterpolation/code.js @@ -0,0 +1,72 @@ +const trailing = <span>Hello </span>; +const leading = <span> John</span>; + +/* prettier-ignore */ +const extraSpaces = <span>Hello John</span>; + +const trailingExpr = <span>Hello {name}</span>; +const leadingExpr = <span>{greeting} John</span>; + +/* prettier-ignore */ +const multiExpr = <span>{greeting} {name}</span>; + +/* prettier-ignore */ +const multiExprSpaced = <span> {greeting} {name} </span>; + +/* prettier-ignore */ +const multiExprTogether = <span> {greeting}{name} </span>; + +/* prettier-ignore */ +const multiLine = <span> + + Hello + +</span> + +/* prettier-ignore */ +const multiLineTrailingSpace = <span> + Hello + John +</span> + +/* prettier-ignore */ +const multiLineNoTrailingSpace = <span> + Hello + John +</span> + +/* prettier-ignore */ +const escape = <span> + <Hi> +</span> + +/* prettier-ignore */ +const escape2 = <Comp> + <Hi> +</Comp> + +/* prettier-ignore */ +const escape3 = <> + <Hi> +</> + +const injection = <span>Hi{"<script>alert();</script>"}</span>; + +let value = "World"; +const evaluated = <span>Hello {value + "!"}</span>; + +let number = 4 + 5; +const evaluatedNonString = <span>4 + 5 = {number}</span>; + +const newLineLiteral = ( + <div> + {s} + {"\n"}d + </div> +); + +const trailingSpace = <div>{expr}</div>; + +const trailingSpaceComp = <Comp>{expr}</Comp>; + +const trailingSpaceFrag = <>{expr}</>; diff --git a/test/bun.js/solid-dom-fixtures/textInterpolation/output.bun.js b/test/bun.js/solid-dom-fixtures/textInterpolation/output.bun.js new file mode 100644 index 000000000..eb4c5347a --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/textInterpolation/output.bun.js @@ -0,0 +1,71 @@ +var _tmpl$1 = template("<span>Hello </span>", 2), _tmpl$2 = template("<span> John</span>", 2), _tmpl$3 = template("<span>Hello John</span>", 2), _tmpl$3 = template("<span> </span>", 4), _tmpl$4 = template("<span> </span>", 4), _tmpl$5 = template("<span> </span>", 4), _tmpl$7 = template("<span>Hello</span>", 2), _tmpl$8 = template("<span>Hello John</span>", 2), _tmpl$9 = template("<span> <Hi> </span>", 2), _tmpl$10 = template("<span>Hi<script>alert();</script></span>", 2), _tmpl$10 = template("<span>4 + 5 = </span>", 3), _tmpl$11 = template("<div>\nd</div>", 3), _tmpl$12 = template("<div</div>", 2); +const trailing = _tmpl$1.cloneNode(true); +const leading = _tmpl$2.cloneNode(true); +const extraSpaces = _tmpl$3.cloneNode(true); +const trailingExpr = () => { + var _tmpl$1 = _tmpl$1.cloneNode(true); + insert(_tmpl$1, name, null); + return _tmpl$1; +}; +const leadingExpr = () => { + var _tmpl$2 = _tmpl$2.cloneNode(true); + insert(_tmpl$2, greeting, null); + return _tmpl$2; +}; +const multiExpr = () => { + var _tmpl$3 = _tmpl$3.cloneNode(true); + insert(_tmpl$3, greeting, null); + insert(_tmpl$3, name, null); + return _tmpl$3; +}; +const multiExprSpaced = () => { + var _tmpl$4 = _tmpl$4.cloneNode(true); + insert(_tmpl$4, greeting, null); + insert(_tmpl$4, name, null); + return _tmpl$4; +}; +const multiExprTogether = () => { + var _tmpl$5 = _tmpl$5.cloneNode(true); + insert(_tmpl$5, greeting, null); + insert(_tmpl$5, name, null); + return _tmpl$5; +}; +const multiLine = _tmpl$7.cloneNode(true); +const multiLineTrailingSpace = _tmpl$8.cloneNode(true); +const multiLineNoTrailingSpace = _tmpl$8.cloneNode(true); +const escape = _tmpl$9.cloneNode(true); +const escape2 = createComponent(Comp, { + get children: [ + "\xA0<Hi>\xA0" + ] +}); +const escape3 = ; +const injection = _tmpl$10.cloneNode(true); +let value = "World"; +const evaluated = () => { + var _tmpl$1 = _tmpl$1.cloneNode(true); + insert(_tmpl$1, value + "!", null); + return _tmpl$1; +}; +let number = 4 + 5; +const evaluatedNonString = () => { + var _tmpl$10 = _tmpl$10.cloneNode(true); + insert(_tmpl$10, number, null); + return _tmpl$10; +}; +const newLineLiteral = () => { + var _tmpl$11 = _tmpl$11.cloneNode(true); + insert(_tmpl$11, s, null); + return _tmpl$11; +}; +const trailingSpace = () => { + var _tmpl$12 = _tmpl$12.cloneNode(true); + insert(_tmpl$12, expr, null); + return _tmpl$12; +}; +const trailingSpaceComp = createComponent(Comp, { + get children: [ + expr + ] +}); +const trailingSpaceFrag = ; diff --git a/test/bun.js/solid-dom-fixtures/textInterpolation/output.js b/test/bun.js/solid-dom-fixtures/textInterpolation/output.js new file mode 100644 index 000000000..b86a631fb --- /dev/null +++ b/test/bun.js/solid-dom-fixtures/textInterpolation/output.js @@ -0,0 +1,144 @@ +import { template as _$template } from "r-dom"; +import { createComponent as _$createComponent } from "r-dom"; +import { insert as _$insert } from "r-dom"; + +const _tmpl$ = /*#__PURE__*/ _$template(`<span>Hello </span>`, 2), + _tmpl$2 = /*#__PURE__*/ _$template(`<span> John</span>`, 2), + _tmpl$3 = /*#__PURE__*/ _$template(`<span>Hello John</span>`, 2), + _tmpl$4 = /*#__PURE__*/ _$template(`<span> </span>`, 2), + _tmpl$5 = /*#__PURE__*/ _$template(`<span> <!> <!> </span>`, 4), + _tmpl$6 = /*#__PURE__*/ _$template(`<span> <!> </span>`, 3), + _tmpl$7 = /*#__PURE__*/ _$template(`<span>Hello</span>`, 2), + _tmpl$8 = /*#__PURE__*/ _$template(`<span> <Hi> </span>`, 2), + _tmpl$9 = /*#__PURE__*/ _$template( + `<span>Hi<script>alert();</script></span>`, + 2 + ), + _tmpl$10 = /*#__PURE__*/ _$template(`<span>Hello World!</span>`, 2), + _tmpl$11 = /*#__PURE__*/ _$template(`<span>4 + 5 = 9</span>`, 2), + _tmpl$12 = /*#__PURE__*/ _$template( + `<div> +d</div>`, + 2 + ), + _tmpl$13 = /*#__PURE__*/ _$template(`<div></div>`, 2); + +const trailing = _tmpl$.cloneNode(true); + +const leading = _tmpl$2.cloneNode(true); +/* prettier-ignore */ + +const extraSpaces = _tmpl$3.cloneNode(true); + +const trailingExpr = (() => { + const _el$4 = _tmpl$.cloneNode(true), + _el$5 = _el$4.firstChild; + + _$insert(_el$4, name, null); + + return _el$4; +})(); + +const leadingExpr = (() => { + const _el$6 = _tmpl$2.cloneNode(true), + _el$7 = _el$6.firstChild; + + _$insert(_el$6, greeting, _el$7); + + return _el$6; +})(); +/* prettier-ignore */ + +const multiExpr = (() => { + const _el$8 = _tmpl$4.cloneNode(true), + _el$9 = _el$8.firstChild; + + _$insert(_el$8, greeting, _el$9); + + _$insert(_el$8, name, null); + + return _el$8; +})(); +/* prettier-ignore */ + +const multiExprSpaced = (() => { + const _el$10 = _tmpl$5.cloneNode(true), + _el$11 = _el$10.firstChild, + _el$14 = _el$11.nextSibling, + _el$12 = _el$14.nextSibling, + _el$15 = _el$12.nextSibling, + _el$13 = _el$15.nextSibling; + + _$insert(_el$10, greeting, _el$14); + + _$insert(_el$10, name, _el$15); + + return _el$10; +})(); +/* prettier-ignore */ + +const multiExprTogether = (() => { + const _el$16 = _tmpl$6.cloneNode(true), + _el$17 = _el$16.firstChild, + _el$19 = _el$17.nextSibling, + _el$18 = _el$19.nextSibling; + + _$insert(_el$16, greeting, _el$19); + + _$insert(_el$16, name, _el$19); + + return _el$16; +})(); +/* prettier-ignore */ + +const multiLine = _tmpl$7.cloneNode(true); +/* prettier-ignore */ + +const multiLineTrailingSpace = _tmpl$3.cloneNode(true); +/* prettier-ignore */ + +const multiLineNoTrailingSpace = _tmpl$3.cloneNode(true); +/* prettier-ignore */ + +const escape = _tmpl$8.cloneNode(true); +/* prettier-ignore */ + +const escape2 = _$createComponent(Comp, { + children: "\xA0<Hi>\xA0" +}); +/* prettier-ignore */ + +const escape3 = "\xA0<Hi>\xA0"; + +const injection = _tmpl$9.cloneNode(true); + +let value = "World"; + +const evaluated = _tmpl$10.cloneNode(true); + +let number = 4 + 5; + +const evaluatedNonString = _tmpl$11.cloneNode(true); + +const newLineLiteral = (() => { + const _el$27 = _tmpl$12.cloneNode(true), + _el$28 = _el$27.firstChild; + + _$insert(_el$27, s, _el$28); + + return _el$27; +})(); + +const trailingSpace = (() => { + const _el$29 = _tmpl$13.cloneNode(true); + + _$insert(_el$29, expr); + + return _el$29; +})(); + +const trailingSpaceComp = _$createComponent(Comp, { + children: expr, +}); + +const trailingSpaceFrag = expr; diff --git a/test/bun.js/some-fs.js b/test/bun.js/some-fs.js new file mode 100644 index 000000000..e6b31f162 --- /dev/null +++ b/test/bun.js/some-fs.js @@ -0,0 +1,51 @@ +const { mkdirSync, existsSync } = require("fs"); + +var performance = globalThis.performance; +if (!performance) { + try { + performance = require("perf_hooks").performance; + } catch (e) {} +} + +const count = parseInt(process.env.ITERATIONS || "1", 10) || 1; +var tempdir = `/tmp/some-fs-test/dir/${Date.now()}/hi`; + +for (let i = 0; i < count; i++) { + tempdir += `/${i.toString(36)}`; +} + +if (existsSync(tempdir)) { + throw new Error( + `existsSync reports ${tempdir} exists, but it probably does not` + ); +} + +var origTempDir = tempdir; +var iterations = new Array(count * count).fill(""); +var total = 0; +for (let i = 0; i < count; i++) { + for (let j = 0; j < count; j++) { + iterations[total++] = `${origTempDir}/${j.toString(36)}-${i.toString(36)}`; + } +} +tempdir = origTempDir; +mkdirSync(origTempDir, { recursive: true }); +const recurse = { recursive: false }; +const start = performance.now(); +for (let i = 0; i < total; i++) { + mkdirSync(iterations[i], recurse); +} + +console.log("MKDIR " + total + " depth took:", performance.now() - start, "ms"); + +if (!existsSync(tempdir)) { + throw new Error( + "Expected directory to exist after mkdirSync, but it doesn't" + ); +} + +if (mkdirSync(tempdir, { recursive: true })) { + throw new Error( + "mkdirSync shouldn't return directory name on existing directories" + ); +} diff --git a/test/bun.js/sql-raw.test.js b/test/bun.js/sql-raw.test.js new file mode 100644 index 000000000..ea7f72bd6 --- /dev/null +++ b/test/bun.js/sql-raw.test.js @@ -0,0 +1,71 @@ +import { expect, it } from "bun:test"; + +var SQL = globalThis[Symbol.for("Bun.lazy")]("sqlite"); + +it("works", () => { + const handle = SQL.open("/tmp/northwind.sqlite"); + + const stmt = SQL.prepare( + handle, + 'SELECT * FROM "Order" WHERE OrderDate > date($date)' + ); + expect(stmt.toString()).toBe( + `SELECT * FROM "Order" WHERE OrderDate > date(NULL)` + ); + + expect( + Array.isArray( + stmt.all({ + // do the conversion this way so that this test runs in multiple timezones + $date: new Date( + new Date(1996, 8, 1, 0, 0, 0, 0).toUTCString() + ).toISOString(), + }) + ) + ).toBe(true); + expect(stmt.toString()).toBe( + `SELECT * FROM "Order" WHERE OrderDate > date('1996-09-01T07:00:00.000Z')` + ); + + var ran = stmt.run({ + $date: new Date( + new Date(1997, 8, 1, 0, 0, 0, 0).toUTCString() + ).toISOString(), + }); + expect(Array.isArray(ran)).toBe(false); + expect(ran === undefined).toBe(true); + expect(stmt.toString()).toBe( + `SELECT * FROM "Order" WHERE OrderDate > date('1997-09-01T07:00:00.000Z')` + ); + + expect( + Array.isArray( + stmt.get({ + $date: new Date( + new Date(1998, 8, 1, 0, 0, 0, 0).toUTCString() + ).toISOString(), + }) + ) + ).toBe(false); + expect(stmt.toString()).toBe( + `SELECT * FROM "Order" WHERE OrderDate > date('1998-09-01T07:00:00.000Z')` + ); + expect(stmt.paramsCount).toBe(1); + expect(stmt.columnsCount).toBe(14); + expect(stmt.columns.length).toBe(14); + stmt.finalize(); + SQL.close(handle); +}); + +it("SQL.run works", () => { + const handle = SQL.open("/tmp/northwind.sqlite"); + expect(typeof handle).toBe("number"); + + expect( + SQL.run(handle, 'SELECT * FROM "Order" WHERE OrderDate > date($date)', { + $date: new Date(1996, 8, 1).toISOString(), + }) + ).toBe(undefined); + + SQL.close(handle); +}); diff --git a/test/bun.js/sqlite.test.js b/test/bun.js/sqlite.test.js new file mode 100644 index 000000000..2250f97f0 --- /dev/null +++ b/test/bun.js/sqlite.test.js @@ -0,0 +1,430 @@ +import { expect, it } from "bun:test"; +import { Database, constants } from "bun:sqlite"; + +var encode = (text) => Buffer.from(text); + +it("Database.open", () => { + // in a folder which doesn't exist + try { + Database.open( + "/this/database/does/not/exist.sqlite", + constants.SQLITE_OPEN_READWRITE + ); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // in a file which doesn't exist + try { + Database.open( + `/tmp/database-${Math.random()}.sqlite`, + constants.SQLITE_OPEN_READWRITE + ); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // in a file which doesn't exist + try { + Database.open(`/tmp/database-${Math.random()}.sqlite`, { readonly: true }); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // in a file which doesn't exist + try { + Database.open(`/tmp/database-${Math.random()}.sqlite`, { readwrite: true }); + throw new Error("Expected an error to be thrown"); + } catch (error) { + expect(error.message).toBe("unable to open database file"); + } + + // create works + { + var db = Database.open(`/tmp/database-${Math.random()}.sqlite`, { + create: true, + }); + db.close(); + } + + // this should not throw + // it creates an in-memory db + new Database().close(); +}); + +it("creates", () => { + const db = Database.open(":memory:"); + db.exec( + "CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER, created TEXT, deci FLOAT, blobby BLOB)" + ); + const stmt = db.prepare( + "INSERT INTO test (name, value, deci, created, blobby) VALUES (?, ?, ?, ?, ?)" + ); + + stmt.run([ + "foo", + 1, + Math.fround(1.111), + new Date(1995, 12, 19).toISOString(), + encode("Hello World"), + ]); + stmt.run([ + "bar", + 2, + Math.fround(2.222), + new Date(1995, 12, 19).toISOString(), + encode("Hello World"), + ]); + stmt.run([ + "baz", + 3, + Math.fround(3.333), + new Date(1995, 12, 19).toISOString(), + encode("Hello World"), + ]); + + stmt.finalize(); + + const stmt2 = db.prepare("SELECT * FROM test"); + expect(JSON.stringify(stmt2.get())).toBe( + JSON.stringify({ + id: 1, + name: "foo", + value: 1, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(1.111), + blobby: encode("Hello World"), + }) + ); + + expect(JSON.stringify(stmt2.all())).toBe( + JSON.stringify([ + { + id: 1, + name: "foo", + value: 1, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(1.111), + blobby: encode("Hello World"), + }, + { + id: 2, + name: "bar", + value: 2, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(2.222), + blobby: encode("Hello World"), + }, + { + id: 3, + name: "baz", + value: 3, + created: new Date(1995, 12, 19).toISOString(), + deci: Math.fround(3.333), + blobby: encode("Hello World"), + }, + ]) + ); + expect(stmt2.run()).toBe(undefined); + + // not necessary to run but it's a good practice + stmt2.finalize(); +}); + +it("typechecks", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + const q = db.prepare("SELECT * FROM test WHERE (name = ?)"); + + var expectfail = (val) => { + try { + q.run([val]); + throw new Error("Expected error"); + } catch (e) { + expect(e.message !== "Expected error").toBe(true); + expect(e.name).toBe("TypeError"); + } + + try { + q.all([val]); + throw new Error("Expected error"); + } catch (e) { + expect(e.message !== "Expected error").toBe(true); + expect(e.name).toBe("TypeError"); + } + + try { + q.get([val]); + throw new Error("Expected error"); + } catch (e) { + expect(e.message !== "Expected error").toBe(true); + expect(e.name).toBe("TypeError"); + } + }; + + expectfail(Symbol("oh hai")); + expectfail(new Date()); + expectfail(class Foo {}); + expectfail(() => class Foo {}); + expectfail(new RangeError("what")); + expectfail(new Map()); + expectfail(new Map([["foo", "bar"]])); + expectfail(new Set()); + expectfail(new Set([1, 2, 3])); +}); + +it("db.query supports TypedArray", () => { + const db = Database.open(":memory:"); + + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, blobby BLOB)"); + + const stmt = db.prepare("INSERT INTO test (blobby) VALUES (?)"); + stmt.run([encode("Hello World")]); + stmt.finalize(); + + const stmt2 = db.prepare("SELECT * FROM test"); + expect(JSON.stringify(stmt2.get())).toBe( + JSON.stringify({ + id: 1, + blobby: encode("Hello World"), + }) + ); + + const stmt3 = db.prepare("SELECT * FROM test WHERE (blobby = ?)"); + + expect(JSON.stringify(stmt3.get([encode("Hello World")]))).toBe( + JSON.stringify({ + id: 1, + blobby: encode("Hello World"), + }) + ); + + expect( + JSON.stringify( + db + .query("SELECT * FROM test WHERE (blobby = ?)") + .get([encode("Hello World")]) + ) + ).toBe( + JSON.stringify({ + id: 1, + blobby: encode("Hello World"), + }) + ); + + expect(stmt3.get([encode("Hello World NOT")])).toBe(null); +}); + +it("supports serialize/deserialize", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + const input = db.serialize(); + const db2 = new Database(input); + + const stmt = db2.prepare("SELECT * FROM test"); + expect(JSON.stringify(stmt.get())).toBe( + JSON.stringify({ + id: 1, + name: "Hello", + }) + ); + + expect(JSON.stringify(stmt.all())).toBe( + JSON.stringify([ + { + id: 1, + name: "Hello", + }, + { + id: 2, + name: "World", + }, + ]) + ); + db2.exec("insert into test (name) values ('foo')"); + expect(JSON.stringify(stmt.all())).toBe( + JSON.stringify([ + { + id: 1, + name: "Hello", + }, + { + id: 2, + name: "World", + }, + { + id: 3, + name: "foo", + }, + ]) + ); + + const db3 = new Database(input, { readonly: true }); + try { + db3.exec("insert into test (name) values ('foo')"); + throw new Error("Expected error"); + } catch (e) { + expect(e.message).toBe("attempt to write a readonly database"); + } +}); + +it("db.query()", () => { + const db = Database.open(":memory:"); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + + var q = db.query("SELECT * FROM test WHERE name = ?"); + expect(q.get("Hello") === null).toBe(true); + + db.exec('INSERT INTO test (name) VALUES ("Hello")'); + db.exec('INSERT INTO test (name) VALUES ("World")'); + + var rows = db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]); + + expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + rows = db.query("SELECT * FROM test WHERE name = ?").all(["World"]); + + // if this fails, it means the query caching failed to update + expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 2, name: "World" }])); + + rows = db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]); + expect(JSON.stringify(rows)).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + // check that the query is cached + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(1); + + db.clearQueryCache(); + + // check clearing the cache decremented the counter + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + + q.finalize(); + try { + // check clearing the cache decremented the counter + + q.all(["Hello"]); + throw new Error("Should have thrown"); + } catch (e) { + expect(e.message !== "Should have thrown").toBe(true); + } + + // check that invalid queries are not cached + // and invalid queries throw + try { + db.query("SELECT * FROM BACON", ["Hello"]).all(); + throw new Error("Should have thrown"); + } catch (e) { + expect(e.message !== "Should have thrown").toBe(true); + expect(db[Symbol.for("Bun.Database.cache.count")]).toBe(0); + } + + // check that it supports multiple arguments + expect( + JSON.stringify( + db + .query("SELECT * FROM test where (name = ? OR name = ?)") + .all(["Hello", "Fooooo"]) + ) + ).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + expect( + JSON.stringify( + db + .query("SELECT * FROM test where (name = ? OR name = ?)") + .all("Hello", "Fooooo") + ) + ).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + // throws if insufficeint arguments + try { + db.query("SELECT * FROM test where (name = ? OR name = ?)").all("Hello"); + } catch (e) { + expect(e.message).toBe("Expected 2 values, got 1"); + } + + // named parameters + expect( + JSON.stringify( + db + .query("SELECT * FROM test where (name = $hello OR name = $goodbye)") + .all({ + $hello: "Hello", + $goodbye: "Fooooo", + }) + ) + ).toBe(JSON.stringify([{ id: 1, name: "Hello" }])); + + db.close(); + + // Check that a closed database doesn't crash + // and does throw an error when trying to run a query + try { + db.query("SELECT * FROM test WHERE name = ?").all(["Hello"]); + throw new Error("Should have thrown"); + } catch (e) { + expect(e.message !== "Should have thrown").toBe(true); + } + + // check that we can call close multiple times + // it should not throw so that your code doesn't break + db.close(); + db.close(); + db.close(); +}); + +it("db.transaction()", () => { + const db = Database.open(":memory:"); + + db.exec( + "CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)" + ); + + const insert = db.prepare( + "INSERT INTO cats (name, age) VALUES (@name, @age)" + ); + + expect(db.inTransaction).toBe(false); + const insertMany = db.transaction((cats) => { + expect(db.inTransaction).toBe(true); + try { + for (const cat of cats) insert.run(cat); + } catch (exception) { + throw exception; + } + }); + + try { + insertMany([ + { "@name": "Joey", "@age": 2 }, + { "@name": "Sally", "@age": 4 }, + { "@name": "Junior", "@age": 1 }, + { "@name": "Sally", "@age": 4 }, + ]); + throw new Error("Should have thrown"); + } catch (exception) { + expect(exception.message).toBe("constraint failed"); + } + + expect(db.inTransaction).toBe(false); + expect(db.query("SELECT * FROM cats").all().length).toBe(0); + + expect(db.inTransaction).toBe(false); + insertMany([ + { "@name": "Joey", "@age": 2 }, + { "@name": "Sally", "@age": 4 }, + { "@name": "Junior", "@age": 1 }, + ]); + expect(db.inTransaction).toBe(false); + expect(db.query("SELECT * FROM cats").all().length).toBe(3); + expect(db.inTransaction).toBe(false); +}); diff --git a/test/bun.js/streams.test.js b/test/bun.js/streams.test.js new file mode 100644 index 000000000..ccbea1d09 --- /dev/null +++ b/test/bun.js/streams.test.js @@ -0,0 +1,317 @@ +import { file, readableStreamToArrayBuffer, readableStreamToArray } from "bun"; +import { expect, it, beforeEach, afterEach } from "bun:test"; +import { writeFileSync } from "node:fs"; +import { gc } from "./gc"; +new Uint8Array(); + +beforeEach(() => gc()); +afterEach(() => gc()); + +it("exists globally", () => { + expect(typeof ReadableStream).toBe("function"); + expect(typeof ReadableStreamBYOBReader).toBe("function"); + expect(typeof ReadableStreamBYOBRequest).toBe("function"); + expect(typeof ReadableStreamDefaultController).toBe("function"); + expect(typeof ReadableStreamDefaultReader).toBe("function"); + expect(typeof TransformStream).toBe("function"); + expect(typeof TransformStreamDefaultController).toBe("function"); + expect(typeof WritableStream).toBe("function"); + expect(typeof WritableStreamDefaultController).toBe("function"); + expect(typeof WritableStreamDefaultWriter).toBe("function"); + expect(typeof ByteLengthQueuingStrategy).toBe("function"); + expect(typeof CountQueuingStrategy).toBe("function"); +}); + +it("ReadableStream (direct)", async () => { + var stream = new ReadableStream({ + pull(controller) { + controller.write("hello"); + controller.write("world"); + controller.close(); + }, + cancel() {}, + type: "direct", + }); + var reader = stream.getReader(); + const chunk = await reader.read(); + expect(chunk.value.join("")).toBe(Buffer.from("helloworld").join("")); + expect((await reader.read()).done).toBe(true); + expect((await reader.read()).done).toBe(true); +}); + +it("ReadableStream (bytes)", async () => { + var stream = new ReadableStream({ + start(controller) { + controller.enqueue(Buffer.from("abdefgh")); + }, + pull(controller) {}, + cancel() {}, + type: "bytes", + }); + const chunks = []; + const chunk = await stream.getReader().read(); + chunks.push(chunk.value); + expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join("")); +}); + +it("ReadableStream (default)", async () => { + var stream = new ReadableStream({ + start(controller) { + controller.enqueue(Buffer.from("abdefgh")); + controller.close(); + }, + pull(controller) {}, + cancel() {}, + }); + const chunks = []; + const chunk = await stream.getReader().read(); + chunks.push(chunk.value); + expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join("")); +}); + +it("readableStreamToArray", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + type: "bytes", + }); + + const chunks = await readableStreamToArray(stream); + + expect(chunks[0].join("")).toBe(Buffer.from("abdefgh").join("")); +}); + +it("readableStreamToArrayBuffer (bytes)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + type: "bytes", + }); + const buffer = await readableStreamToArrayBuffer(stream); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("readableStreamToArrayBuffer (default)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + + const buffer = await readableStreamToArrayBuffer(stream); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("ReadableStream for Blob", async () => { + var blob = new Blob(["abdefgh", "ijklmnop"]); + expect(await blob.text()).toBe("abdefghijklmnop"); + var stream; + try { + stream = blob.stream(); + stream = blob.stream(); + } catch (e) { + console.error(e); + console.error(e.stack); + } + const chunks = []; + var reader; + reader = stream.getReader(); + + while (true) { + var chunk; + try { + chunk = await reader.read(); + } catch (e) { + console.error(e); + console.error(e.stack); + } + + if (chunk.done) break; + chunks.push(new TextDecoder().decode(chunk.value)); + } + expect(chunks.join("")).toBe( + new TextDecoder().decode(Buffer.from("abdefghijklmnop")) + ); +}); + +it("ReadableStream for File", async () => { + var blob = file(import.meta.dir + "/fetch.js.txt"); + var stream = blob.stream(24); + const chunks = []; + var reader = stream.getReader(); + stream = undefined; + while (true) { + const chunk = await reader.read(); + gc(true); + if (chunk.done) break; + chunks.push(chunk.value); + expect(chunk.value.byteLength <= 24).toBe(true); + gc(true); + } + reader = undefined; + const output = new Uint8Array(await blob.arrayBuffer()).join(""); + const input = chunks.map((a) => a.join("")).join(""); + expect(output).toBe(input); + gc(true); +}); + +it("ReadableStream for File errors", async () => { + try { + var blob = file(import.meta.dir + "/fetch.js.txt.notfound"); + blob.stream().getReader(); + throw new Error("should not reach here"); + } catch (e) { + expect(e.code).toBe("ENOENT"); + expect(e.syscall).toBe("open"); + } +}); + +it("ReadableStream for empty blob closes immediately", async () => { + var blob = new Blob([]); + var stream = blob.stream(); + const chunks = []; + var reader = stream.getReader(); + while (true) { + const chunk = await reader.read(); + if (chunk.done) break; + chunks.push(chunk.value); + } + + expect(chunks.length).toBe(0); +}); + +it("ReadableStream for empty file closes immediately", async () => { + writeFileSync("/tmp/bun-empty-file-123456", ""); + var blob = file("/tmp/bun-empty-file-123456"); + var stream; + try { + stream = blob.stream(); + } catch (e) { + console.error(e.stack); + } + const chunks = []; + var reader = stream.getReader(); + while (true) { + const chunk = await reader.read(); + if (chunk.done) break; + chunks.push(chunk.value); + } + + expect(chunks.length).toBe(0); +}); + +it("new Response(stream).arrayBuffer() (bytes)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + type: "bytes", + }); + const buffer = await new Response(stream).arrayBuffer(); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("new Response(stream).arrayBuffer() (default)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const buffer = await new Response(stream).arrayBuffer(); + expect(new TextDecoder().decode(new Uint8Array(buffer))).toBe("abdefgh"); +}); + +it("new Response(stream).text() (default)", async () => { + var queue = [Buffer.from("abdefgh")]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const text = await new Response(stream).text(); + expect(text).toBe("abdefgh"); +}); + +it("new Response(stream).json() (default)", async () => { + var queue = [Buffer.from(JSON.stringify({ hello: true }))]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const json = await new Response(stream).json(); + expect(json.hello).toBe(true); +}); + +it("new Response(stream).blob() (default)", async () => { + var queue = [Buffer.from(JSON.stringify({ hello: true }))]; + var stream = new ReadableStream({ + pull(controller) { + var chunk = queue.shift(); + if (chunk) { + controller.enqueue(chunk); + } else { + controller.close(); + } + }, + cancel() {}, + }); + const blob = await new Response(stream).blob(); + expect(await blob.text()).toBe('{"hello":true}'); +}); + +it("Blob.stream() -> new Response(stream).text()", async () => { + var blob = new Blob(["abdefgh"]); + var stream = blob.stream(); + const text = await new Response(stream).text(); + expect(text).toBe("abdefgh"); +}); diff --git a/test/bun.js/text-encoder.test.js b/test/bun.js/text-encoder.test.js new file mode 100644 index 000000000..5687e0222 --- /dev/null +++ b/test/bun.js/text-encoder.test.js @@ -0,0 +1,212 @@ +import { expect, it, describe } from "bun:test"; +import { gc as gcTrace } from "./gc"; + +const getByteLength = (str) => { + // returns the byte length of an utf8 string + var s = str.length; + for (var i = str.length - 1; i >= 0; i--) { + var code = str.charCodeAt(i); + if (code > 0x7f && code <= 0x7ff) s++; + else if (code > 0x7ff && code <= 0xffff) s += 2; + if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate + } + return s; +}; + +describe("TextDecoder", () => { + it("should decode ascii text", () => { + const decoder = new TextDecoder("latin1"); + gcTrace(true); + expect(decoder.encoding).toBe("windows-1252"); + gcTrace(true); + expect(decoder.decode(new Uint8Array([0x41, 0x42, 0x43]))).toBe("ABC"); + gcTrace(true); + const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + gcTrace(true); + expect(decoder.decode(Uint8Array.from(result))).toBe( + String.fromCharCode(...result) + ); + gcTrace(true); + }); + + it("should decode unicode text", () => { + const decoder = new TextDecoder(); + gcTrace(true); + var text = `❤️ Red Heart`; + + const bytes = [ + 226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116, + ]; + const decoded = decoder.decode(Uint8Array.from(bytes)); + expect(decoder.encoding).toBe("utf-8"); + + gcTrace(true); + + for (let i = 0; i < text.length; i++) { + expect(decoded.charCodeAt(i)).toBe(text.charCodeAt(i)); + } + expect(decoded).toHaveLength(text.length); + gcTrace(true); + }); + + it("should decode unicode text with multiple consecutive emoji", () => { + const decoder = new TextDecoder(); + const encoder = new TextEncoder(); + gcTrace(true); + var text = `❤️❤️❤️❤️❤️❤️ Red Heart`; + + text += ` ✨ Sparkles 🔥 Fire 😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 ☹️ 😣 😖 😫 😩 🥺 😢 😭 😤 😠 😡 🤬 🤯 😳 🥵 🥶 😱 😨 😰`; + gcTrace(true); + expect(decoder.decode(encoder.encode(text))).toBe(text); + gcTrace(true); + const bytes = new Uint8Array(getByteLength(text) * 8); + gcTrace(true); + const amount = encoder.encodeInto(text, bytes); + gcTrace(true); + expect(decoder.decode(bytes.subarray(0, amount.written))).toBe(text); + gcTrace(true); + }); +}); + +describe("TextEncoder", () => { + it("should encode latin1 text", () => { + gcTrace(true); + const text = "Hello World!"; + const encoder = new TextEncoder(); + gcTrace(true); + const encoded = encoder.encode(text); + gcTrace(true); + expect(encoded instanceof Uint8Array).toBe(true); + expect(encoded.length).toBe(text.length); + gcTrace(true); + const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + for (let i = 0; i < result.length; i++) { + expect(encoded[i]).toBe(result[i]); + } + }); + + it("should encode long latin1 text", async () => { + const text = "Hello World!".repeat(1000); + const encoder = new TextEncoder(); + gcTrace(true); + const encoded = encoder.encode(text); + gcTrace(true); + expect(encoded instanceof Uint8Array).toBe(true); + expect(encoded.length).toBe(text.length); + gcTrace(true); + const decoded = new TextDecoder().decode(encoded); + expect(decoded).toBe(text); + gcTrace(); + await new Promise((resolve) => setTimeout(resolve, 1)); + gcTrace(); + expect(decoded).toBe(text); + }); + + it("should encode latin1 rope text", () => { + var text = "Hello"; + text += " "; + text += "World!"; + + gcTrace(true); + const encoder = new TextEncoder(); + const encoded = encoder.encode(text); + gcTrace(true); + const into = new Uint8Array(100); + const out = encoder.encodeInto(text, into); + gcTrace(true); + expect(out.read).toBe(text.length); + expect(out.written).toBe(encoded.length); + + expect(encoded instanceof Uint8Array).toBe(true); + const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + for (let i = 0; i < result.length; i++) { + expect(encoded[i]).toBe(result[i]); + expect(encoded[i]).toBe(into[i]); + } + expect(encoded.length).toBe(getByteLength(text)); + }); + + it("should encode utf-16 text", () => { + var text = `❤️ Red Heart + ✨ Sparkles + 🔥 Fire + `; + var encoder = new TextEncoder(); + var decoder = new TextDecoder(); + gcTrace(true); + expect(decoder.decode(encoder.encode(text))).toBe(text); + gcTrace(true); + }); + + // this test is from a web platform test in WebKit + describe("should use a unicode replacement character for invalid surrogate pairs", () => { + var bad = [ + { + encoding: "utf-16le", + input: [0x00, 0xd8], + expected: "\uFFFD", + name: "lone surrogate lead", + }, + { + encoding: "utf-16le", + input: [0x00, 0xdc], + expected: "\uFFFD", + name: "lone surrogate trail", + }, + { + encoding: "utf-16le", + input: [0x00, 0xd8, 0x00, 0x00], + expected: "\uFFFD\u0000", + name: "unmatched surrogate lead", + }, + { + encoding: "utf-16le", + input: [0x00, 0xdc, 0x00, 0x00], + expected: "\uFFFD\u0000", + name: "unmatched surrogate trail", + }, + { + encoding: "utf-16le", + input: [0x00, 0xdc, 0x00, 0xd8], + expected: "\uFFFD\uFFFD", + name: "swapped surrogate pair", + }, + ]; + + bad.forEach(function (t) { + it(t.encoding + " - " + t.name, () => { + gcTrace(true); + expect( + new TextDecoder(t.encoding).decode(new Uint8Array(t.input)) + ).toBe(t.expected); + expect( + new TextDecoder(t.encoding).decode( + new Uint16Array(new Uint8Array(t.input).buffer) + ) + ).toBe(t.expected); + gcTrace(true); + }); + // test(function () { + // assert_throws_js(TypeError, function () { + // new TextDecoder(t.encoding, { fatal: true }).decode( + // new Uint8Array(t.input) + // ); + // }); + // }, t.encoding + " - " + t.name + " (fatal flag set)"); + }); + }); + + it("should encode utf-16 rope text", () => { + gcTrace(true); + var textReal = `❤️ Red Heart ✨ Sparkles 🔥 Fire`; + + var a = textReal.split(""); + var text = ""; + for (let j of a) { + text += j; + } + + var encoder = new TextEncoder(); + expect(new TextDecoder().decode(encoder.encode(text))).toBe(textReal); + }); +}); diff --git a/test/bun.js/toml-fixture.toml b/test/bun.js/toml-fixture.toml new file mode 100644 index 000000000..090563ef7 --- /dev/null +++ b/test/bun.js/toml-fixture.toml @@ -0,0 +1,39 @@ + +framework = "next" +origin = "http://localhost:5000" +inline.array = [1234, 4, 5, 6] + + +[macros] +react-relay = { "graphql" = "node_modules/bun-macro-relay/bun-macro-relay.tsx" } + +[install.scopes] +"@mybigcompany2" = { "token" = "123456", "url" = "https://registry.mybigcompany.com" } +"@mybigcompany3" = { "token" = "123456", "url" = "https://registry.mybigcompany.com", "three" = 4 } + + +[install.scopes."@mybigcompany"] +token = "123456" +url = "https://registry.mybigcompany.com" + +[bundle.packages] +"@emotion/react" = true + + +[dev] +foo = 123 +"foo.bar" = "baz" +"abba.baba" = "baba" +dabba = -123 +doo = 123.456 +one.two.three = 4 + +[[array]] +entry_one = "one" +entry_two = "two" + +[[array]] +entry_one = "three" + +[[array.nested]] +entry_one = "four" diff --git a/test/bun.js/toml.test.js b/test/bun.js/toml.test.js new file mode 100644 index 000000000..44141b2d4 --- /dev/null +++ b/test/bun.js/toml.test.js @@ -0,0 +1,30 @@ +import { describe, it, expect } from "bun:test"; +import { gc } from "./gc"; + +it("syntax", async () => { + gc(); + + const toml = (await import("./toml-fixture.toml")).default; + gc(); + + expect(toml.framework).toBe("next"); + expect(toml.bundle.packages["@emotion/react"]).toBe(true); + expect(toml.array[0].entry_one).toBe("one"); + expect(toml.array[0].entry_two).toBe("two"); + expect(toml.array[1].entry_one).toBe("three"); + expect(toml.array[1].entry_two).toBe(undefined); + expect(toml.array[1].nested[0].entry_one).toBe("four"); + expect(toml.dev.one.two.three).toBe(4); + expect(toml.dev.foo).toBe(123); + expect(toml.inline.array[0]).toBe(1234); + expect(toml.inline.array[1]).toBe(4); + expect(toml.dev["foo.bar"]).toBe("baz"); + expect(toml.install.scopes["@mybigcompany"].url).toBe( + "https://registry.mybigcompany.com" + ); + expect(toml.install.scopes["@mybigcompany2"].url).toBe( + "https://registry.mybigcompany.com" + ); + expect(toml.install.scopes["@mybigcompany3"].three).toBe(4); + gc(); +}); diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js new file mode 100644 index 000000000..f8da4c18c --- /dev/null +++ b/test/bun.js/transpiler.test.js @@ -0,0 +1,1675 @@ +import { expect, it, describe } from "bun:test"; + +describe("Bun.Transpiler", () => { + describe("exports.replace", () => { + const transpiler = new Bun.Transpiler({ + exports: { + replace: { + // export var foo = function() { } + // => + // export var foo = "bar"; + foo: "bar", + + // export const getStaticProps = /* code */ + // => + // export var __N_SSG = true; + getStaticProps: ["__N_SSG", true], + getStaticPaths: ["__N_SSG", true], + // export function getStaticProps(ctx) { /* code */ } + // => + // export var __N_SSP = true; + getServerSideProps: ["__N_SSP", true], + }, + + // Explicitly remove the top-level export, even if it is in use by + // another part of the file + eliminate: ["loader", "localVarToRemove"], + }, + /* only per-file for now, so this isn't good yet */ + treeShaking: true, + + // remove non-bare unused exports, even if they may have side effects + // Consistent with tsc & esbuild, this is enabled by default for TypeScript files + // this flag lets you enable it for JavaScript files + // this already existed, just wasn't exposed in the API + trimUnusedImports: true, + }); + + it("a deletes dead exports and any imports only referenced in dead regions", () => { + const out = transpiler.transformSync(` + import {getUserById} from './my-database'; + + export async function getStaticProps(ctx){ + return { props: { user: await getUserById(ctx.params.id) } }; + } + + export default function MyComponent({user}) { + getStaticProps(); + return <div id='user'>{user.name}</div>; + } + `); + }); + + it("deletes dead exports and any imports only referenced in dead regions", () => { + const output = transpiler.transformSync(` + import deadFS from 'fs'; + import liveFS from 'fs'; + + export var deleteMe = 100; + + export function loader() { + deadFS.readFileSync("/etc/passwd"); + liveFS.readFileSync("/etc/passwd"); + } + + export function action() { + require("foo"); + liveFS.readFileSync("/etc/passwd") + deleteMe = 101; + } + + export function baz() { + require("bar"); + } + `); + expect(output.includes("loader")).toBe(false); + expect(output.includes("react")).toBe(false); + expect(output.includes("action")).toBe(true); + expect(output.includes("deadFS")).toBe(false); + expect(output.includes("liveFS")).toBe(true); + }); + + it("supports replacing exports", () => { + const output = transpiler.transformSync(` + import deadFS from 'fs'; + import anotherDeadFS from 'fs'; + import liveFS from 'fs'; + + export var localVarToRemove = deadFS.readFileSync("/etc/passwd"); + export var localVarToReplace = 1; + + var getStaticProps = function () { + deadFS.readFileSync("/etc/passwd") + }; + + export {getStaticProps} + + export function baz() { + liveFS.readFileSync("/etc/passwd"); + require("bar"); + } + `); + expect(output.includes("loader")).toBe(false); + expect(output.includes("react")).toBe(false); + expect(output.includes("deadFS")).toBe(false); + expect(output.includes("default")).toBe(false); + expect(output.includes("anotherDeadFS")).toBe(false); + expect(output.includes("liveFS")).toBe(true); + expect(output.includes("__N_SSG")).toBe(true); + expect(output.includes("localVarToReplace")).toBe(true); + expect(output.includes("localVarToRemove")).toBe(false); + }); + }); + + const transpiler = new Bun.Transpiler({ + loader: "tsx", + define: { + "process.env.NODE_ENV": JSON.stringify("development"), + user_undefined: "undefined", + }, + macro: { + react: { + bacon: `${import.meta.dir}/macro-check.js`, + }, + }, + platform: "browser", + }); + const bunTranspiler = new Bun.Transpiler({ + loader: "tsx", + define: { + "process.env.NODE_ENV": JSON.stringify("development"), + user_undefined: "undefined", + }, + platform: "bun", + macro: { + inline: { + whatDidIPass: `${import.meta.dir}/inline.macro.js`, + }, + react: { + bacon: `${import.meta.dir}/macro-check.js`, + }, + }, + }); + + const code = `import { useParams } from "remix"; + import type { LoaderFunction, ActionFunction } from "remix"; + import { type xx } from 'mod'; + import { type xx as yy } from 'mod'; + import { type 'xx' as yy } from 'mod'; + import { type if as yy } from 'mod'; + import React, { type ReactNode, Component as Romponent, Component } from 'react'; + + + export const loader: LoaderFunction = async ({ + params + }) => { + console.log(params.postId); + }; + + export const action: ActionFunction = async ({ + params + }) => { + console.log(params.postId); + }; + + export default function PostRoute() { + const params = useParams(); + console.log(params.postId); + } + + + + + + `; + + it("JSX", () => { + var bun = new Bun.Transpiler({ + loader: "jsx", + define: { + "process.env.NODE_ENV": JSON.stringify("development"), + }, + }); + expect(bun.transformSync("export var foo = <div foo />")).toBe( + `export var foo = jsx("div", { + foo: true +}, undefined, false, undefined, this); +` + ); + expect(bun.transformSync("export var foo = <div foo={foo} />")).toBe( + `export var foo = jsx("div", { + foo +}, undefined, false, undefined, this); +` + ); + expect(bun.transformSync("export var foo = <div {...foo} />")).toBe( + `export var foo = jsx("div", { + ...foo +}, undefined, false, undefined, this); +` + ); + + expect(bun.transformSync("export var hi = <div {foo} />")).toBe( + `export var hi = jsx("div", { + foo +}, undefined, false, undefined, this); +` + ); + expect(bun.transformSync("export var hi = <div {foo.bar.baz} />")).toBe( + `export var hi = jsx("div", { + baz: foo.bar.baz +}, undefined, false, undefined, this); +` + ); + expect(bun.transformSync("export var hi = <div {foo?.bar?.baz} />")).toBe( + `export var hi = jsx("div", { + baz: foo?.bar?.baz +}, undefined, false, undefined, this); +` + ); + expect( + bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />") + ).toBe( + `export var hi = jsx("div", { + baz: foo["baz"].bar?.baz +}, undefined, false, undefined, this); +` + ); + + // cursed + expect( + bun.transformSync( + "export var hi = <div {foo[{name: () => true}.name].hi} />" + ) + ).toBe( + `export var hi = jsx("div", { + hi: foo[{ name: () => true }.name].hi +}, undefined, false, undefined, this); +` + ); + expect( + bun.transformSync("export var hi = <Foo {process.env.NODE_ENV} />") + ).toBe( + `export var hi = jsx(Foo, { + NODE_ENV: "development" +}, undefined, false, undefined, this); +` + ); + + expect( + bun.transformSync("export var hi = <div {foo['baz'].bar?.baz} />") + ).toBe( + `export var hi = jsx("div", { + baz: foo["baz"].bar?.baz +}, undefined, false, undefined, this); +` + ); + try { + bun.transformSync("export var hi = <div {foo}={foo}= />"); + throw new Error("Expected error"); + } catch (e) { + expect(e.errors[0].message.includes('Expected ">"')).toBe(true); + } + + expect( + bun.transformSync("export var hi = <div {Foo}><Foo></Foo></div>") + ).toBe( + `export var hi = jsx("div", { + Foo, + children: jsx(Foo, {}, undefined, false, undefined, this) +}, undefined, false, undefined, this); +` + ); + expect( + bun.transformSync("export var hi = <div {Foo}><Foo></Foo></div>") + ).toBe( + `export var hi = jsx("div", { + Foo, + children: jsx(Foo, {}, undefined, false, undefined, this) +}, undefined, false, undefined, this); +` + ); + + expect(bun.transformSync("export var hi = <div>{123}}</div>").trim()).toBe( + `export var hi = jsx("div", { + children: [ + 123, + "}" + ] +}, undefined, true, undefined, this); + `.trim() + ); + }); + + describe("inline JSX", () => { + const inliner = new Bun.Transpiler({ + loader: "tsx", + define: { + "process.env.NODE_ENV": JSON.stringify("production"), + user_undefined: "undefined", + }, + platform: "bun", + jsxOptimizationInline: true, + treeShaking: false, + }); + + it("inlines static JSX into object literals", () => { + expect( + inliner + .transformSync( + ` +export var hi = <div>{123}</div> +export var hiWithKey = <div key="hey">{123}</div> +export var hiWithRef = <div ref={foo}>{123}</div> + +export var ComponentThatChecksDefaultProps = <Hello></Hello> +export var ComponentThatChecksDefaultPropsAndHasChildren = <Hello>my child</Hello> +export var ComponentThatHasSpreadCausesDeopt = <Hello {...spread} /> + +`.trim() + ) + .trim() + ).toBe( + `var $$typeof = Symbol.for("react.element"); +export var hi = { + $$typeof, + type: "div", + key: null, + ref: null, + props: { + children: 123 + }, + _owner: null +}; +export var hiWithKey = { + $$typeof, + type: "div", + key: "hey", + ref: null, + props: { + children: 123 + }, + _owner: null +}; +export var hiWithRef = jsx("div", { + ref: foo, + children: 123 +}); +export var ComponentThatChecksDefaultProps = { + $$typeof, + type: Hello, + key: null, + ref: null, + props: Hello.defaultProps || {}, + _owner: null +}; +export var ComponentThatChecksDefaultPropsAndHasChildren = { + $$typeof, + type: Hello, + key: null, + ref: null, + props: __merge({ + children: "my child" + }, Hello.defaultProps), + _owner: null +}; +export var ComponentThatHasSpreadCausesDeopt = jsx(Hello, { + ...spread +}); +`.trim() + ); + }); + }); + + it("require with a dynamic non-string expression", () => { + var nodeTranspiler = new Bun.Transpiler({ platform: "node" }); + expect(nodeTranspiler.transformSync("require('hi' + bar)")).toBe( + 'require("hi" + bar);\n' + ); + }); + + it("CommonJS", () => { + var nodeTranspiler = new Bun.Transpiler({ platform: "node" }); + expect(nodeTranspiler.transformSync("module.require('hi' + 123)")).toBe( + 'require("hi" + 123);\n' + ); + + expect( + nodeTranspiler.transformSync("module.require(1 ? 'foo' : 'bar')") + ).toBe('require("foo");\n'); + expect(nodeTranspiler.transformSync("require(1 ? 'foo' : 'bar')")).toBe( + 'require("foo");\n' + ); + + expect( + nodeTranspiler.transformSync("module.require(unknown ? 'foo' : 'bar')") + ).toBe('unknown ? require("foo") : require("bar");\n'); + }); + + describe("regressions", () => { + it("unexpected super", () => { + const input = ` + 'use strict'; + + const ErrorReportingMixinBase = require('./mixin-base'); + const PositionTrackingPreprocessorMixin = require('../position-tracking/preprocessor-mixin'); + const Mixin = require('../../utils/mixin'); + + class ErrorReportingPreprocessorMixin extends ErrorReportingMixinBase { + constructor(preprocessor, opts) { + super(preprocessor, opts); + + this.posTracker = Mixin.install(preprocessor, PositionTrackingPreprocessorMixin); + this.lastErrOffset = -1; + } + + _reportError(code) { + //NOTE: avoid reporting error twice on advance/retreat + if (this.lastErrOffset !== this.posTracker.offset) { + this.lastErrOffset = this.posTracker.offset; + super._reportError(code); + } + } + } + + module.exports = ErrorReportingPreprocessorMixin; + + +`; + expect(transpiler.transformSync(input, "js").length > 0).toBe(true); + }); + }); + + describe("scanImports", () => { + it("reports import paths, excluding types", () => { + const imports = transpiler.scanImports(code, "tsx"); + expect(imports.filter(({ path }) => path === "remix")).toHaveLength(1); + expect(imports.filter(({ path }) => path === "mod")).toHaveLength(0); + expect(imports.filter(({ path }) => path === "react")).toHaveLength(1); + expect(imports).toHaveLength(2); + }); + }); + + const parsed = ( + code, + trim = true, + autoExport = false, + transpiler_ = transpiler + ) => { + if (autoExport) { + code = "export default (" + code + ")"; + } + + var out = transpiler_.transformSync(code, "js"); + if (autoExport && out.startsWith("export default ")) { + out = out.substring("export default ".length); + } + + if (trim) { + out = out.trim(); + + if (out.endsWith(";")) { + out = out.substring(0, out.length - 1); + } + + return out.trim(); + } + + return out; + }; + + const expectPrinted = (code, out) => { + expect(parsed(code, true, true)).toBe(out); + }; + + const expectPrinted_ = (code, out) => { + expect(parsed(code, !out.endsWith(";\n"), false)).toBe(out); + }; + + const expectBunPrinted_ = (code, out) => { + expect(parsed(code, !out.endsWith(";\n"), false, bunTranspiler)).toBe(out); + }; + + const expectParseError = (code, message) => { + try { + parsed(code, false, false); + } catch (er) { + var err = er; + if (er instanceof AggregateError) { + err = err.errors[0]; + } + + expect(er.message).toBe(message); + + return; + } + + throw new Error("Expected parse error for code\n\t" + code); + }; + const ts = { + parsed: (code, trim = true, autoExport = false) => { + if (autoExport) { + code = "export default (" + code + ")"; + } + + var out = transpiler.transformSync(code, "ts"); + if (autoExport && out.startsWith("export default ")) { + out = out.substring("export default ".length); + } + + if (trim) { + out = out.trim(); + + if (out.endsWith(";")) { + out = out.substring(0, out.length - 1); + } + + return out.trim(); + } + + return out; + }, + + expectPrinted: (code, out) => { + expect(ts.parsed(code, true, true)).toBe(out); + }, + + expectPrinted_: (code, out) => { + expect(ts.parsed(code, !out.endsWith(";\n"), false)).toBe(out); + }, + + expectParseError: (code, message) => { + try { + ts.parsed(code, false, false); + } catch (er) { + var err = er; + if (er instanceof AggregateError) { + err = err.errors[0]; + } + + expect(er.message).toBe(message); + + return; + } + + throw new Error("Expected parse error for code\n\t" + code); + }, + }; + + describe("parser", () => { + it("arrays", () => { + expectPrinted("[]", "[]"); + expectPrinted("[,]", "[,]"); + expectPrinted("[1]", "[1]"); + expectPrinted("[1,]", "[1]"); + expectPrinted("[,1]", "[, 1]"); + expectPrinted("[1,2]", "[1, 2]"); + expectPrinted("[,1,2]", "[, 1, 2]"); + expectPrinted("[1,,2]", "[1, , 2]"); + expectPrinted("[1,2,]", "[1, 2]"); + expectPrinted("[1,2,,]", "[1, 2, ,]"); + }); + + it("exponentiation", () => { + expectPrinted("(delete x) ** 0", "(delete x) ** 0"); + expectPrinted("(delete x.prop) ** 0", "(delete x.prop) ** 0"); + expectPrinted("(delete x[0]) ** 0", "(delete x[0]) ** 0"); + + expectPrinted("(delete x?.prop) ** 0", "(delete x?.prop) ** 0"); + + expectPrinted("(void x) ** 0", "(void x) ** 0"); + expectPrinted("(typeof x) ** 0", "(typeof x) ** 0"); + expectPrinted("(+x) ** 0", "(+x) ** 0"); + expectPrinted("(-x) ** 0", "(-x) ** 0"); + expectPrinted("(~x) ** 0", "(~x) ** 0"); + expectPrinted("(!x) ** 0", "(!x) ** 0"); + expectPrinted("(await x) ** 0", "(await x) ** 0"); + expectPrinted("(await -x) ** 0", "(await -x) ** 0"); + + expectPrinted("--x ** 2", "--x ** 2"); + expectPrinted("++x ** 2", "++x ** 2"); + expectPrinted("x-- ** 2", "x-- ** 2"); + expectPrinted("x++ ** 2", "x++ ** 2"); + + expectPrinted("(-x) ** 2", "(-x) ** 2"); + expectPrinted("(+x) ** 2", "(+x) ** 2"); + expectPrinted("(~x) ** 2", "(~x) ** 2"); + expectPrinted("(!x) ** 2", "(!x) ** 2"); + expectPrinted("(-1) ** 2", "(-1) ** 2"); + expectPrinted("(+1) ** 2", "1 ** 2"); + expectPrinted("(~1) ** 2", "(~1) ** 2"); + expectPrinted("(!1) ** 2", "false ** 2"); + expectPrinted("(void x) ** 2", "(void x) ** 2"); + expectPrinted("(delete x) ** 2", "(delete x) ** 2"); + expectPrinted("(typeof x) ** 2", "(typeof x) ** 2"); + expectPrinted("undefined ** 2", "undefined ** 2"); + + expectParseError("-x ** 2", "Unexpected **"); + expectParseError("+x ** 2", "Unexpected **"); + expectParseError("~x ** 2", "Unexpected **"); + expectParseError("!x ** 2", "Unexpected **"); + expectParseError("void x ** 2", "Unexpected **"); + expectParseError("delete x ** 2", "Unexpected **"); + expectParseError("typeof x ** 2", "Unexpected **"); + + expectParseError("-x.y() ** 2", "Unexpected **"); + expectParseError("+x.y() ** 2", "Unexpected **"); + expectParseError("~x.y() ** 2", "Unexpected **"); + expectParseError("!x.y() ** 2", "Unexpected **"); + expectParseError("void x.y() ** 2", "Unexpected **"); + expectParseError("delete x.y() ** 2", "Unexpected **"); + expectParseError("typeof x.y() ** 2", "Unexpected **"); + + expectParseError("delete x ** 0", "Unexpected **"); + expectParseError("delete x.prop ** 0", "Unexpected **"); + expectParseError("delete x[0] ** 0", "Unexpected **"); + expectParseError("delete x?.prop ** 0", "Unexpected **"); + expectParseError("void x ** 0", "Unexpected **"); + expectParseError("typeof x ** 0", "Unexpected **"); + expectParseError("+x ** 0", "Unexpected **"); + expectParseError("-x ** 0", "Unexpected **"); + expectParseError("~x ** 0", "Unexpected **"); + expectParseError("!x ** 0", "Unexpected **"); + expectParseError("await x ** 0", "Unexpected **"); + expectParseError("await -x ** 0", "Unexpected **"); + }); + + it("await", () => { + expectPrinted("await x", "await x"); + expectPrinted("await +x", "await +x"); + expectPrinted("await -x", "await -x"); + expectPrinted("await ~x", "await ~x"); + expectPrinted("await !x", "await !x"); + expectPrinted("await --x", "await --x"); + expectPrinted("await ++x", "await ++x"); + expectPrinted("await x--", "await x--"); + expectPrinted("await x++", "await x++"); + expectPrinted("await void x", "await void x"); + expectPrinted("await typeof x", "await typeof x"); + expectPrinted("await (x * y)", "await (x * y)"); + expectPrinted("await (x ** y)", "await (x ** y)"); + + expectPrinted_( + "async function f() { await delete x }", + "async function f() {\n await delete x;\n}" + ); + + // expectParseError( + // "await delete x", + // "Delete of a bare identifier cannot be used in an ECMAScript module" + // ); + }); + + it("import assert", () => { + expectPrinted_( + `import json from "./foo.json" assert { type: "json" };`, + `import json from "./foo.json"` + ); + expectPrinted_( + `import json from "./foo.json";`, + `import json from "./foo.json"` + ); + expectPrinted_( + `import("./foo.json", { type: "json" });`, + `import("./foo.json")` + ); + }); + + it("import with unicode escape", () => { + expectPrinted_( + `import { name } from 'mod\\u1011';`, + `import {name} from "mod\\u1011"` + ); + }); + + it("fold string addition", () => { + expectPrinted_( + `export const foo = "a" + "b";`, + `export const foo = "ab"` + ); + expectPrinted_( + `export const foo = "F" + "0" + "F" + "0123456789" + "ABCDEF" + "0123456789ABCDEFF0123456789ABCDEF00" + "b";`, + `export const foo = "F0F0123456789ABCDEF0123456789ABCDEFF0123456789ABCDEF00b"` + ); + expectPrinted_( + `export const foo = "a" + 1 + "b";`, + `export const foo = "a" + 1 + "b"` + ); + expectPrinted_( + `export const foo = "a" + "b" + 1 + "b";`, + `export const foo = "ab" + 1 + "b"` + ); + expectPrinted_( + `export const foo = "a" + "b" + 1 + "b" + "c";`, + `export const foo = "ab" + 1 + "bc"` + ); + }); + + it("numeric constants", () => { + expectBunPrinted_("export const foo = 1 + 2", "export const foo = 3"); + expectBunPrinted_("export const foo = 1 - 2", "export const foo = -1"); + expectBunPrinted_("export const foo = 1 * 2", "export const foo = 2"); + }); + + it("pass objects to macros", () => { + var object = { + helloooooooo: { + message: [12345], + }, + }; + + const output = bunTranspiler.transformSync( + ` + import {whatDidIPass} from 'inline'; + + export function foo() { + return whatDidIPass(); + } + `, + object + ); + expect(output).toBe(`export function foo() { + return { + helloooooooo: { + message: [ + 12345 + ] + } + }; +} +`); + }); + + it("rewrite string to length", () => { + expectPrinted_( + `export const foo = "a".length + "b".length;`, + `export const foo = 1 + 1` + ); + expectBunPrinted_( + `export const foo = "a".length + "b".length;`, + `export const foo = 2` + ); + }); + + describe("Bun.js", () => { + it("require -> import.meta.require", () => { + expectBunPrinted_( + `export const foo = require('bar.node')`, + `export const foo = import.meta.require("bar.node")` + ); + }); + + it("require.resolve -> import.meta.resolveSync", () => { + expectBunPrinted_( + `export const foo = require.resolve('bar.node')`, + `export const foo = import.meta.resolveSync("bar.node")` + ); + }); + + it('require.resolve(path, {paths: ["blah"]}) -> import.meta.resolveSync', () => { + expectBunPrinted_( + `export const foo = require.resolve('bar.node', {paths: ["blah"]})`, + `export const foo = import.meta.resolveSync("bar.node", { paths: ["blah"] })` + ); + }); + }); + + describe("Browsers", () => { + it('require.resolve("my-module") -> "/resolved/my-module"', () => { + // the module resolver & linker doesn't run with Bun.Transpiler + // so in this test, it becomes the same path string + expectPrinted_( + `export const foo = require.resolve('my-module')`, + `export const foo = "my-module"` + ); + }); + }); + + it("define", () => { + expectPrinted_( + `export default typeof user_undefined === 'undefined';`, + `export default true` + ); + expectPrinted_( + `export default typeof user_undefined !== 'undefined';`, + `export default false` + ); + + expectPrinted_( + `export default typeof user_undefined !== 'undefined';`, + `export default false` + ); + expectPrinted_(`export default !user_undefined;`, `export default true`); + }); + + it("decls", () => { + // expectParseError("var x = 0", ""); + // expectParseError("let x = 0", ""); + // expectParseError("const x = 0", ""); + // expectParseError("for (var x = 0;;) ;", ""); + // expectParseError("for (let x = 0;;) ;", ""); + // expectParseError("for (const x = 0;;) ;", ""); + + // expectParseError("for (var x in y) ;", ""); + // expectParseError("for (let x in y) ;", ""); + // expectParseError("for (const x in y) ;", ""); + // expectParseError("for (var x of y) ;", ""); + // expectParseError("for (let x of y) ;", ""); + // expectParseError("for (const x of y) ;", ""); + + // expectParseError("var x", ""); + // expectParseError("let x", ""); + expectParseError("const x", 'The constant "x" must be initialized'); + expectParseError("const {}", "This constant must be initialized"); + expectParseError("const []", "This constant must be initialized"); + // expectParseError("for (var x;;) ;", ""); + // expectParseError("for (let x;;) ;", ""); + expectParseError( + "for (const x;;) ;", + 'The constant "x" must be initialized' + ); + expectParseError( + "for (const {};;) ;", + "This constant must be initialized" + ); + expectParseError( + "for (const [];;) ;", + "This constant must be initialized" + ); + + // Make sure bindings are visited during parsing + expectPrinted_("var {[x]: y} = {}", "var { [x]: y } = {}"); + expectPrinted_("var {...x} = {}", "var { ...x } = {}"); + + // Test destructuring patterns + expectPrinted_("var [...x] = []", "var [...x] = []"); + expectPrinted_("var {...x} = {}", "var { ...x } = {}"); + + expectPrinted_( + "export var foo = ([...x] = []) => {}", + "export var foo = ([...x] = []) => {\n}" + ); + + expectPrinted_( + "export var foo = ({...x} = {}) => {}", + "export var foo = ({ ...x } = {}) => {\n}" + ); + + expectParseError("var [...x,] = []", 'Unexpected "," after rest pattern'); + expectParseError("var {...x,} = {}", 'Unexpected "," after rest pattern'); + expectParseError( + "export default function() { return ([...x,] = []) => {} }", + "Unexpected trailing comma after rest element" + ); + expectParseError( + "({...x,} = {}) => {}", + "Unexpected trailing comma after rest element" + ); + + expectPrinted_("[b, ...c] = d", "[b, ...c] = d"); + expectPrinted_("([b, ...c] = d)", "[b, ...c] = d"); + expectPrinted_("({b, ...c} = d)", "({ b, ...c } = d)"); + expectPrinted_("({a = b} = c)", "({ a = b } = c)"); + expectPrinted_("({a: b = c} = d)", "({ a: b = c } = d)"); + expectPrinted_("({a: b.c} = d)", "({ a: b.c } = d)"); + expectPrinted_("[a = {}] = b", "[a = {}] = b"); + expectPrinted_("[[...a, b].x] = c", "[[...a, b].x] = c"); + expectPrinted_("[{...a, b}.x] = c", "[{ ...a, b }.x] = c"); + expectPrinted_("({x: [...a, b].x} = c)", "({ x: [...a, b].x } = c)"); + expectPrinted_("({x: {...a, b}.x} = c)", "({ x: { ...a, b }.x } = c)"); + expectPrinted_("[x = [...a, b]] = c", "[x = [...a, b]] = c"); + expectPrinted_("[x = {...a, b}] = c", "[x = { ...a, b }] = c"); + expectPrinted_("({x = [...a, b]} = c)", "({ x = [...a, b] } = c)"); + expectPrinted_("({x = {...a, b}} = c)", "({ x = { ...a, b } } = c)"); + + expectPrinted_("(x = y)", "x = y"); + expectPrinted_("([] = [])", "[] = []"); + expectPrinted_("({} = {})", "({} = {})"); + expectPrinted_("([[]] = [[]])", "[[]] = [[]]"); + expectPrinted_("({x: {}} = {x: {}})", "({ x: {} } = { x: {} })"); + expectPrinted_("(x) = y", "x = y"); + expectParseError("([]) = []", "Invalid assignment target"); + expectParseError("({}) = {}", "Invalid assignment target"); + expectParseError("[([])] = [[]]", "Invalid assignment target"); + expectParseError("({x: ({})} = {x: {}})", "Invalid assignment target"); + expectParseError( + "(([]) = []) => {}", + "Unexpected parentheses in binding pattern" + ); + expectParseError( + "(({}) = {}) => {}", + "Unexpected parentheses in binding pattern" + ); + expectParseError("function f(([]) = []) {}", "Parse error"); + expectParseError( + "function f(({}) = {}) {}", + "Parse error" + // 'Expected identifier but found "("\n' + ); + + expectPrinted_("for (x in y) ;", "for (x in y) {\n}"); + expectPrinted_("for ([] in y) ;", "for ([] in y) {\n}"); + expectPrinted_("for ({} in y) ;", "for ({} in y) {\n}"); + expectPrinted_("for ((x) in y) ;", "for (x in y) {\n}"); + expectParseError("for (([]) in y) ;", "Invalid assignment target"); + expectParseError("for (({}) in y) ;", "Invalid assignment target"); + + expectPrinted_("for (x of y) ;", "for (x of y) {\n}"); + expectPrinted_("for ([] of y) ;", "for ([] of y) {\n}"); + expectPrinted_("for ({} of y) ;", "for ({} of y) {\n}"); + expectPrinted_("for ((x) of y) ;", "for (x of y) {\n}"); + expectParseError("for (([]) of y) ;", "Invalid assignment target"); + expectParseError("for (({}) of y) ;", "Invalid assignment target"); + + expectParseError("[[...a, b]] = c", 'Unexpected "," after rest pattern'); + expectParseError("[{...a, b}] = c", 'Unexpected "," after rest pattern'); + expectParseError( + "({x: [...a, b]} = c)", + 'Unexpected "," after rest pattern' + ); + expectParseError( + "({x: {...a, b}} = c)", + 'Unexpected "," after rest pattern' + ); + expectParseError("[b, ...c,] = d", 'Unexpected "," after rest pattern'); + expectParseError("([b, ...c,] = d)", 'Unexpected "," after rest pattern'); + expectParseError("({b, ...c,} = d)", 'Unexpected "," after rest pattern'); + expectParseError("({a = b})", 'Unexpected "="'); + expectParseError("({x = {a = b}} = c)", 'Unexpected "="'); + expectParseError("[a = {b = c}] = d", 'Unexpected "="'); + + expectPrinted_( + "for ([{a = {}}] in b) {}", + "for ([{ a = {} }] in b) {\n}" + ); + expectPrinted_( + "for ([{a = {}}] of b) {}", + "for ([{ a = {} }] of b) {\n}" + ); + expectPrinted_("for ({a = {}} in b) {}", "for ({ a = {} } in b) {\n}"); + expectPrinted_("for ({a = {}} of b) {}", "for ({ a = {} } of b) {\n}"); + + expectParseError("({a = {}} in b)", 'Unexpected "="'); + expectParseError("[{a = {}}]\nof()", 'Unexpected "="'); + expectParseError( + "for ([...a, b] in c) {}", + 'Unexpected "," after rest pattern' + ); + expectParseError( + "for ([...a, b] of c) {}", + 'Unexpected "," after rest pattern' + ); + }); + + it("regexp", () => { + expectPrinted("/x/g", "/x/g"); + expectPrinted("/x/i", "/x/i"); + expectPrinted("/x/m", "/x/m"); + expectPrinted("/x/s", "/x/s"); + expectPrinted("/x/u", "/x/u"); + expectPrinted("/x/y", "/x/y"); + expectPrinted("/gimme/g", "/gimme/g"); + expectPrinted("/gimgim/g", "/gimgim/g"); + + expectParseError( + "/x/msuygig", + 'Duplicate flag "g" in regular expression' + ); + }); + + it("identifier escapes", () => { + expectPrinted_("var _\u0076\u0061\u0072", "var _var"); + expectParseError( + "var \u0076\u0061\u0072", + 'Expected identifier but found "\u0076\u0061\u0072"' + ); + expectParseError( + "\\u0076\\u0061\\u0072 foo", + "Unexpected \\u0076\\u0061\\u0072" + ); + + expectPrinted_("foo._\u0076\u0061\u0072", "foo._var"); + expectPrinted_("foo.\u0076\u0061\u0072", "foo.var"); + + // expectParseError("\u200Ca", 'Unexpected "\\u200c"'); + // expectParseError("\u200Da", 'Unexpected "\\u200d"'); + }); + }); + + it("private identifiers", () => { + expectParseError("#foo", "Unexpected #foo"); + expectParseError("#foo in this", "Unexpected #foo"); + expectParseError("this.#foo", 'Expected identifier but found "#foo"'); + expectParseError("this?.#foo", 'Expected identifier but found "#foo"'); + expectParseError("({ #foo: 1 })", 'Expected identifier but found "#foo"'); + expectParseError( + "class Foo { x = { #foo: 1 } }", + 'Expected identifier but found "#foo"' + ); + expectParseError("class Foo { x = #foo }", 'Expected "in" but found "}"'); + expectParseError( + "class Foo { #foo; foo() { delete this.#foo } }", + 'Deleting the private name "#foo" is forbidden' + ); + expectParseError( + "class Foo { #foo; foo() { delete this?.#foo } }", + 'Deleting the private name "#foo" is forbidden' + ); + expectParseError( + "class Foo extends Bar { #foo; foo() { super.#foo } }", + 'Expected identifier but found "#foo"' + ); + expectParseError( + "class Foo { #foo = () => { for (#foo in this) ; } }", + "Unexpected #foo" + ); + expectParseError( + "class Foo { #foo = () => { for (x = #foo in this) ; } }", + "Unexpected #foo" + ); + expectPrinted_("class Foo { #foo }", "class Foo {\n #foo;\n}"); + expectPrinted_("class Foo { #foo = 1 }", "class Foo {\n #foo = 1;\n}"); + expectPrinted_( + "class Foo { #foo = #foo in this }", + "class Foo {\n #foo = #foo in this;\n}" + ); + expectPrinted_( + "class Foo { #foo = #foo in (#bar in this); #bar }", + "class Foo {\n #foo = #foo in (#bar in this);\n #bar;\n}" + ); + expectPrinted_( + "class Foo { #foo() {} }", + "class Foo {\n #foo() {\n }\n}" + ); + expectPrinted_( + "class Foo { get #foo() {} }", + "class Foo {\n get #foo() {\n }\n}" + ); + expectPrinted_( + "class Foo { set #foo(x) {} }", + "class Foo {\n set #foo(x) {\n }\n}" + ); + expectPrinted_( + "class Foo { static #foo }", + "class Foo {\n static #foo;\n}" + ); + expectPrinted_( + "class Foo { static #foo = 1 }", + "class Foo {\n static #foo = 1;\n}" + ); + expectPrinted_( + "class Foo { static #foo() {} }", + "class Foo {\n static #foo() {\n }\n}" + ); + expectPrinted_( + "class Foo { static get #foo() {} }", + "class Foo {\n static get #foo() {\n }\n}" + ); + expectPrinted_( + "class Foo { static set #foo(x) {} }", + "class Foo {\n static set #foo(x) {\n }\n}" + ); + + expectParseError( + "class Foo { #foo = #foo in #bar in this; #bar }", + "Unexpected #bar" + ); + + expectParseError( + "class Foo { #constructor }", + 'Invalid field name "#constructor"' + ); + expectParseError( + "class Foo { #constructor() {} }", + 'Invalid method name "#constructor"' + ); + expectParseError( + "class Foo { static #constructor }", + 'Invalid field name "#constructor"' + ); + expectParseError( + "class Foo { static #constructor() {} }", + 'Invalid method name "#constructor"' + ); + expectParseError( + "class Foo { #\\u0063onstructor }", + 'Invalid field name "#constructor"' + ); + expectParseError( + "class Foo { #\\u0063onstructor() {} }", + 'Invalid method name "#constructor"' + ); + expectParseError( + "class Foo { static #\\u0063onstructor }", + 'Invalid field name "#constructor"' + ); + expectParseError( + "class Foo { static #\\u0063onstructor() {} }", + 'Invalid method name "#constructor"' + ); + const errorText = '"#foo" has already been declared'; + expectParseError("class Foo { #foo; #foo }", errorText); + expectParseError("class Foo { #foo; static #foo }", errorText); + expectParseError("class Foo { static #foo; #foo }", errorText); + expectParseError("class Foo { #foo; #foo() {} }", errorText); + expectParseError("class Foo { #foo; get #foo() {} }", errorText); + expectParseError("class Foo { #foo; set #foo(x) {} }", errorText); + expectParseError("class Foo { #foo() {} #foo }", errorText); + expectParseError("class Foo { get #foo() {} #foo }", errorText); + expectParseError("class Foo { set #foo(x) {} #foo }", errorText); + expectParseError("class Foo { get #foo() {} get #foo() {} }", errorText); + expectParseError("class Foo { set #foo(x) {} set #foo(x) {} }", errorText); + expectParseError( + "class Foo { get #foo() {} set #foo(x) {} #foo }", + errorText + ); + expectParseError( + "class Foo { set #foo(x) {} get #foo() {} #foo }", + errorText + ); + + expectPrinted_( + "class Foo { get #foo() {} set #foo(x) { this.#foo } }", + "class Foo {\n get #foo() {\n }\n set #foo(x) {\n this.#foo;\n }\n}" + ); + expectPrinted_( + "class Foo { set #foo(x) { this.#foo } get #foo() {} }", + "class Foo {\n set #foo(x) {\n this.#foo;\n }\n get #foo() {\n }\n}" + ); + expectPrinted_( + "class Foo { #foo } class Bar { #foo }", + "class Foo {\n #foo;\n}\n\nclass Bar {\n #foo;\n}" + ); + expectPrinted_( + "class Foo { foo = this.#foo; #foo }", + "class Foo {\n foo = this.#foo;\n #foo;\n}" + ); + expectPrinted_( + "class Foo { foo = this?.#foo; #foo }", + "class Foo {\n foo = this?.#foo;\n #foo;\n}" + ); + expectParseError( + "class Foo { #foo } class Bar { foo = this.#foo }", + 'Private name "#foo" must be declared in an enclosing class' + ); + expectParseError( + "class Foo { #foo } class Bar { foo = this?.#foo }", + 'Private name "#foo" must be declared in an enclosing class' + ); + expectParseError( + "class Foo { #foo } class Bar { foo = #foo in this }", + 'Private name "#foo" must be declared in an enclosing class' + ); + + expectPrinted_( + `class Foo { + #if + #im() { return this.#im(this.#if) } + static #sf + static #sm() { return this.#sm(this.#sf) } + foo() { + return class { + #inner() { + return [this.#im, this?.#inner, this?.x.#if] + } + } + } +} +`, + `class Foo { + #if; + #im() { + return this.#im(this.#if); + } + static #sf; + static #sm() { + return this.#sm(this.#sf); + } + foo() { + return class { + #inner() { + return [this.#im, this?.#inner, this?.x.#if]; + } + }; + } +}` + ); + }); + + it("type only exports", () => { + let { expectPrinted_, expectParseError } = ts; + expectPrinted_("export type {foo, bar as baz} from 'bar'", ""); + expectPrinted_("export type {foo, bar as baz}", ""); + expectPrinted_("export type {foo} from 'bar'; x", "x"); + expectPrinted_("export type {foo} from 'bar'\nx", "x"); + expectPrinted_("export type {default} from 'bar'", ""); + expectPrinted_( + "export { type } from 'mod'; type", + 'export { type } from "mod";\ntype' + ); + expectPrinted_( + "export { type, as } from 'mod'", + 'export { type, as } from "mod"' + ); + expectPrinted_( + "export { x, type foo } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { x, type as } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { x, type foo as bar } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { x, type foo as as } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { type as as } from 'mod'; as", + 'export { type as as } from "mod";\nas' + ); + expectPrinted_( + "export { type as foo } from 'mod'; foo", + 'export { type as foo } from "mod";\nfoo' + ); + expectPrinted_( + "export { type as type } from 'mod'; type", + 'export { type } from "mod";\ntype' + ); + expectPrinted_( + "export { x, type as as foo } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { x, type as as as } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { x, type type as as } from 'mod'; x", + 'export { x } from "mod";\nx' + ); + expectPrinted_( + "export { x, \\u0074ype y }; let x, y", + "export { x };\nlet x, y" + ); + expectPrinted_( + "export { x, \\u0074ype y } from 'mod'", + 'export { x } from "mod"' + ); + expectPrinted_( + "export { x, type if } from 'mod'", + 'export { x } from "mod"' + ); + expectPrinted_("export { x, type y as if }; let x", "export { x };\nlet x"); + expectPrinted_("export { type x };", ""); + }); + + it("delete + optional chain", () => { + expectPrinted_("delete foo.bar.baz", "delete foo.bar.baz"); + expectPrinted_("delete foo?.bar.baz", "delete foo?.bar.baz"); + expectPrinted_("delete foo?.bar?.baz", "delete foo?.bar?.baz"); + }); + + it("useDefineForConst TypeScript class initialization", () => { + var { expectPrinted_ } = ts; + expectPrinted_( + ` +class Foo { + constructor(public x: string = "hey") {} + bar: number; +} +`.trim(), + ` +class Foo { + x; + constructor(x = "hey") { + this.x = x; + } + bar; +} +`.trim() + ); + }); + + it("class static blocks", () => { + expectPrinted_( + "class Foo { static {} }", + "class Foo {\n static {\n }\n}" + ); + expectPrinted_( + "class Foo { static {} x = 1 }", + "class Foo {\n static {\n }\n x = 1;\n}" + ); + expectPrinted_( + "class Foo { static { this.foo() } }", + "class Foo {\n static {\n this.foo();\n }\n}" + ); + + expectParseError( + "class Foo { static { yield } }", + '"yield" is a reserved word and cannot be used in strict mode' + ); + expectParseError( + "class Foo { static { await } }", + 'The keyword "await" cannot be used here' + ); + expectParseError( + "class Foo { static { return } }", + "A return statement cannot be used here" + ); + expectParseError( + "class Foo { static { break } }", + 'Cannot use "break" here' + ); + expectParseError( + "class Foo { static { continue } }", + 'Cannot use "continue" here' + ); + expectParseError( + "x: { class Foo { static { break x } } }", + 'There is no containing label named "x"' + ); + expectParseError( + "x: { class Foo { static { continue x } } }", + 'There is no containing label named "x"' + ); + + expectParseError( + "class Foo { get #x() { this.#x = 1 } }", + 'Writing to getter-only property "#x" will throw' + ); + expectParseError( + "class Foo { get #x() { this.#x += 1 } }", + 'Writing to getter-only property "#x" will throw' + ); + expectParseError( + "class Foo { set #x(x) { this.#x } }", + 'Reading from setter-only property "#x" will throw' + ); + expectParseError( + "class Foo { set #x(x) { this.#x += 1 } }", + 'Reading from setter-only property "#x" will throw' + ); + + // Writing to method warnings + expectParseError( + "class Foo { #x() { this.#x = 1 } }", + 'Writing to read-only method "#x" will throw' + ); + expectParseError( + "class Foo { #x() { this.#x += 1 } }", + 'Writing to read-only method "#x" will throw' + ); + }); + + describe("simplification", () => { + it("unary operator", () => { + expectPrinted("a = !(b, c)", "a = (b , !c)"); + }); + + it("constant folding", () => { + expectPrinted("1 && 2", "2"); + expectPrinted("1 || 2", "1"); + expectPrinted("0 && 1", "0"); + expectPrinted("0 || 1", "1"); + + expectPrinted("null ?? 1", "1"); + expectPrinted("undefined ?? 1", "1"); + expectPrinted("0 ?? 1", "0"); + expectPrinted("false ?? 1", "false"); + expectPrinted('"" ?? 1', '""'); + + expectPrinted("typeof undefined", '"undefined"'); + expectPrinted("typeof null", '"object"'); + expectPrinted("typeof false", '"boolean"'); + expectPrinted("typeof true", '"boolean"'); + expectPrinted("typeof 123", '"number"'); + expectPrinted("typeof 123n", '"bigint"'); + expectPrinted("typeof 'abc'", '"string"'); + expectPrinted("typeof function() {}", '"function"'); + expectPrinted("typeof (() => {})", '"function"'); + expectPrinted("typeof {}", "typeof {}"); + expectPrinted("typeof []", "typeof []"); + + expectPrinted("undefined === undefined", "true"); + expectPrinted("undefined !== undefined", "false"); + expectPrinted("undefined == undefined", "true"); + expectPrinted("undefined != undefined", "false"); + + expectPrinted("null === null", "true"); + expectPrinted("null !== null", "false"); + expectPrinted("null == null", "true"); + expectPrinted("null != null", "false"); + + expectPrinted("undefined === null", "undefined === null"); + expectPrinted("undefined !== null", "undefined !== null"); + expectPrinted("undefined == null", "undefined == null"); + expectPrinted("undefined != null", "undefined != null"); + + expectPrinted("true === true", "true"); + expectPrinted("true === false", "false"); + expectPrinted("true !== true", "false"); + expectPrinted("true !== false", "true"); + expectPrinted("true == true", "true"); + expectPrinted("true == false", "false"); + expectPrinted("true != true", "false"); + expectPrinted("true != false", "true"); + + expectPrinted("1 === 1", "true"); + expectPrinted("1 === 2", "false"); + expectPrinted("1 === '1'", '1 === "1"'); + expectPrinted("1 == 1", "true"); + expectPrinted("1 == 2", "false"); + expectPrinted("1 == '1'", '1 == "1"'); + + expectPrinted("1 !== 1", "false"); + expectPrinted("1 !== 2", "true"); + expectPrinted("1 !== '1'", '1 !== "1"'); + expectPrinted("1 != 1", "false"); + expectPrinted("1 != 2", "true"); + expectPrinted("1 != '1'", '1 != "1"'); + + expectPrinted("'a' === '\\x61'", "true"); + expectPrinted("'a' === '\\x62'", "false"); + expectPrinted("'a' === 'abc'", "false"); + expectPrinted("'a' !== '\\x61'", "false"); + expectPrinted("'a' !== '\\x62'", "true"); + expectPrinted("'a' !== 'abc'", "true"); + expectPrinted("'a' == '\\x61'", "true"); + expectPrinted("'a' == '\\x62'", "false"); + expectPrinted("'a' == 'abc'", "false"); + expectPrinted("'a' != '\\x61'", "false"); + expectPrinted("'a' != '\\x62'", "true"); + expectPrinted("'a' != 'abc'", "true"); + + expectPrinted("'a' + 'b'", '"ab"'); + expectPrinted("'a' + 'bc'", '"abc"'); + expectPrinted("'ab' + 'c'", '"abc"'); + expectPrinted("x + 'a' + 'b'", 'x + "ab"'); + expectPrinted("x + 'a' + 'bc'", 'x + "abc"'); + expectPrinted("x + 'ab' + 'c'", 'x + "abc"'); + expectPrinted("'a' + 1", '"a" + 1'); + expectPrinted("x * 'a' + 'b'", 'x * "a" + "b"'); + + expectPrinted("'string' + `template`", `"stringtemplate"`); + + expectPrinted("`template` + 'string'", "`templatestring`"); + + // TODO: string template simplification + // expectPrinted("'string' + `a${foo}b`", "`stringa${foo}b`"); + // expectPrinted("'string' + tag`template`", '"string" + tag`template`;'); + // expectPrinted("`a${foo}b` + 'string'", "`a${foo}bstring`"); + // expectPrinted("tag`template` + 'string'", 'tag`template` + "string"'); + // expectPrinted("`template` + `a${foo}b`", "`templatea${foo}b`"); + // expectPrinted("`a${foo}b` + `template`", "`a${foo}btemplate`"); + // expectPrinted("`a${foo}b` + `x${bar}y`", "`a${foo}bx${bar}y`"); + // expectPrinted( + // "`a${i}${j}bb` + `xxx${bar}yyyy`", + // "`a${i}${j}bbxxx${bar}yyyy`" + // ); + // expectPrinted( + // "`a${foo}bb` + `xxx${i}${j}yyyy`", + // "`a${foo}bbxxx${i}${j}yyyy`" + // ); + // expectPrinted( + // "`template` + tag`template2`", + // "`template` + tag`template2`" + // ); + // expectPrinted( + // "tag`template` + `template2`", + // "tag`template` + `template2`" + // ); + + expectPrinted("123", "123"); + expectPrinted("123 .toString()", "123 .toString()"); + expectPrinted("-123", "-123"); + expectPrinted("(-123).toString()", "(-123).toString()"); + expectPrinted("-0", "-0"); + expectPrinted("(-0).toString()", "(-0).toString()"); + expectPrinted("-0 === 0", "true"); + + expectPrinted("NaN", "NaN"); + expectPrinted("NaN.toString()", "NaN.toString()"); + expectPrinted("NaN === NaN", "false"); + + expectPrinted("Infinity", "Infinity"); + expectPrinted("Infinity.toString()", "Infinity.toString()"); + expectPrinted("(-Infinity).toString()", "(-Infinity).toString()"); + expectPrinted("Infinity === Infinity", "true"); + expectPrinted("Infinity === -Infinity", "false"); + + expectPrinted("123n === 1_2_3n", "true"); + }); + describe("type coercions", () => { + const dead = ` + if ("") { + TEST_FAIL + } + + if (false) { + TEST_FAIL + } + + if (0) { + TEST_FAIL + } + + if (void 0) { + TEST_FAIL + } + + if (null) { + TEST_FAIL + } + + var should_be_true = typeof "" === "string" || false + var should_be_false = typeof "" !== "string" && TEST_FAIL; + var should_be_false_2 = typeof true === "string" && TEST_FAIL; + var should_be_false_3 = typeof false === "string" && TEST_FAIL; + var should_be_false_4 = typeof 123n === "string" && TEST_FAIL; + var should_be_false_5 = typeof function(){} === "string" && TEST_FAIL; + var should_be_kept = typeof globalThis.BACON === "string" && TEST_OK; + var should_be_kept_1 = typeof TEST_OK === "string"; + + var should_be_kept_2 = TEST_OK ?? true; + var should_be_kept_4 = { "TEST_OK": true } ?? TEST_FAIL; + var should_be_false_6 = false ?? TEST_FAIL; + var should_be_true_7 = true ?? TEST_FAIL; + `; + const out = transpiler.transformSync(dead); + + for (let line of out.split("\n")) { + it(line, () => { + if (line.includes("should_be_kept")) { + expect(line.includes("TEST_OK")).toBe(true); + } + + if (line.includes("should_be_false")) { + if (!line.includes("= false")) + throw new Error(`Expected false in "${line}"`); + expect(line.includes("= false")).toBe(true); + } + + if (line.includes("TEST_FAIL")) { + throw new Error(`"${line}"\n\tshould not contain TEST_FAIL`); + } + }); + } + }); + }); + + describe("scan", () => { + it("reports all export names", () => { + const { imports, exports } = transpiler.scan(code); + + expect(exports[0]).toBe("action"); + expect(exports[2]).toBe("loader"); + expect(exports[1]).toBe("default"); + expect(exports).toHaveLength(3); + + expect(imports.filter(({ path }) => path === "remix")).toHaveLength(1); + expect(imports.filter(({ path }) => path === "mod")).toHaveLength(0); + expect(imports.filter(({ path }) => path === "react")).toHaveLength(1); + expect(imports).toHaveLength(2); + }); + }); + + describe("transform", () => { + it("supports macros", async () => { + const out = await transpiler.transform(` + import {keepSecondArgument} from 'macro:${ + import.meta.dir + }/macro-check.js'; + + export default keepSecondArgument("Test failed", "Test passed"); + export function otherNamesStillWork() {} + `); + expect(out.includes("Test failed")).toBe(false); + expect(out.includes("Test passed")).toBe(true); + + // ensure both the import and the macro function call are removed + expect(out.includes("keepSecondArgument")).toBe(false); + expect(out.includes("otherNamesStillWork")).toBe(true); + }); + + it("sync supports macros", () => { + const out = transpiler.transformSync(` + import {keepSecondArgument} from 'macro:${ + import.meta.dir + }/macro-check.js'; + + export default keepSecondArgument("Test failed", "Test passed"); + export function otherNamesStillWork() { + + } + `); + expect(out.includes("Test failed")).toBe(false); + expect(out.includes("Test passed")).toBe(true); + + expect(out.includes("keepSecondArgument")).toBe(false); + expect(out.includes("otherNamesStillWork")).toBe(true); + }); + + const importLines = [ + "import {createElement, bacon} from 'react';", + "import {bacon, createElement} from 'react';", + ]; + describe("sync supports macros remap", () => { + for (let importLine of importLines) { + it(importLine, () => { + var thisCode = ` + ${importLine} + + export default bacon("Test failed", "Test passed"); + export function otherNamesStillWork() { + return createElement("div"); + } + + `; + var out = transpiler.transformSync(thisCode); + try { + expect(out.includes("Test failed")).toBe(false); + expect(out.includes("Test passed")).toBe(true); + + expect(out.includes("bacon")).toBe(false); + expect(out.includes("createElement")).toBe(true); + } catch (e) { + console.log("Failing code:\n\n" + out + "\n"); + throw e; + } + }); + } + }); + + it("macro remap removes import statement if its the only used one", () => { + const out = transpiler.transformSync(` + import {bacon} from 'react'; + + export default bacon("Test failed", "Test passed"); + `); + + expect(out.includes("Test failed")).toBe(false); + expect(out.includes("Test passed")).toBe(true); + + expect(out.includes("bacon")).toBe(false); + expect(out.includes("import")).toBe(false); + }); + + it("removes types", () => { + expect(code.includes("mod")).toBe(true); + expect(code.includes("xx")).toBe(true); + expect(code.includes("ActionFunction")).toBe(true); + expect(code.includes("LoaderFunction")).toBe(true); + expect(code.includes("ReactNode")).toBe(true); + expect(code.includes("React")).toBe(true); + expect(code.includes("Component")).toBe(true); + const out = transpiler.transformSync(code); + + expect(out.includes("ActionFunction")).toBe(false); + expect(out.includes("LoaderFunction")).toBe(false); + expect(out.includes("mod")).toBe(false); + expect(out.includes("xx")).toBe(false); + expect(out.includes("ReactNode")).toBe(false); + const { exports } = transpiler.scan(out); + exports.sort(); + + expect(exports[0]).toBe("action"); + expect(exports[2]).toBe("loader"); + expect(exports[1]).toBe("default"); + expect(exports).toHaveLength(3); + }); + }); +}); diff --git a/test/bun.js/tsconfig.json b/test/bun.js/tsconfig.json new file mode 100644 index 000000000..9a6c36e06 --- /dev/null +++ b/test/bun.js/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "noEmit": true, + "allowJs": true, + "typeRoots": ["../../types"], + "types": ["bun"], + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + "paths": { + "foo/bar": ["baz.js"] + } + } +} diff --git a/test/bun.js/unsafe.test.js b/test/bun.js/unsafe.test.js new file mode 100644 index 000000000..741dc0241 --- /dev/null +++ b/test/bun.js/unsafe.test.js @@ -0,0 +1,51 @@ +import { test, expect, it, describe } from "bun:test"; +import { gc } from "./gc"; + +it("arrayBufferToString u8", async () => { + var encoder = new TextEncoder(); + const bytes = encoder.encode("hello world"); + gc(true); + expect(Bun.unsafe.arrayBufferToString(bytes)).toBe("hello world"); + gc(true); + await new Promise((resolve) => setTimeout(resolve, 0)); + gc(true); +}); + +it("arrayBufferToString ArrayBuffer", async () => { + var encoder = new TextEncoder(); + var bytes = encoder.encode("hello world"); + gc(true); + const out = Bun.unsafe.arrayBufferToString(bytes.buffer); + expect(out).toBe("hello world"); + gc(true); + await new Promise((resolve) => setTimeout(resolve, 0)); + globalThis.bytes = bytes; + gc(true); + expect(out).toBe("hello world"); +}); + +it("arrayBufferToString u16", () => { + var encoder = new TextEncoder(); + const bytes = encoder.encode("hello world"); + var uint16 = new Uint16Array(bytes.byteLength); + uint16.set(bytes); + const charCodes = Bun.unsafe + .arrayBufferToString(uint16) + .split("") + .map((a) => a.charCodeAt(0)); + gc(true); + for (let i = 0; i < charCodes.length; i++) { + expect("hello world"[i]).toBe(String.fromCharCode(charCodes[i])); + } + gc(true); + expect(charCodes.length).toBe("hello world".length); + gc(true); +}); + +it("Bun.allocUnsafe", () => { + var buffer = Bun.allocUnsafe(1024); + expect(buffer instanceof Uint8Array).toBe(true); + expect(buffer.length).toBe(1024); + buffer[0] = 0; + expect(buffer[0]).toBe(0); +}); diff --git a/test/bun.js/url.test.ts b/test/bun.js/url.test.ts new file mode 100644 index 000000000..37ea2008b --- /dev/null +++ b/test/bun.js/url.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from "bun:test"; + +describe("url", () => { + it("prints", () => { + expect(Bun.inspect(new URL("https://example.com"))).toBe( + "https://example.com/" + ); + + expect( + Bun.inspect( + new URL( + "https://github.com/Jarred-Sumner/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night" + ) + ) + ).toBe( + "https://github.com/Jarred-Sumner/bun/issues/135?hello%20i%20have%20spaces%20thank%20you%20good%20night" + ); + }); + it("works", () => { + const inputs: [ + [ + string, + { + hash: string; + host: string; + hostname: string; + href: string; + origin: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + username: string; + } + ] + ] = [ + [ + "https://username:password@api.foo.bar.com:9999/baz/okay/i/123?ran=out&of=things#to-use-as-a-placeholder", + { + hash: "#to-use-as-a-placeholder", + host: "api.foo.bar.com:9999", + hostname: "api.foo.bar.com", + href: "https://username:password@api.foo.bar.com:9999/baz/okay/i/123?ran=out&of=things#to-use-as-a-placeholder", + origin: "https://api.foo.bar.com:9999", + password: "password", + pathname: "/baz/okay/i/123", + port: "9999", + protocol: "https:", + search: "?ran=out&of=things", + username: "username", + }, + ], + [ + "https://url.spec.whatwg.org/#url-serializing", + { + hash: "#url-serializing", + host: "url.spec.whatwg.org", + hostname: "url.spec.whatwg.org", + href: "https://url.spec.whatwg.org/#url-serializing", + origin: "https://url.spec.whatwg.org", + password: "", + pathname: "/", + port: "", + protocol: "https:", + search: "", + username: "", + }, + ], + [ + "https://url.spec.whatwg.org#url-serializing", + { + hash: "#url-serializing", + host: "url.spec.whatwg.org", + hostname: "url.spec.whatwg.org", + href: "https://url.spec.whatwg.org/#url-serializing", + origin: "https://url.spec.whatwg.org", + password: "", + pathname: "/", + port: "", + protocol: "https:", + search: "", + username: "", + }, + ], + ]; + + for (let [url, values] of inputs) { + const result = new URL(url); + expect(result.hash).toBe(values.hash); + expect(result.host).toBe(values.host); + expect(result.hostname).toBe(values.hostname); + expect(result.href).toBe(values.href); + expect(result.password).toBe(values.password); + expect(result.pathname).toBe(values.pathname); + expect(result.port).toBe(values.port); + expect(result.protocol).toBe(values.protocol); + expect(result.search).toBe(values.search); + expect(result.username).toBe(values.username); + } + }); +}); diff --git a/test/bun.js/wasm-return-1-test.zig b/test/bun.js/wasm-return-1-test.zig new file mode 100644 index 000000000..d46bdae92 --- /dev/null +++ b/test/bun.js/wasm-return-1-test.zig @@ -0,0 +1,5 @@ +export fn hello() i32 { + return 1; +} + +pub fn main() void {} diff --git a/test/bun.js/wasm.js b/test/bun.js/wasm.js new file mode 100644 index 000000000..a4daaaffe --- /dev/null +++ b/test/bun.js/wasm.js @@ -0,0 +1 @@ +import * as wasm from "./wasm-return-1-test.wasm"; diff --git a/test/bun.js/wasm.test.js b/test/bun.js/wasm.test.js new file mode 100644 index 000000000..ab88d5beb --- /dev/null +++ b/test/bun.js/wasm.test.js @@ -0,0 +1,20 @@ +import { it } from "bun:test"; +// import * as wasm from "./wasm-return-1-test.wasm"; + +// import { readFileSync } from "fs"; + +// it("wasm readFileSync", async () => { +// console.log("here"); +// console.log(wasm.hello()); +// }); + +// it("wasm import", async () => { +// console.log("heyt"); +// try { +// console.log("hi"); +// expect(wasm.hello()).toBe(1); +// } catch (err) { +// console.error(err); +// throw err; +// } +// }); diff --git a/test/bun.js/web-globals.test.js b/test/bun.js/web-globals.test.js new file mode 100644 index 000000000..ac7c22e84 --- /dev/null +++ b/test/bun.js/web-globals.test.js @@ -0,0 +1,46 @@ +import { expect, test } from "bun:test"; + +test("exists", () => { + expect(typeof URL !== "undefined").toBe(true); + expect(typeof URLSearchParams !== "undefined").toBe(true); + expect(typeof DOMException !== "undefined").toBe(true); + expect(typeof Event !== "undefined").toBe(true); + expect(typeof EventTarget !== "undefined").toBe(true); + expect(typeof AbortController !== "undefined").toBe(true); + expect(typeof AbortSignal !== "undefined").toBe(true); + expect(typeof CustomEvent !== "undefined").toBe(true); + expect(typeof Headers !== "undefined").toBe(true); + expect(typeof ErrorEvent !== "undefined").toBe(true); + expect(typeof CloseEvent !== "undefined").toBe(true); + expect(typeof MessageEvent !== "undefined").toBe(true); + expect(typeof TextEncoder !== "undefined").toBe(true); + expect(typeof WebSocket !== "undefined").toBe(true); +}); + +test("CloseEvent", () => { + var event = new CloseEvent("close", { reason: "world" }); + expect(event.type).toBe("close"); + const target = new EventTarget(); + var called = false; + target.addEventListener("close", ({ type, reason }) => { + expect(type).toBe("close"); + expect(reason).toBe("world"); + called = true; + }); + target.dispatchEvent(event); + expect(called).toBe(true); +}); + +test("MessageEvent", () => { + var event = new MessageEvent("message", { data: "world" }); + expect(event.type).toBe("message"); + const target = new EventTarget(); + var called = false; + target.addEventListener("message", ({ type, data }) => { + expect(type).toBe("message"); + expect(data).toBe("world"); + called = true; + }); + target.dispatchEvent(event); + expect(called).toBe(true); +}); diff --git a/test/bun.js/websocket.test.js b/test/bun.js/websocket.test.js new file mode 100644 index 000000000..ab825fa63 --- /dev/null +++ b/test/bun.js/websocket.test.js @@ -0,0 +1,79 @@ +import { describe, it, expect } from "bun:test"; +import { unsafe } from "bun"; +import { gc } from "./gc"; + +const TEST_WEBSOCKET_HOST = + process.env.TEST_WEBSOCKET_HOST || "wss://ws.postman-echo.com/raw"; + +describe("WebSocket", () => { + it("should connect", async () => { + const ws = new WebSocket(TEST_WEBSOCKET_HOST); + await new Promise((resolve, reject) => { + ws.onopen = resolve; + ws.onerror = reject; + }); + var closed = new Promise((resolve, reject) => { + ws.onclose = resolve; + }); + ws.close(); + await closed; + }); + + it("should send and receive messages", async () => { + const ws = new WebSocket(TEST_WEBSOCKET_HOST); + await new Promise((resolve, reject) => { + ws.onopen = resolve; + ws.onerror = reject; + ws.onclose = () => { + reject("WebSocket closed"); + }; + }); + const count = 10; + + // 10 messages in burst + var promise = new Promise((resolve, reject) => { + var remain = count; + ws.onmessage = (event) => { + gc(true); + expect(event.data).toBe("Hello World!"); + remain--; + + if (remain <= 0) { + ws.onmessage = () => {}; + resolve(); + } + }; + ws.onerror = reject; + }); + + for (let i = 0; i < count; i++) { + ws.send("Hello World!"); + gc(true); + } + + await promise; + var echo = 0; + + // 10 messages one at a time + function waitForEcho() { + return new Promise((resolve, reject) => { + gc(true); + const msg = `Hello World! ${echo++}`; + ws.onmessage = (event) => { + expect(event.data).toBe(msg); + resolve(); + }; + ws.onerror = reject; + ws.onclose = reject; + ws.send(msg); + gc(true); + }); + } + gc(true); + for (let i = 0; i < count; i++) await waitForEcho(); + ws.onclose = () => {}; + ws.onerror = () => {}; + ws.close(); + gc(true); + }); +}); diff --git a/test/bun.js/writeFileSync.txt b/test/bun.js/writeFileSync.txt new file mode 100644 index 000000000..a0fe4515f --- /dev/null +++ b/test/bun.js/writeFileSync.txt @@ -0,0 +1 @@ +File
\ No newline at end of file diff --git a/test/bun.js/zlib.test.js b/test/bun.js/zlib.test.js new file mode 100644 index 000000000..aecb095cc --- /dev/null +++ b/test/bun.js/zlib.test.js @@ -0,0 +1,18 @@ +import { describe, it, expect } from "bun:test"; +import { gzipSync, deflateSync, inflateSync, gunzipSync } from "bun"; + +describe("zlib", () => { + it("should be able to deflate and inflate", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = deflateSync(data); + const decompressed = inflateSync(compressed); + expect(decompressed.join("")).toBe(data.join("")); + }); + + it("should be able to gzip and gunzip", () => { + const data = new TextEncoder().encode("Hello World!".repeat(1)); + const compressed = gzipSync(data); + const decompressed = gunzipSync(compressed); + expect(decompressed.join("")).toBe(data.join("")); + }); +}); |