aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/bun-types/globals.d.ts24
-rw-r--r--src/bun.js/api/bun.zig2
-rw-r--r--src/bun.js/bindings/JSDOMFile.cpp105
-rw-r--r--src/bun.js/bindings/JSDOMFile.h7
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp32
-rw-r--r--src/bun.js/bindings/blob.cpp14
-rw-r--r--src/bun.js/modules/NodeBufferModule.h7
-rw-r--r--src/bun.js/webcore/blob.zig126
-rw-r--r--test/js/bun/globals.test.js75
-rw-r--r--test/js/node/buffer.test.js2
-rw-r--r--test/js/web/html/FormData.test.ts4
-rw-r--r--test/js/web/web-globals.test.js1
12 files changed, 387 insertions, 12 deletions
diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts
index 1a10cc8e0..740cc9690 100644
--- a/packages/bun-types/globals.d.ts
+++ b/packages/bun-types/globals.d.ts
@@ -827,7 +827,7 @@ type ResponseType =
| "opaque"
| "opaqueredirect";
-type FormDataEntryValue = Blob | string;
+type FormDataEntryValue = File | string;
/** Provides a way to easily construct a set of key/value pairs representing
* form fields and their values, which can then be easily sent using the
@@ -959,6 +959,28 @@ declare var Blob: {
new (parts?: BlobPart[], options?: BlobPropertyBag): Blob;
};
+interface File extends Blob {
+ readonly lastModified: number;
+ readonly name: string;
+}
+
+declare var File: {
+ prototype: File;
+
+ /**
+ * Create a new [File](https://developer.mozilla.org/en-US/docs/Web/API/File)
+ *
+ * @param `parts` - An array of strings, numbers, BufferSource, or [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) objects
+ * @param `name` - The name of the file
+ * @param `options` - An object containing properties to be added to the [File](https://developer.mozilla.org/en-US/docs/Web/API/File)
+ */
+ new (
+ parts: BlobPart[],
+ name: string,
+ options?: BlobPropertyBag & { lastModified?: Date | number },
+ ): File;
+};
+
interface ResponseInit {
headers?: HeadersInit;
/** @default 200 */
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index e98151fb1..8d1244195 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -15,7 +15,7 @@ pub const BunObject = struct {
pub const build = Bun.JSBundler.buildFn;
pub const connect = JSC.wrapStaticMethod(JSC.API.Listener, "connect", false);
pub const deflateSync = JSC.wrapStaticMethod(JSZlib, "deflateSync", true);
- pub const file = WebCore.Blob.constructFile;
+ pub const file = WebCore.Blob.constructBunFile;
pub const fs = Bun.fs;
pub const gc = Bun.runGC;
pub const generateHeapSnapshot = Bun.generateHeapSnapshot;
diff --git a/src/bun.js/bindings/JSDOMFile.cpp b/src/bun.js/bindings/JSDOMFile.cpp
new file mode 100644
index 000000000..1d7770ac1
--- /dev/null
+++ b/src/bun.js/bindings/JSDOMFile.cpp
@@ -0,0 +1,105 @@
+#include "root.h"
+#include "ZigGeneratedClasses.h"
+#include "JavaScriptCore/ObjectConstructor.h"
+#include "JavaScriptCore/InternalFunction.h"
+#include "JavaScriptCore/FunctionPrototype.h"
+#include "JSDOMFile.h"
+
+using namespace JSC;
+
+extern "C" void* JSDOMFile__construct(JSC::JSGlobalObject*, JSC::CallFrame* callframe);
+extern "C" bool JSDOMFile__hasInstance(EncodedJSValue, JSC::JSGlobalObject*, EncodedJSValue);
+
+// TODO: make this inehrit from JSBlob instead of InternalFunction
+// That will let us remove this hack for [Symbol.hasInstance] and fix the prototype chain.
+class JSDOMFile : public JSC::InternalFunction {
+ using Base = JSC::InternalFunction;
+
+public:
+ JSDOMFile(JSC::VM& vm, JSC::Structure* structure)
+ : Base(vm, structure, nullptr, construct)
+ {
+ }
+
+ DECLARE_INFO;
+
+ static constexpr unsigned StructureFlags = (Base::StructureFlags & ~ImplementsDefaultHasInstance) | ImplementsHasInstance;
+
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.internalFunctionSpace();
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(InternalFunctionType, StructureFlags), info());
+ }
+
+ void finishCreation(JSC::VM& vm)
+ {
+ Base::finishCreation(vm, 2, "File"_s);
+ }
+
+ static JSDOMFile* create(JSC::VM& vm, JSGlobalObject* globalObject)
+ {
+ auto* zigGlobal = reinterpret_cast<Zig::GlobalObject*>(globalObject);
+ auto* object = new (NotNull, JSC::allocateCell<JSDOMFile>(vm)) JSDOMFile(vm, createStructure(vm, globalObject, zigGlobal->functionPrototype()));
+ object->finishCreation(vm);
+
+ // This is not quite right. But we'll fix it if someone files an issue about it.
+ object->putDirect(vm, vm.propertyNames->prototype, zigGlobal->JSBlobPrototype(), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | 0);
+
+ return object;
+ }
+
+ static bool customHasInstance(JSObject* object, JSGlobalObject* globalObject, JSValue value)
+ {
+ if (!value.isObject())
+ return false;
+
+ // Note: this breaks [Symbol.hasInstance]
+ // We must do this for now until we update the code generator to export classes
+ return JSDOMFile__hasInstance(JSValue::encode(object), globalObject, JSValue::encode(value));
+ }
+
+ static EncodedJSValue construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)
+ {
+ Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSC::VM& vm = globalObject->vm();
+ JSObject* newTarget = asObject(callFrame->newTarget());
+ auto* constructor = globalObject->JSDOMFileConstructor();
+ Structure* structure = globalObject->JSBlobStructure();
+ if (constructor != newTarget) {
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>(
+ // ShadowRealm functions belong to a different global object.
+ getFunctionRealm(globalObject, newTarget));
+ RETURN_IF_EXCEPTION(scope, {});
+ structure = InternalFunction::createSubclassStructure(
+ globalObject,
+ newTarget,
+ functionGlobalObject->JSBlobStructure());
+ }
+
+ void* ptr = JSDOMFile__construct(globalObject, callFrame);
+
+ if (UNLIKELY(!ptr)) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ return JSValue::encode(
+ WebCore::JSBlob::create(vm, globalObject, structure, ptr));
+ }
+};
+
+const JSC::ClassInfo JSDOMFile::s_info = { "File"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMFile) };
+
+namespace Bun {
+
+JSC::JSObject* createJSDOMFileConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ return JSDOMFile::create(vm, globalObject);
+}
+
+} \ No newline at end of file
diff --git a/src/bun.js/bindings/JSDOMFile.h b/src/bun.js/bindings/JSDOMFile.h
new file mode 100644
index 000000000..eed1e98ab
--- /dev/null
+++ b/src/bun.js/bindings/JSDOMFile.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "root.h"
+
+namespace Bun {
+JSC::JSObject* createJSDOMFileConstructor(JSC::VM&, JSC::JSGlobalObject*);
+}
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index f8fc2ea2b..0ecafeae4 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -123,6 +123,8 @@
#include "JSMessagePort.h"
#include "JSBroadcastChannel.h"
+#include "JSDOMFile.h"
+
#if ENABLE(REMOTE_INSPECTOR)
#include "JavaScriptCore/RemoteInspectorServer.h"
#endif
@@ -2780,6 +2782,12 @@ void GlobalObject::finishCreation(VM& vm)
Base::finishCreation(vm);
ASSERT(inherits(info()));
+ m_JSDOMFileConstructor.initLater(
+ [](const Initializer<JSObject>& init) {
+ JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
+ init.set(fileConstructor);
+ });
+
m_cryptoObject.initLater(
[](const Initializer<JSObject>& init) {
JSC::JSGlobalObject* globalObject = init.owner;
@@ -3330,6 +3338,26 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPostMessage,
return JSValue::encode(jsUndefined());
}
+JSC_DEFINE_CUSTOM_GETTER(JSDOMFileConstructor_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
+{
+ Zig::GlobalObject* bunGlobalObject = jsCast<Zig::GlobalObject*>(globalObject);
+ return JSValue::encode(
+ bunGlobalObject->JSDOMFileConstructor());
+}
+
+JSC_DEFINE_CUSTOM_SETTER(JSDOMFileConstructor_setter,
+ (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue value, JSC::PropertyName property))
+{
+ if (JSValue::decode(thisValue) != globalObject) {
+ return false;
+ }
+
+ auto& vm = globalObject->vm();
+ globalObject->putDirect(vm, property, JSValue::decode(value), 0);
+ return true;
+}
+
JSC_DEFINE_CUSTOM_GETTER(BunCommonJSModule_getter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
{
Zig::GlobalObject* bunGlobalObject = jsCast<Zig::GlobalObject*>(globalObject);
@@ -3777,6 +3805,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "Crypto"_s), JSC::CustomGetterSetter::create(vm, JSCrypto_getter, JSCrypto_setter),
JSC::PropertyAttribute::DontDelete | 0);
+ putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "File"_s), JSC::CustomGetterSetter::create(vm, JSDOMFileConstructor_getter, JSDOMFileConstructor_setter),
+ JSC::PropertyAttribute::DontDelete | 0);
+
putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "DOMException"_s), JSC::CustomGetterSetter::create(vm, JSDOMException_getter, nullptr),
JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
@@ -3996,6 +4027,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_emitReadableNextTickFunction.visit(visitor);
thisObject->m_JSBufferSubclassStructure.visit(visitor);
thisObject->m_cryptoObject.visit(visitor);
+ thisObject->m_JSDOMFileConstructor.visit(visitor);
thisObject->m_requireFunctionUnbound.visit(visitor);
thisObject->m_requireResolveFunctionUnbound.visit(visitor);
diff --git a/src/bun.js/bindings/blob.cpp b/src/bun.js/bindings/blob.cpp
index 257f230e1..0f255d7c8 100644
--- a/src/bun.js/bindings/blob.cpp
+++ b/src/bun.js/bindings/blob.cpp
@@ -1,17 +1,29 @@
#include "blob.h"
+#include "ZigGeneratedClasses.h"
extern "C" JSC::EncodedJSValue Blob__create(JSC::JSGlobalObject* globalObject, void* impl);
+extern "C" void* Blob__setAsFile(void* impl, BunString* filename);
namespace WebCore {
JSC::JSValue toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, WebCore::Blob& impl)
{
+ BunString filename = Bun::toString(impl.fileName());
+ impl.m_impl = Blob__setAsFile(impl.impl(), &filename);
+
return JSC::JSValue::decode(Blob__create(lexicalGlobalObject, Blob__dupe(impl.impl())));
}
JSC::JSValue toJSNewlyCreated(JSC::JSGlobalObject* lexicalGlobalObject, JSDOMGlobalObject* globalObject, Ref<WebCore::Blob>&& impl)
{
- return JSC::JSValue::decode(Blob__create(lexicalGlobalObject, impl->impl()));
+ auto fileNameStr = impl->fileName();
+ BunString filename = Bun::toString(fileNameStr);
+
+ EncodedJSValue encoded = Blob__create(lexicalGlobalObject, impl->impl());
+ JSBlob* blob = jsCast<JSBlob*>(JSC::JSValue::decode(encoded));
+ Blob__setAsFile(blob->wrapped(), &filename);
+
+ return JSC::JSValue::decode(encoded);
}
} \ No newline at end of file
diff --git a/src/bun.js/modules/NodeBufferModule.h b/src/bun.js/modules/NodeBufferModule.h
index 5c6acd48e..5eea9c099 100644
--- a/src/bun.js/modules/NodeBufferModule.h
+++ b/src/bun.js/modules/NodeBufferModule.h
@@ -149,12 +149,11 @@ DEFINE_NATIVE_MODULE(NodeBuffer) {
put(JSC::Identifier::fromString(vm, "SlowBuffer"_s), slowBuffer);
auto blobIdent = JSC::Identifier::fromString(vm, "Blob"_s);
- JSValue blobValue =
- lexicalGlobalObject->get(globalObject, PropertyName(blobIdent));
+ JSValue blobValue = globalObject->JSBlobConstructor();
put(blobIdent, blobValue);
- // TODO: implement File
- put(JSC::Identifier::fromString(vm, "File"_s), blobValue);
+ put(JSC::Identifier::fromString(vm, "File"_s),
+ globalObject->JSDOMFileConstructor());
put(JSC::Identifier::fromString(vm, "INSPECT_MAX_BYTES"_s),
JSC::jsNumber(50));
diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig
index 983841581..df2e17ce4 100644
--- a/src/bun.js/webcore/blob.zig
+++ b/src/bun.js/webcore/blob.zig
@@ -88,8 +88,13 @@ pub const Blob = struct {
/// When UTF-16, they're nearly always due to non-ascii characters
is_all_ascii: ?bool = null,
+ /// Was it created via file constructor?
+ is_jsdom_file: bool = false,
+
globalThis: *JSGlobalObject = undefined,
+ last_modified: f64 = 0.0,
+
/// Max int of double precision
/// 9 petabytes is probably enough for awhile
/// We want to avoid coercing to a BigInt because that's a heap allocation
@@ -478,6 +483,22 @@ pub const Blob = struct {
return Blob__dupe(this);
}
+ export fn Blob__setAsFile(this: *Blob, path_str: *bun.String) *Blob {
+ this.is_jsdom_file = true;
+
+ // This is not 100% correct...
+ if (this.store) |store| {
+ if (store.data == .bytes) {
+ if (store.data.bytes.stored_name.len == 0) {
+ var utf8 = path_str.toUTF8WithoutRef(bun.default_allocator).clone(bun.default_allocator) catch unreachable;
+ store.data.bytes.stored_name = bun.PathString.init(utf8.slice());
+ }
+ }
+ }
+
+ return this;
+ }
+
export fn Blob__dupe(ptr: *anyopaque) *Blob {
var this = bun.cast(*Blob, ptr);
var new = bun.default_allocator.create(Blob) catch unreachable;
@@ -494,6 +515,7 @@ pub const Blob = struct {
_ = Blob__dupeFromJS;
_ = Blob__destroy;
_ = Blob__dupe;
+ _ = Blob__setAsFile;
}
pub fn writeFormatForSize(size: usize, writer: anytype, comptime enable_ansi_colors: bool) !void {
@@ -1123,7 +1145,102 @@ pub const Blob = struct {
return JSC.JSPromise.resolvedPromiseValue(globalThis, JSC.JSValue.jsNumber(written));
}
- pub fn constructFile(
+ pub export fn JSDOMFile__hasInstance(_: JSC.JSValue, _: *JSC.JSGlobalObject, value: JSC.JSValue) callconv(.C) bool {
+ JSC.markBinding(@src());
+ var blob = value.as(Blob) orelse return false;
+ return blob.is_jsdom_file;
+ }
+
+ pub export fn JSDOMFile__construct(
+ globalThis: *JSC.JSGlobalObject,
+ callframe: *JSC.CallFrame,
+ ) callconv(.C) ?*Blob {
+ JSC.markBinding(@src());
+ var allocator = bun.default_allocator;
+ var blob: Blob = undefined;
+ var arguments = callframe.arguments(3);
+ var args = arguments.ptr[0..arguments.len];
+
+ if (args.len < 2) {
+ globalThis.throwInvalidArguments("new File(bits, name) expects at least 2 arguments", .{});
+ return null;
+ }
+
+ const name_value_str = bun.String.tryFromJS(args[1], globalThis) orelse {
+ globalThis.throwInvalidArguments("new File(bits, name) expects string as the second argument", .{});
+ return null;
+ };
+
+ blob = get(globalThis, args[0], false, true) catch |err| {
+ if (err == error.InvalidArguments) {
+ globalThis.throwInvalidArguments("new File(bits, name) expects iterable as the first argument", .{});
+ return null;
+ }
+ globalThis.throwOutOfMemory();
+ return null;
+ };
+
+ if (blob.store) |store_| {
+ store_.data.bytes.stored_name = bun.PathString.init(
+ (name_value_str.toUTF8WithoutRef(bun.default_allocator).clone(bun.default_allocator) catch unreachable).slice(),
+ );
+ }
+
+ if (args.len > 2) {
+ const options = args[2];
+ if (options.isObject()) {
+ // type, the ASCII-encoded string in lower case
+ // representing the media type of the Blob.
+ // Normative conditions for this member are provided
+ // in the § 3.1 Constructors.
+ if (options.get(globalThis, "type")) |content_type| {
+ inner: {
+ if (content_type.isString()) {
+ var content_type_str = content_type.toSlice(globalThis, bun.default_allocator);
+ defer content_type_str.deinit();
+ var slice = content_type_str.slice();
+ if (!strings.isAllASCII(slice)) {
+ break :inner;
+ }
+ blob.content_type_was_set = true;
+
+ if (globalThis.bunVM().mimeType(slice)) |mime| {
+ blob.content_type = mime.value;
+ break :inner;
+ }
+ var content_type_buf = allocator.alloc(u8, slice.len) catch unreachable;
+ blob.content_type = strings.copyLowercase(slice, content_type_buf);
+ blob.content_type_allocated = true;
+ }
+ }
+ }
+
+ if (options.getTruthy(globalThis, "lastModified")) |last_modified| {
+ blob.last_modified = last_modified.coerce(f64, globalThis);
+ }
+ }
+ }
+
+ if (blob.content_type.len == 0) {
+ blob.content_type = "";
+ blob.content_type_was_set = false;
+ }
+
+ var blob_ = allocator.create(Blob) catch unreachable;
+ blob_.* = blob;
+ blob_.allocator = allocator;
+ blob_.is_jsdom_file = true;
+ return blob_;
+ }
+
+ comptime {
+ if (!JSC.is_bindgen) {
+ _ = JSDOMFile__hasInstance;
+ _ = JSDOMFile__construct;
+ }
+ }
+
+ pub fn constructBunFile(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
@@ -2484,7 +2601,7 @@ pub const Blob = struct {
cap: SizeType = 0,
allocator: std.mem.Allocator,
- /// Used by standalone module graph
+ /// Used by standalone module graph and the File constructor
stored_name: bun.PathString = bun.PathString.empty,
pub fn init(bytes: []u8, allocator: std.mem.Allocator) ByteStore {
@@ -2505,6 +2622,7 @@ pub const Blob = struct {
}
pub fn deinit(this: *ByteStore) void {
+ bun.default_allocator.free(this.stored_name.slice());
this.allocator.free(this.ptr[0..this.cap]);
}
@@ -2911,6 +3029,10 @@ pub const Blob = struct {
}
}
+ if (this.is_jsdom_file) {
+ return JSValue.jsNumber(this.last_modified);
+ }
+
return JSValue.jsNumber(init_timestamp);
}
diff --git a/test/js/bun/globals.test.js b/test/js/bun/globals.test.js
index 6b004a5f7..fd291d8bc 100644
--- a/test/js/bun/globals.test.js
+++ b/test/js/bun/globals.test.js
@@ -1,4 +1,4 @@
-import { expect, it } from "bun:test";
+import { expect, it, describe } from "bun:test";
it("extendable", () => {
const classes = [Blob, TextDecoder, TextEncoder, Request, Response, Headers, HTMLRewriter, Bun.Transpiler, Buffer];
@@ -25,6 +25,7 @@ it("writable", () => {
["ErrorEvent", ErrorEvent],
["CustomEvent", CustomEvent],
["CloseEvent", CloseEvent],
+ ["File", File],
];
for (let [name, Class] of classes) {
globalThis[name] = 123;
@@ -45,8 +46,80 @@ it("name", () => {
["HTMLRewriter", HTMLRewriter],
["Transpiler", Bun.Transpiler],
["Buffer", Buffer],
+ ["File", File],
];
for (let [name, Class] of classes) {
expect(Class.name).toBe(name);
}
});
+
+describe("File", () => {
+ it("constructor", () => {
+ const file = new File(["foo"], "bar.txt", { type: "text/plain;charset=utf-8" });
+ expect(file.name).toBe("bar.txt");
+ expect(file.type).toBe("text/plain;charset=utf-8");
+ expect(file.size).toBe(3);
+ expect(file.lastModified).toBe(0);
+ });
+
+ it("constructor with lastModified", () => {
+ const file = new File(["foo"], "bar.txt", { type: "text/plain;charset=utf-8", lastModified: 123 });
+ expect(file.name).toBe("bar.txt");
+ expect(file.type).toBe("text/plain;charset=utf-8");
+ expect(file.size).toBe(3);
+ expect(file.lastModified).toBe(123);
+ });
+
+ it("constructor with undefined name", () => {
+ const file = new File(["foo"], undefined);
+ expect(file.name).toBe("undefined");
+ expect(file.type).toBe("");
+ expect(file.size).toBe(3);
+ expect(file.lastModified).toBe(0);
+ });
+
+ it("constructor throws invalid args", () => {
+ const invalid = [[], [undefined], [null], [Symbol(), "foo"], [Symbol(), Symbol(), Symbol()]];
+ for (let args of invalid) {
+ expect(() => new File(...args)).toThrow();
+ }
+ });
+
+ it("instanceof", () => {
+ const file = new File(["foo"], "bar.txt", { type: "text/plain;charset=utf-8" });
+ expect(file instanceof File).toBe(true);
+ expect(file instanceof Blob).toBe(true);
+ expect(file instanceof Object).toBe(true);
+ expect(file instanceof Function).toBe(false);
+ const blob = new Blob(["foo"], { type: "text/plain;charset=utf-8" });
+ expect(blob instanceof File).toBe(false);
+ });
+
+ it("extendable", async () => {
+ class Foo extends File {
+ constructor(...args) {
+ super(...args);
+ }
+
+ bar() {
+ return true;
+ }
+
+ text() {
+ return super.text();
+ }
+ }
+ const foo = new Foo(["foo"], "bar.txt", { type: "text/plain;charset=utf-8" });
+ expect(foo instanceof File).toBe(true);
+ expect(foo instanceof Blob).toBe(true);
+ expect(foo instanceof Object).toBe(true);
+ expect(foo instanceof Function).toBe(false);
+ expect(foo instanceof Foo).toBe(true);
+ expect(foo.bar()).toBe(true);
+ expect(foo.name).toBe("bar.txt");
+ expect(foo.type).toBe("text/plain;charset=utf-8");
+ expect(foo.size).toBe(3);
+ expect(foo.lastModified).toBe(0);
+ expect(await foo.text()).toBe("foo");
+ });
+});
diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js
index 834536020..4040f5ce2 100644
--- a/test/js/node/buffer.test.js
+++ b/test/js/node/buffer.test.js
@@ -1748,7 +1748,7 @@ it("constants", () => {
});
it("File", () => {
- expect(BufferModule.File).toBe(Blob);
+ expect(BufferModule.File).toBe(File);
});
it("transcode", () => {
diff --git a/test/js/web/html/FormData.test.ts b/test/js/web/html/FormData.test.ts
index 8c66abb10..c742bd33e 100644
--- a/test/js/web/html/FormData.test.ts
+++ b/test/js/web/html/FormData.test.ts
@@ -15,9 +15,11 @@ describe("FormData", () => {
it("should be able to append a Blob", async () => {
const formData = new FormData();
- formData.append("foo", new Blob(["bar"]));
+ formData.append("foo", new Blob(["bar"]), "mynameis.txt");
expect(await ((await formData.get("foo")) as Blob)!.text()).toBe("bar");
expect(formData.getAll("foo")[0] instanceof Blob).toBe(true);
+ expect(formData.getAll("foo")[0] instanceof File).toBe(true);
+ expect((formData.getAll("foo")[0] as File).name).toBe("mynameis.txt");
});
it("should be able to set a Blob", async () => {
diff --git a/test/js/web/web-globals.test.js b/test/js/web/web-globals.test.js
index 46422c210..9b4c86006 100644
--- a/test/js/web/web-globals.test.js
+++ b/test/js/web/web-globals.test.js
@@ -20,6 +20,7 @@ test("exists", () => {
expect(typeof Blob !== "undefined").toBe(true);
expect(typeof FormData !== "undefined").toBe(true);
expect(typeof Worker !== "undefined").toBe(true);
+ expect(typeof File !== "undefined").toBe(true);
});
const globalSetters = [