diff --git a/README.ja.md b/README.ja.md index 608b47c..e719278 100644 --- a/README.ja.md +++ b/README.ja.md @@ -207,6 +207,15 @@ pub fn themed_component( } ``` +`glendix/js/object` は意図的にデータ構築だけを担当します。任意のプロパティの +読み取り・書き込み・削除、メソッド呼び出し、コンストラクター実行などの動的な +interop は、別の `glendix/js/reflect` モジュールにあります。Reflection は、 +プロパティやメソッドの存在を型システムではなく呼び出し側が保証する +unsafe/dynamic な境界です。`__proto__` を通常のデータとして保持する保証は +`object.from_entries` にだけ適用されます。Reflection による代入は通常の +JavaScript setter の意味を維持するため、信頼できないプロパティ名を +`reflect.set` に渡してはいけません。 + ## WebAssembly 依存関係 Glendix は、ブラウザ toolchain が使用する次の標準的な静的 URL 形式の @@ -268,6 +277,17 @@ JavaScript 設定、最終 MPK ビルドを担当します。 opaque 型 `glendix/js/array.JsArray(element)` は削除されたので、値の型は `gleam/javascript/array.Array(element)` で注釈してください。 +`glendix/js/object` はデータ構築専用になりました。従来の reflection 操作である +`get`、`set`、`delete`、`has`、`call_method`、 +`call_method_without_arguments`、`new_instance` と `JsConstructor` 型は、新しい +`glendix/js/reflect` モジュールへ移動しました。関数名・ラベル・挙動は維持されて +います。従来の `object.get(from: handle, key: "x")` は +`import glendix/js/reflect` を追加し、 +`reflect.get(from: handle, key: "x")` へ変更してください。データ構築関数 +(`from_entries`、`empty`、`string`、`int`、`float`、`bool`、`from_object`) は +`glendix/js/object` に残り、`from_entries` は `__proto__` のようなキーでも +prototype pollution を起こさない `Object.fromEntries` の挙動を維持します。 + ## 開発 ```sh diff --git a/README.ko.md b/README.ko.md index 98bb572..aef2775 100644 --- a/README.ko.md +++ b/README.ko.md @@ -201,6 +201,13 @@ pub fn themed_component( } ``` +`glendix/js/object`는 의도적으로 데이터 생성만 담당한다. 임의 속성의 읽기·쓰기·삭제, +메서드 호출, 생성자 실행 같은 동적 interop은 별도의 `glendix/js/reflect` 모듈에 +있다. Reflection은 속성이나 메서드가 실제로 존재함을 타입 시스템이 아니라 호출자가 +보장하는 unsafe/dynamic 경계다. `__proto__`를 일반 데이터로 보존하는 보장은 +`object.from_entries`에만 적용된다. Reflection 대입은 일반 JavaScript setter 의미를 +유지하므로 신뢰할 수 없는 속성 이름을 `reflect.set`에 전달하면 안 된다. + ## WebAssembly 의존성 Glendix는 브라우저 도구가 사용하는 다음 표준 정적 URL 형식의 WebAssembly @@ -262,6 +269,16 @@ JavaScript 어댑터를 더 이상 포함하지 않는다. `from_list`와 `to_li 타입 `glendix/js/array.JsArray(element)`는 제거되었으므로 값의 타입은 `gleam/javascript/array.Array(element)`로 표기한다. +`glendix/js/object`는 이제 데이터 생성만 담당한다. 기존 reflection 연산인 `get`, +`set`, `delete`, `has`, `call_method`, `call_method_without_arguments`, +`new_instance`와 `JsConstructor` 타입은 새 `glendix/js/reflect` 모듈로 이동했다. +함수 이름·label·동작은 유지된다. 기존 +`object.get(from: handle, key: "x")` 호출은 `import glendix/js/reflect`를 추가하고 +`reflect.get(from: handle, key: "x")`로 변경한다. 데이터 생성 함수 +(`from_entries`, `empty`, `string`, `int`, `float`, `bool`, `from_object`)는 +`glendix/js/object`에 남아 있으며, `from_entries`는 `__proto__` 같은 키에도 +prototype pollution을 일으키지 않는 `Object.fromEntries` 동작을 유지한다. + ## 개발 ```sh diff --git a/README.md b/README.md index 30d20af..8855dbf 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,14 @@ pub fn themed_component( } ``` +`glendix/js/object` is deliberately data-only. Dynamic interop — reading, +writing, or deleting arbitrary properties, calling methods, and invoking +constructors — lives in the separate `glendix/js/reflect` module. Reflection is +the unsafe/dynamic boundary where the caller, not the type system, guarantees a +property or method exists. The `__proto__`-as-data guarantee applies to +`object.from_entries`; reflective assignment retains ordinary JavaScript setter +semantics, so never pass untrusted property names to `reflect.set`. + ## WebAssembly dependencies Glendix automatically packages browser WebAssembly modules referenced with the @@ -332,6 +340,17 @@ common `list |> array.from_list |> array.to_list` usage is unchanged. The former opaque `glendix/js/array.JsArray(element)` type is removed; annotate values with `gleam/javascript/array.Array(element)` instead. +`glendix/js/object` is now data-only. Its reflection operations moved unchanged +to the new `glendix/js/reflect` module: `get`, `set`, `delete`, `has`, +`call_method`, `call_method_without_arguments`, and `new_instance`, together +with the `JsConstructor` type. Their names, labels, and behavior are preserved, +so migrate a pre-split call such as `object.get(from: handle, key: "x")` to +`reflect.get(from: handle, key: "x")` (add `import glendix/js/reflect`). Data +construction (`from_entries`, `empty`, `string`, `int`, `float`, `bool`, +`from_object`) stays in `glendix/js/object`, and `from_entries` keeps its +prototype-pollution-safe `Object.fromEntries` behavior for keys such as +`__proto__`. + ## Development ```sh diff --git a/src/glendix/js/object.gleam b/src/glendix/js/object.gleam index db8eaa8..615b09f 100644 --- a/src/glendix/js/object.gleam +++ b/src/glendix/js/object.gleam @@ -1,4 +1,19 @@ -//// Creates and manipulates typed JavaScript object handles. +//// Builds plain, prototype-safe JavaScript data objects from typed entries. +//// +//// This module owns *data-only* object construction. It converts typed Gleam +//// scalars into opaque JavaScript values and assembles them into plain objects +//// whose keys are always ordinary own data properties. Dynamic interop such as +//// property reads and writes, method calls, and constructor invocation lives in +//// the separate `glendix/js/reflect` module, so this data boundary never +//// depends on arbitrary reflection. +//// +//// Construction keeps a small bespoke FFI (`Object.fromEntries`) on purpose: no +//// ecosystem package builds a live, prototype-pollution-safe plain object. +//// The public `gleam/javascript` API only covers arrays, promises, and symbols, +//// while Plinth has no general plain-object builder. A `gleam/json` round-trip +//// would be indirect and lossy for live handles. The retained FFI guarantees +//// that even a `__proto__` entry is stored as ordinary own data instead of +//// invoking the legacy prototype setter. //// /// Represents a JavaScript value whose runtime shape is intentionally opaque. @@ -7,9 +22,6 @@ pub type JsValue /// Represents a JavaScript object handle. pub type JsObject -/// Represents a JavaScript constructor handle. -pub type JsConstructor - /// Represents a JavaScript boolean value. pub type JsBoolean { /// JavaScript `true`. @@ -61,55 +73,6 @@ pub fn empty() -> JsObject { empty_object_raw() } -/// Reads an object property. -pub fn get(from object: JsObject, key key: String) -> JsValue { - get_property_raw(object, key) -} - -/// Mutates an object property and returns the same object handle. -pub fn set( - on object: JsObject, - key key: String, - to value: JsValue, -) -> JsObject { - set_property_raw(object, key, value) -} - -/// Deletes an object property and returns the same object handle. -pub fn delete(from object: JsObject, key key: String) -> JsObject { - delete_property_raw(object, key) -} - -/// Reports whether an object has the given property. -pub fn has(in object: JsObject, key key: String) -> Bool { - has_property_raw(object, key) -} - -/// Calls an object method with a list of arguments. -pub fn call_method( - on object: JsObject, - named method: String, - with arguments: List(JsValue), -) -> JsValue { - call_method_raw(object, method, arguments) -} - -/// Calls an object method without arguments. -pub fn call_method_without_arguments( - on object: JsObject, - named method: String, -) -> JsValue { - call_method_without_arguments_raw(object, method) -} - -/// Creates an object with JavaScript's `new` operator. -pub fn new_instance( - using constructor: JsConstructor, - with arguments: List(JsValue), -) -> JsObject { - new_instance_raw(constructor, arguments) -} - // -- FFI -- @external(javascript, "./object_ffi.mjs", "identity") fn string_raw(value: String) -> JsValue @@ -131,34 +94,3 @@ fn create_object_raw(entries: List(#(String, JsValue))) -> JsObject @external(javascript, "./object_ffi.mjs", "empty_object") fn empty_object_raw() -> JsObject - -@external(javascript, "./object_ffi.mjs", "get_property") -fn get_property_raw(object: JsObject, key: String) -> JsValue - -@external(javascript, "./object_ffi.mjs", "set_property") -fn set_property_raw(object: JsObject, key: String, value: JsValue) -> JsObject - -@external(javascript, "./object_ffi.mjs", "delete_property") -fn delete_property_raw(object: JsObject, key: String) -> JsObject - -@external(javascript, "./object_ffi.mjs", "has_property") -fn has_property_raw(object: JsObject, key: String) -> Bool - -@external(javascript, "./object_ffi.mjs", "call_method") -fn call_method_raw( - object: JsObject, - method: String, - arguments: List(JsValue), -) -> JsValue - -@external(javascript, "./object_ffi.mjs", "call_method_0") -fn call_method_without_arguments_raw( - object: JsObject, - method: String, -) -> JsValue - -@external(javascript, "./object_ffi.mjs", "new_instance") -fn new_instance_raw( - constructor: JsConstructor, - arguments: List(JsValue), -) -> JsObject diff --git a/src/glendix/js/object_ffi.mjs b/src/glendix/js/object_ffi.mjs index be01d67..c4f60c9 100644 --- a/src/glendix/js/object_ffi.mjs +++ b/src/glendix/js/object_ffi.mjs @@ -7,29 +7,6 @@ export function create_object(entries) { export function empty_object() { return {}; } -export function get_property(obj, key) { - return obj[key]; -} -export function set_property(obj, key, value) { - obj[key] = value; - return obj; -} -export function delete_property(obj, key) { - delete obj[key]; - return obj; -} -export function has_property(obj, key) { - return key in obj; -} -export function call_method(obj, method, args) { - return obj[method](...args.toArray()); -} -export function call_method_0(obj, method) { - return obj[method](); -} -export function new_instance(constructor, args) { - return new constructor(...args.toArray()); -} export function identity(value) { return value; } diff --git a/src/glendix/js/reflect.gleam b/src/glendix/js/reflect.gleam new file mode 100644 index 0000000..0659ad9 --- /dev/null +++ b/src/glendix/js/reflect.gleam @@ -0,0 +1,110 @@ +//// Performs dynamic JavaScript reflection against object handles. +//// +//// These operations are the interop boundary: they read and write arbitrary +//// properties, invoke methods, and call constructors by name at runtime. They +//// are inherently dynamic and unsafe in the sense that the caller, not the +//// type system, guarantees a property exists, a method is callable, or a value +//// is a constructor. Keep this surface minimal and prefer the data-only +//// `glendix/js/object` module whenever a plain data object is enough. +//// +//// The `__proto__`-as-data guarantee belongs specifically to +//// `object.from_entries`. `set` deliberately preserves ordinary JavaScript +//// assignment semantics, so a key such as `__proto__` can invoke an inherited +//// setter. Do not pass untrusted property names to reflection operations. +//// +//// Object handles and values flow through `glendix/js/object`, so both modules +//// share one typed representation of JavaScript objects and values. +//// + +import glendix/js/object + +/// Represents a JavaScript constructor handle. +pub type JsConstructor + +/// Reads an object property. +pub fn get(from handle: object.JsObject, key key: String) -> object.JsValue { + get_property_raw(handle, key) +} + +/// Mutates an object property and returns the same object handle. +pub fn set( + on handle: object.JsObject, + key key: String, + to value: object.JsValue, +) -> object.JsObject { + set_property_raw(handle, key, value) +} + +/// Deletes an object property and returns the same object handle. +pub fn delete( + from handle: object.JsObject, + key key: String, +) -> object.JsObject { + delete_property_raw(handle, key) +} + +/// Reports whether an object or its prototype chain has the given property. +pub fn has(in handle: object.JsObject, key key: String) -> Bool { + has_property_raw(handle, key) +} + +/// Calls an object method with a list of arguments. +pub fn call_method( + on handle: object.JsObject, + named method: String, + with arguments: List(object.JsValue), +) -> object.JsValue { + call_method_raw(handle, method, arguments) +} + +/// Calls an object method without arguments. +pub fn call_method_without_arguments( + on handle: object.JsObject, + named method: String, +) -> object.JsValue { + call_method_without_arguments_raw(handle, method) +} + +/// Creates an object with JavaScript's `new` operator. +pub fn new_instance( + using constructor: JsConstructor, + with arguments: List(object.JsValue), +) -> object.JsObject { + new_instance_raw(constructor, arguments) +} + +// -- FFI -- +@external(javascript, "./reflect_ffi.mjs", "get_property") +fn get_property_raw(handle: object.JsObject, key: String) -> object.JsValue + +@external(javascript, "./reflect_ffi.mjs", "set_property") +fn set_property_raw( + handle: object.JsObject, + key: String, + value: object.JsValue, +) -> object.JsObject + +@external(javascript, "./reflect_ffi.mjs", "delete_property") +fn delete_property_raw(handle: object.JsObject, key: String) -> object.JsObject + +@external(javascript, "./reflect_ffi.mjs", "has_property") +fn has_property_raw(handle: object.JsObject, key: String) -> Bool + +@external(javascript, "./reflect_ffi.mjs", "call_method") +fn call_method_raw( + handle: object.JsObject, + method: String, + arguments: List(object.JsValue), +) -> object.JsValue + +@external(javascript, "./reflect_ffi.mjs", "call_method_0") +fn call_method_without_arguments_raw( + handle: object.JsObject, + method: String, +) -> object.JsValue + +@external(javascript, "./reflect_ffi.mjs", "new_instance") +fn new_instance_raw( + constructor: JsConstructor, + arguments: List(object.JsValue), +) -> object.JsObject diff --git a/src/glendix/js/reflect_ffi.mjs b/src/glendix/js/reflect_ffi.mjs new file mode 100644 index 0000000..e4191af --- /dev/null +++ b/src/glendix/js/reflect_ffi.mjs @@ -0,0 +1,23 @@ +export function get_property(object, key) { + return object[key]; +} +export function set_property(object, key, value) { + object[key] = value; + return object; +} +export function delete_property(object, key) { + delete object[key]; + return object; +} +export function has_property(object, key) { + return key in object; +} +export function call_method(object, method, args) { + return object[method](...args.toArray()); +} +export function call_method_0(object, method) { + return object[method](); +} +export function new_instance(constructor, args) { + return new constructor(...args.toArray()); +} diff --git a/test/glendix/js/object_test.gleam b/test/glendix/js/object_test.gleam new file mode 100644 index 0000000..013691c --- /dev/null +++ b/test/glendix/js/object_test.gleam @@ -0,0 +1,82 @@ +//// Tests plain JavaScript data-object construction and value coercion. +//// + +import gleeunit/should +import glendix/js/object + +/// Verifies object construction preserves the order of ordinary string keys. +pub fn from_entries_preserves_entry_order_test() -> Nil { + object.from_entries([ + #("theme", object.string("dark")), + #("locale", object.string("en")), + #("density", object.int(2)), + ]) + |> object_json + |> should.equal("{\"theme\":\"dark\",\"locale\":\"en\",\"density\":2}") +} + +/// Verifies an empty entry list safely builds an empty object. +pub fn from_entries_without_entries_is_empty_object_test() -> Nil { + object.from_entries([]) + |> object_json + |> should.equal("{}") +} + +/// Verifies the dedicated empty constructor returns a normal plain object. +pub fn empty_builds_empty_plain_object_test() -> Nil { + let handle = object.empty() + handle + |> object_json + |> should.equal("{}") + handle + |> has_default_prototype + |> should.be_true +} + +/// Verifies a duplicate key keeps the last supplied value. +pub fn from_entries_duplicate_key_keeps_last_value_test() -> Nil { + object.from_entries([ + #("theme", object.string("light")), + #("theme", object.string("dark")), + ]) + |> object_json + |> should.equal("{\"theme\":\"dark\"}") +} + +/// Verifies every typed value coercion preserves its JavaScript representation. +pub fn from_entries_preserves_supported_value_representations_test() -> Nil { + let nested = object.from_entries([#("name", object.string("nested"))]) + object.from_entries([ + #("string", object.string("glendix")), + #("int", object.int(42)), + #("float", object.float(3.5)), + #("true", object.bool(object.TrueValue)), + #("false", object.bool(object.FalseValue)), + #("object", object.from_object(nested)), + ]) + |> object_json + |> should.equal( + "{\"string\":\"glendix\",\"int\":42,\"float\":3.5,\"true\":true,\"false\":false,\"object\":{\"name\":\"nested\"}}", + ) +} + +/// Verifies `__proto__` remains own data without changing the object prototype. +pub fn from_entries_proto_key_is_safe_data_test() -> Nil { + let handle = object.from_entries([#("__proto__", object.string("safe"))]) + handle + |> object_json + |> should.equal("{\"__proto__\":\"safe\"}") + handle + |> proto_key_is_safe_data + |> should.be_true +} + +// -- FFI -- +@external(javascript, "./object_test_ffi.mjs", "object_json") +fn object_json(handle: object.JsObject) -> String + +@external(javascript, "./object_test_ffi.mjs", "proto_key_is_safe_data") +fn proto_key_is_safe_data(handle: object.JsObject) -> Bool + +@external(javascript, "./object_test_ffi.mjs", "has_default_prototype") +fn has_default_prototype(handle: object.JsObject) -> Bool diff --git a/test/glendix/js/object_test_ffi.mjs b/test/glendix/js/object_test_ffi.mjs new file mode 100644 index 0000000..0ae5f1c --- /dev/null +++ b/test/glendix/js/object_test_ffi.mjs @@ -0,0 +1,19 @@ +export function object_json(object) { + return JSON.stringify(object); +} + +export function proto_key_is_safe_data(object) { + const property = Object.getOwnPropertyDescriptor(object, "__proto__"); + return ( + Object.getPrototypeOf(object) === Object.prototype && + property !== undefined && + property.value === "safe" && + property.enumerable === true && + property.writable === true && + property.configurable === true + ); +} + +export function has_default_prototype(object) { + return Object.getPrototypeOf(object) === Object.prototype; +} diff --git a/test/glendix/js/reflect_test.gleam b/test/glendix/js/reflect_test.gleam new file mode 100644 index 0000000..f868eb7 --- /dev/null +++ b/test/glendix/js/reflect_test.gleam @@ -0,0 +1,105 @@ +//// Tests dynamic JavaScript property, method, and constructor reflection. +//// + +import gleeunit/should +import glendix/js/object +import glendix/js/reflect + +/// Verifies reflection reads existing values and preserves missing `undefined`. +pub fn get_reads_existing_and_missing_properties_test() -> Nil { + let handle = object.from_entries([#("theme", object.string("dark"))]) + handle + |> reflect.get(key: "theme") + |> value_to_string + |> should.equal("dark") + handle + |> reflect.get(key: "missing") + |> value_is_undefined + |> should.be_true +} + +/// Verifies a set overwrites in place and returns the same object handle. +pub fn set_overwrites_and_returns_same_handle_test() -> Nil { + let handle = object.from_entries([#("count", object.int(1))]) + let updated = reflect.set(on: handle, key: "count", to: object.int(7)) + same_object(handle, updated) + |> should.be_true + updated + |> reflect.get(key: "count") + |> value_to_string + |> should.equal("7") + updated + |> reflect.set(key: "added", to: object.bool(object.TrueValue)) + |> reflect.get(key: "added") + |> value_to_string + |> should.equal("true") +} + +/// Verifies deleting present or missing properties returns the same handle. +pub fn delete_removes_property_and_returns_same_handle_test() -> Nil { + let handle = object.from_entries([#("theme", object.string("dark"))]) + let cleared = reflect.delete(from: handle, key: "theme") + same_object(handle, cleared) + |> should.be_true + cleared + |> reflect.has(key: "theme") + |> should.be_false + cleared + |> reflect.delete(key: "missing") + |> same_object(cleared) + |> should.be_true +} + +/// Verifies presence checks retain JavaScript prototype-chain semantics. +pub fn has_reports_own_missing_and_inherited_properties_test() -> Nil { + let handle = object.from_entries([#("theme", object.string("dark"))]) + reflect.has(in: handle, key: "theme") + |> should.be_true + reflect.has(in: handle, key: "missing") + |> should.be_false + reflect.has(in: handle, key: "toString") + |> should.be_true +} + +/// Verifies method calls forward ordered arguments to the receiver. +pub fn call_method_passes_arguments_test() -> Nil { + method_object() + |> reflect.call_method(named: "add", with: [object.int(2), object.int(5)]) + |> value_to_string + |> should.equal("10") +} + +/// Verifies argument-free method calls bind the receiver as `this`. +pub fn call_method_without_arguments_reads_receiver_test() -> Nil { + method_object() + |> reflect.call_method_without_arguments(named: "describe") + |> value_to_string + |> should.equal("total:3") +} + +/// Verifies construction forwards every argument and returns the built object. +pub fn new_instance_constructs_object_test() -> Nil { + point_constructor() + |> reflect.new_instance(with: [object.int(3), object.int(4)]) + |> point_summary + |> should.equal("3,4") +} + +// -- FFI -- +@external(javascript, "./reflect_test_ffi.mjs", "value_to_string") +fn value_to_string(value: object.JsValue) -> String + +@external(javascript, "./reflect_test_ffi.mjs", "value_is_undefined") +fn value_is_undefined(value: object.JsValue) -> Bool + +@external(javascript, "./reflect_test_ffi.mjs", "method_object") +fn method_object() -> object.JsObject + +@external(javascript, "./reflect_test_ffi.mjs", "point_constructor") +fn point_constructor() -> reflect.JsConstructor + +@external(javascript, "./reflect_test_ffi.mjs", "same_object") +fn same_object(left: object.JsObject, right: object.JsObject) -> Bool + +@external(javascript, "./reflect_test_ffi.mjs", "point_summary") +fn point_summary(handle: object.JsObject) -> String diff --git a/test/glendix/js/reflect_test_ffi.mjs b/test/glendix/js/reflect_test_ffi.mjs new file mode 100644 index 0000000..cbbb86d --- /dev/null +++ b/test/glendix/js/reflect_test_ffi.mjs @@ -0,0 +1,36 @@ +export function value_to_string(value) { + return String(value); +} + +export function value_is_undefined(value) { + return value === undefined; +} + +export function method_object() { + return { + total: 3, + describe() { + return "total:" + this.total; + }, + add(first, second) { + return this.total + first + second; + }, + }; +} + +export function point_constructor() { + return class ReflectPoint { + constructor(x, y) { + this.x = x; + this.y = y; + } + }; +} + +export function same_object(left, right) { + return left === right; +} + +export function point_summary(point) { + return `${point.x},${point.y}`; +} diff --git a/test/glendix_test.gleam b/test/glendix_test.gleam index 78a70f4..77c23bd 100644 --- a/test/glendix_test.gleam +++ b/test/glendix_test.gleam @@ -298,41 +298,6 @@ pub fn environment_unavailable_match_media_is_unresolved_test() -> Nil { |> should.equal(environment.ResolutionUnavailable) } -/// Verifies object construction preserves entry order. -pub fn object_from_entries_preserves_entry_order_test() -> Nil { - object.from_entries([ - #("theme", object.string("dark")), - #("locale", object.string("en")), - #("density", object.int(2)), - ]) - |> object_json - |> should.equal("{\"theme\":\"dark\",\"locale\":\"en\",\"density\":2}") -} - -/// Verifies an empty entry list safely builds an empty object. -pub fn object_from_entries_without_entries_is_empty_object_test() -> Nil { - object.from_entries([]) - |> object_json - |> should.equal("{}") -} - -/// Verifies a duplicate key keeps the last supplied value. -pub fn object_from_entries_duplicate_key_keeps_last_value_test() -> Nil { - object.from_entries([ - #("theme", object.string("light")), - #("theme", object.string("dark")), - ]) - |> object_json - |> should.equal("{\"theme\":\"dark\"}") -} - -/// Verifies a prototype-looking key remains ordinary own object data. -pub fn object_from_entries_proto_key_is_safe_data_test() -> Nil { - object.from_entries([#("__proto__", object.string("safe"))]) - |> object_json - |> should.equal("{\"__proto__\":\"safe\"}") -} - /// Verifies an object passes through Glendix bindings as one intact prop. pub fn binding_object_prop_preserves_object_test() -> Nil { let configuration = @@ -499,9 +464,6 @@ fn stub_prefers_none() -> Nil @external(javascript, "./glendix_test_ffi.mjs", "clear_match_media") fn clear_match_media() -> Nil -@external(javascript, "./glendix_test_ffi.mjs", "object_json") -fn object_json(handle handle: object.JsObject) -> String - @external(javascript, "./glendix_test_ffi.mjs", "element_prop_json") fn element_prop_json(element element: redraw.Element, key key: String) -> String diff --git a/test/glendix_test_ffi.mjs b/test/glendix_test_ffi.mjs index 47186cc..592e54f 100644 --- a/test/glendix_test_ffi.mjs +++ b/test/glendix_test_ffi.mjs @@ -226,10 +226,6 @@ export function clear_match_media() { delete globalThis.matchMedia; } -export function object_json(object) { - return JSON.stringify(object); -} - export function element_prop_json(element, key) { return JSON.stringify(element.props[key]); }