diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f580aa..f5204cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## main +### API changes + +- Add `Result.forEach` https://github.com/rescript-association/rescript-core/pull/116 + +### Documentation + +- Docstrings for `Object`. Not yet complete. https://github.com/rescript-association/rescript-core/pull/117 + ## 0.2.0 ### API changes diff --git a/src/Core__Object.res b/src/Core__Object.res index e2e14c50..b40a5a15 100644 --- a/src/Core__Object.res +++ b/src/Core__Object.res @@ -1,31 +1,281 @@ -@obj external empty: unit => {..} = "" +/** +`empty` create a new object that inherits the properties and methods from the standard built-in Object, such as `toString`. See [Object on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) -@val external is: ('a, 'b) => bool = "Object.is" +## Examples + +```rescript +let x = Object.empty() +x->Object.keysToArray->Array.length // 0 +x->Object.get("toString")->Option.isSome // true +``` +*/ +@obj +external empty: unit => {..} = "" + +/** +`is` determines if two objects are identical in all contexts. Objects, arrays, records, and other non-primitives are only identical if they reference the **exact** same object in memory. Primitives like ints, floats, and strings are identical if they have the same value. `+0` and `-0` are distinct. NaN is equal to itself. See [Object.is on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) + +In most scenarios use `==` or `===` or the custom `equals` function (if provided) for the type. + +## Examples + +```rescript +Object.is(25, 13) // false +Object.is("abc", "abc") // true +Object.is(undefined, undefined) // true +Object.is(undefined, null) // false +Object.is(-0.0, 0.0) // false +Object.is(list{1, 2}, list{1, 2}) // false + +Object.is([1, 2, 3], [1, 2, 3]) // false +[1, 2, 3] == [1, 2, 3] // true +[1, 2, 3] === [1, 2, 3] // false + +let fruit = {"name": "Apple" } +Object.is(fruit, fruit) // true +Object.is(fruit, {"name": "Apple" }) // false +fruit == {"name": "Apple" } // true +fruit === {"name": "Apple" } // false +``` +*/ +@val +external is: ('a, 'a) => bool = "Object.is" + +/** +`create` creates a new object, using an existing object as the prototype of the new object. See [Object.create on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) + +**Note:** ReScript provides [first-class support for immutable objects](https://rescript-lang.org/docs/manual/latest/object) and [records](https://rescript-lang.org/docs/manual/latest/record). This is often safer and more convenient than using `create` and other functions in this module. + +## Examples + +```rescript +let x = {"fruit": "banana"} +let y = Object.create(x) +y->Object.get("fruit") // Some("banana") +``` +*/ +@val +external create: {..} => {..} = "Object.create" -@val external create: {..} => {..} = "Object.create" @val external createWithProperties: ({..}, {..}) => {..} = "Object.create" + @val external createWithNull: (@as(json`null`) _, unit) => {..} = "Object.create" + @val external createWithNullAndProperties: (@as(json`null`) _, {..}) => {..} = "Object.create" -@val external assign: ({..}, {..}) => {..} = "Object.assign" -@variadic @val external assignMany: ({..}, array<{..}>) => {..} = "Object.assign" -@val external copy: (@as(json`{}`) _, {..}) => {..} = "Object.assign" +/** +`assign(target, source)` copies enumerable own properties from the source to the target, overwriting properties with the same name. It returns the modified target object. A deep clone is not created; properties are copied by reference. + +**Warning:** ReScript provides compile-time support for type-safe access to JavaScript objects. This eliminates common errors such as accessing properties that do not exist, or using a property of type x as if it were a y. Using `assign` can bypass these safety checks and lead to run-time errors (if you are not careful). ReScript provides [first-class support for immutable objects](https://rescript-lang.org/docs/manual/latest/object) and [records](https://rescript-lang.org/docs/manual/latest/record). This is often safer and more convenient than using `assign` and other functions in this module. + +See [Object.assign on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) or [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign). + +## Examples + +```rescript +Object.assign({"a": 1}, {"a": 2}) // {"a": 2} +Object.assign({"a": 1, "b": 2}, {"a": 0}) // {"a": 0, "b": 2} +Object.assign({"a": 1}, {"a": null}) // {"a": null} +``` +*/ +@val +external assign: ({..}, {..}) => {..} = "Object.assign" + +/** +`assignMany(target, sources)` copies enumerable own properties from each source to the target, overwriting properties with the same name. Later sources' properties overwrite earlier ones. It returns the modified target object. A deep clone is not created; properties are copied by reference. + +**Note:** ReScript provides [first-class support for immutable objects](https://rescript-lang.org/docs/manual/latest/object), including spreading one object into another. This is often more convenient than using `assign` or `assignMany`. + +See [Object.assign on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) or [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign). +*/ +@variadic +@val +external assignMany: ({..}, array<{..}>) => {..} = "Object.assign" + +@val external copy: (@as(json`{}`) _, {..} as 'a) => 'a = "Object.assign" + +/** +`get` gets the value of a property by name. Returns `None` if the property does not exist or has the value `undefined`. Otherwise returns `Some`, including if the value is `null`. + +## Examples + +```rescript +{"a": 1}->Object.get("a") // Some(1) +{"a": 1}->Object.get("b") // None +{"a": undefined}->Object.get("a") // None +{"a": null}->Object.get("a") // Some(null) +{"a": 1}->Object.get("toString")->Option.isSome // true +``` +*/ +@get_index +external get: ({..}, string) => option<'a> = "" + +/** +`getSymbol` gets the value of a property by symbol. Returns `None` if the property does not exist or has the value `undefined`. Otherwise returns `Some`, including if the value is `null`. + +## Examples + +```rescript +let fruit = Symbol.make("fruit") +let x = Object.empty() +x->Object.setSymbol(fruit, "banana") +x->Object.getSymbol(fruit) // Some("banana") +``` +*/ +@get_index +external getSymbol: ({..}, Core__Symbol.t) => option<'a> = "" -@get_index external get: ({..}, string) => option<'a> = "" -@get_index external getSymbol: ({..}, Core__Symbol.t) => option<'a> = "" @get_index external getSymbolUnsafe: ({..}, Core__Symbol.t) => 'a = "" -@set_index external set: ({..}, string, 'a) => unit = "" +/** +`set(name, value)` assigns a value to the named object property, overwriting the previous value if any. See [Working with Objects on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#objects_and_properties) + +## Examples + +```rescript +{"a": 1}->Object.set("a", 2) // {"a": 2} +{"a": 1}->Object.set("a", None) // {"a": None} +{"a": 1}->Object.set("b", 2) // {"a": 1, "b": 2} +``` +*/ +@set_index +external set: ({..}, string, 'a) => unit = "" + @set_index external setSymbol: ({..}, Core__Symbol.t, 'a) => unit = "" -@val external keysToArray: {..} => array = "Object.keys" +/** +`keysToArray` returns an array of an object's own enumerable string-keyed property names. See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.keys) +or [Object.keys on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys). + +## Examples + +```rescript +{"a": 1, "b": 2}->Object.keysToArray // ["a", "b"] +{"a": None}->Object.keysToArray // ["a"] +Object.empty()->Object.keysToArray // [] +``` +*/ +@val +external keysToArray: {..} => array = "Object.keys" + +/** +`hasOwnProperty` determines whether the object has the specified property as its **own** property, as opposed to inheriting it. See [hasOwnProperty on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty) + +## Examples + +```rescript +let point = {"x": 1, "y": 2} +{"a": 1}->hasOwnProperty("a") // true +{"a": 1}->hasOwnProperty("b") // false +{"a": 1}->hasOwnProperty("toString") // false +``` +*/ +@val +external hasOwnProperty: ({..}, string) => bool = "Object.prototype.hasOwnProperty.call" + +/** +`seal` seals an object. Sealing an object prevents extensions and makes existing properties non-configurable. A sealed object has a fixed set of properties. Unlike `freeze`, values of existing properties can still be changed as long as they are writable. + +**Note:** `seal` returns the same object that was passed in; it does not create a copy. Any attempt to delete or add properties to a sealed object will fail, either silently or by throwing an error. + +See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.seal) and [Object.seal on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) + +## Examples + +```rescript +let point = {"x": 1, "y": 2} +point->Object.set("x", -7) // succeeds +point->Object.seal->ignore +point->Object.set("z", 9) // fails +point->Object.set("x", 13) // succeeds +``` +*/ +@val +external seal: ({..} as 'a) => 'a = "Object.seal" + +/** +`preventExtensions` prevents new properties from being added to the object. It modifies the object (rather than creating a copy) and returns it. + +See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.preventextensions) and [Object.preventExtensions on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) + +## Examples + +```rescript +let obj = {"a": 1} +obj->Object.set("b", 2) // succeeds +obj->Object.preventExtensions->ignore +obj->Object.set("c", 3) // fails +``` +*/ +@val +external preventExtensions: ({..} as 'a) => 'a = "Object.preventExtensions" + +/** +`freeze` freezes an object. Freezing an object makes existing properties non-writable and prevents extensions. Once an object is frozen, new properties cannot be be added, existing properties cannot be removed, and their values cannot be changed. + +**Note:** `freeze` returns the same object that was passed in; it does not create a frozen copy. Any attempt to change a frozen object will fail, either silently or by throwing an exception. + +See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.isfrozen) and [Object.isFrozen on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen). + +## Examples + + ```rescript +let obj = {"a": 1} +obj->Object.set("a", 2) // succeeds +obj->Object.freeze->ignore +obj->Object.set("a", 3) // fails +``` +*/ +@val +external freeze: ({..} as 'a) => 'a = "Object.freeze" + +/** +`isSealed` determines if an object is sealed. A sealed object has a fixed set of properties. + +See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.issealed) and [Object.isSealed on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) + +## Examples + +```rescript +let point = {"x": 1, "y": 3}->Object.seal +let pointIsSealed = point->Object.isSealed // true +let fruit = {"name": "Apple" } +let fruitIsSealed = fruit->Object.isSealed // false + ``` +*/ +@val +external isSealed: 'a => bool = "Object.isSealed" + +/** +`isFrozen` determines if an object is frozen. An object is frozen if an only if it is not extensible, all its properties are non-configurable, and all its data properties are non-writable. + +See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.isfrozen) and [Object.isFrozen on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen). + +## Examples + +```rescript +let point = {"x": 1, "y": 3}->Object.freeze +let pointIsFrozen = point->Object.isFrozen // true +let fruit = {"name": "Apple" } +let fruitIsFrozen = fruit->Object.isFrozen // false + ``` +*/ +@val +external isFrozen: 'a => bool = "Object.isFrozen" + +/** +`isExtensible` determines if an object is extensible (whether it can have new properties added to it). -@val external hasOwnProperty: ({..}, string) => bool = "Object.prototype.hasOwnProperty.call" +See [ECMAScript Language Specification](https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.isextensible) and [Object.isExtensible on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) -@val external seal: 'a => 'a = "Object.seal" -@val external preventExtensions: 'a => 'a = "Object.preventExtensions" -@val external freeze: 'a => 'a = "Object.freeze" +## Examples -@val external isSealed: 'a => bool = "Object.isSealed" -@val external isFrozen: 'a => bool = "Object.isFrozen" -@val external isExtensible: 'a => bool = "Object.isExtensible" +```rescript +let obj = {"a": 1} +obj->Object.isExtensible // true +obj->Object.preventExtensions->ignore +obj->Object.isExtensible // false +``` +*/ +@val +external isExtensible: 'a => bool = "Object.isExtensible" diff --git a/test/ObjectTests.mjs b/test/ObjectTests.mjs new file mode 100644 index 00000000..5b2ac5cd --- /dev/null +++ b/test/ObjectTests.mjs @@ -0,0 +1,636 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Test from "./Test.mjs"; +import * as Curry from "rescript/lib/es6/curry.js"; +import * as Caml_obj from "rescript/lib/es6/caml_obj.js"; +import * as Core__Option from "../src/Core__Option.mjs"; + +var eq = Caml_obj.equal; + +Test.run([ + [ + "ObjectTests.res", + 12, + 20, + 30 + ], + "is: ints" + ], Object.is(25, 25), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 14, + 20, + 33 + ], + "is: strings" + ], Object.is("abc", "abc"), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 15, + 20, + 33 + ], + "is: strings" + ], Object.is("abc", "ABC"), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 17, + 20, + 44 + ], + "is: null and undefined" + ], Object.is(null, undefined), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 18, + 20, + 44 + ], + "is: null and undefined" + ], Object.is(undefined, undefined), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 19, + 20, + 44 + ], + "is: null and undefined" + ], Object.is(null, null), eq, true); + +var nums = [ + 1, + 2, + 3 +]; + +Test.run([ + [ + "ObjectTests.res", + 22, + 20, + 32 + ], + "is: arrays" + ], Object.is([ + 1, + 2, + 3 + ], [ + 1, + 2, + 3 + ]), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 23, + 20, + 32 + ], + "is: arrays" + ], Object.is(nums, nums), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 24, + 20, + 32 + ], + "is: arrays" + ], Caml_obj.equal([ + 1, + 2, + 3 + ], [ + 1, + 2, + 3 + ]), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 25, + 20, + 32 + ], + "is: arrays" + ], [ + 1, + 2, + 3 + ] === [ + 1, + 2, + 3 + ], eq, false); + +Test.run([ + [ + "ObjectTests.res", + 27, + 20, + 30 + ], + "is: list" + ], Object.is({ + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } + }, { + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } + }), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 28, + 20, + 30 + ], + "is: list" + ], Caml_obj.equal({ + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } + }, { + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } + }), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 29, + 20, + 30 + ], + "is: list" + ], ({ + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } + }) === ({ + hd: 1, + tl: { + hd: 2, + tl: { + hd: 3, + tl: /* [] */0 + } + } + }), eq, false); + +var d = new Date(2000, 1); + +Test.run([ + [ + "ObjectTests.res", + 33, + 13, + 23 + ], + "is: date" + ], Object.is(new Date(2000, 1), new Date(2000, 1)), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 38, + 20, + 30 + ], + "is: date" + ], Object.is(d, d), eq, true); + +var x = { + a: 1 +}; + +Test.run([ + [ + "ObjectTests.res", + 41, + 20, + 33 + ], + "is: objects" + ], Object.is(x, x), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 42, + 20, + 33 + ], + "is: objects" + ], Object.is({ + a: 1 + }, { + a: 1 + }), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 43, + 20, + 33 + ], + "is: objects" + ], Object.is({}, {}), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 44, + 20, + 45 + ], + "is: === and == operator" + ], x === x, eq, true); + +Test.run([ + [ + "ObjectTests.res", + 45, + 20, + 45 + ], + "is: === and == operator" + ], Caml_obj.equal(x, x), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 46, + 20, + 45 + ], + "is: === and == operator" + ], Caml_obj.equal({ + a: 1 + }, { + a: 1 + }), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 48, + 20, + 31 + ], + "is: zeros" + ], Object.is(0, 0), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 49, + 20, + 31 + ], + "is: zeros" + ], Object.is(-0.0, -0.0), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 50, + 20, + 31 + ], + "is: zeros" + ], Object.is(0.0, -0.0), eq, false); + +function mkBig(s) { + return BigInt(s); +} + +Test.run([ + [ + "ObjectTests.res", + 53, + 20, + 32 + ], + "is: bigint" + ], Object.is(BigInt("123456789"), BigInt("123456789")), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 54, + 20, + 32 + ], + "is: bigint" + ], Object.is(BigInt("123489"), BigInt("123456789")), eq, false); + +Test.run([ + [ + "ObjectTests.res", + 55, + 20, + 32 + ], + "is: bigint" + ], Object.is(BigInt("000000000"), BigInt("0")), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 56, + 20, + 32 + ], + "is: bigint" + ], Caml_obj.equal(BigInt("123"), BigInt("123")), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 57, + 20, + 32 + ], + "is: bigint" + ], BigInt("123") === BigInt("123"), eq, true); + +Test.run([ + [ + "ObjectTests.res", + 62, + 13, + 50 + ], + "assign copies from source to target" + ], Object.assign({ + a: 1, + b: 2 + }, { + b: 3, + c: 0 + }), eq, { + a: 1, + b: 3, + c: 0 + }); + +function assignOverwritesTarget(title, source) { + var sourceObj = { + a: source + }; + Test.run([ + [ + "ObjectTests.res", + 70, + 22, + 39 + ], + "assign " + title + "" + ], Object.assign({ + a: 1 + }, sourceObj), eq, sourceObj); + Test.run([ + [ + "ObjectTests.res", + 71, + 22, + 39 + ], + "assign " + title + "" + ], Object.assign({ + a: undefined + }, sourceObj), eq, sourceObj); + Test.run([ + [ + "ObjectTests.res", + 72, + 22, + 39 + ], + "assign " + title + "" + ], Object.assign({ + a: null + }, sourceObj), eq, sourceObj); +} + +assignOverwritesTarget("when source is undefined", undefined); + +assignOverwritesTarget("when source is null", null); + +assignOverwritesTarget("when source is a number", 1); + +assignOverwritesTarget("when source is a string", "abc"); + +function runGetTest(i) { + Test.run([ + [ + "ObjectTests.res", + 90, + 22, + 46 + ], + "Object.get: " + i.title + "" + ], Curry._1(i.get, Curry._1(i.source, undefined)), eq, i.expected); +} + +runGetTest({ + title: "prop exists, return Some", + source: (function (param) { + return { + a: 1 + }; + }), + get: (function (__x) { + return __x["a"]; + }), + expected: 1 + }); + +runGetTest({ + title: "prop NOT exist, return None", + source: (function (param) { + return { + a: 1 + }; + }), + get: (function (i) { + return i["banana"]; + }), + expected: undefined + }); + +runGetTest({ + title: "prop like toString, return Some", + source: (function (param) { + return { + a: 1 + }; + }), + get: (function (i) { + return Core__Option.isSome(i["toString"]); + }), + expected: true + }); + +runGetTest({ + title: "prop exist but explicitly undefined, return None", + source: (function (param) { + return { + a: undefined + }; + }), + get: (function (i) { + return i["a"]; + }), + expected: undefined + }); + +runGetTest({ + title: "prop exist but explicitly null, return None", + source: (function (param) { + return { + a: null + }; + }), + get: (function (i) { + return i["a"]; + }), + expected: null + }); + +runGetTest({ + title: "prop exists and is an array, can get it", + source: (function (param) { + return { + a: [ + 1, + 2, + 3 + ] + }; + }), + get: (function (i) { + return Core__Option.getWithDefault(Core__Option.map(i["a"], (function (i) { + return i.concat([ + 4, + 5 + ]); + })), []); + }), + expected: [ + 1, + 2, + 3, + 4, + 5 + ] + }); + +function getSymbolTestWhenExists(param) { + var obj = {}; + var fruit = Symbol("fruit"); + obj[fruit] = "banana"; + var retrieved = obj[fruit]; + Test.run([ + [ + "ObjectTests.res", + 150, + 15, + 63 + ], + "Object.getSymbol when exists return it as Some" + ], retrieved, eq, "banana"); +} + +getSymbolTestWhenExists(undefined); + +Test.run([ + [ + "ObjectTests.res", + 159, + 13, + 65 + ], + "Object.getSymbol when not exists return it as None" + ], ({})[Symbol("fruit")], eq, undefined); + +Test.run([ + [ + "ObjectTests.res", + 168, + 13, + 46 + ], + "Object.create clones properties" + ], Object.create({ + a: 1 + })["a"], eq, 1); + +Test.run([ + [ + "ObjectTests.res", + 175, + 13, + 46 + ], + "Object.create clones properties" + ], Object.create({ + a: 1 + })["b"], eq, undefined); + +export { + eq , + nums , + d , + x , + mkBig , + assignOverwritesTarget , + runGetTest , + getSymbolTestWhenExists , +} +/* Not a pure module */ diff --git a/test/ObjectTests.res b/test/ObjectTests.res new file mode 100644 index 00000000..6f888696 --- /dev/null +++ b/test/ObjectTests.res @@ -0,0 +1,180 @@ +open RescriptCore + +let eq = (a, b) => a == b + +// ===== is ===== + +// == Removed when argument types were changed to be the same == +// Test.run(__POS_OF__("is: different types"), Object.is("abc", false), eq, false) +// Test.run(__POS_OF__("is: null and undefined"), Object.is(null, None), eq, false) +// Test.run(__POS_OF__("is: undefined and None"), Object.is(undefined, None), eq, true) + +Test.run(__POS_OF__("is: ints"), Object.is(25, 25), eq, true) + +Test.run(__POS_OF__("is: strings"), Object.is("abc", "abc"), eq, true) +Test.run(__POS_OF__("is: strings"), Object.is("abc", "ABC"), eq, false) + +Test.run(__POS_OF__("is: null and undefined"), Object.is(null, undefined), eq, false) +Test.run(__POS_OF__("is: null and undefined"), Object.is(undefined, undefined), eq, true) +Test.run(__POS_OF__("is: null and undefined"), Object.is(null, null), eq, true) + +let nums = [1, 2, 3] +Test.run(__POS_OF__("is: arrays"), Object.is([1, 2, 3], [1, 2, 3]), eq, false) +Test.run(__POS_OF__("is: arrays"), Object.is(nums, nums), eq, true) +Test.run(__POS_OF__("is: arrays"), [1, 2, 3] == [1, 2, 3], eq, true) +Test.run(__POS_OF__("is: arrays"), [1, 2, 3] === [1, 2, 3], eq, false) + +Test.run(__POS_OF__("is: list"), Object.is(list{1, 2, 3}, list{1, 2, 3}), eq, false) +Test.run(__POS_OF__("is: list"), list{1, 2, 3} == list{1, 2, 3}, eq, true) +Test.run(__POS_OF__("is: list"), list{1, 2, 3} === list{1, 2, 3}, eq, false) + +let d = Date.makeWithYM(~year=2000, ~month=1) +Test.run( + __POS_OF__("is: date"), + Object.is(Date.makeWithYM(~year=2000, ~month=1), Date.makeWithYM(~year=2000, ~month=1)), + eq, + false, +) +Test.run(__POS_OF__("is: date"), Object.is(d, d), eq, true) + +let x = {"a": 1} +Test.run(__POS_OF__("is: objects"), Object.is(x, x), eq, true) +Test.run(__POS_OF__("is: objects"), Object.is({"a": 1}, {"a": 1}), eq, false) +Test.run(__POS_OF__("is: objects"), Object.is(Object.empty(), Object.empty()), eq, false) // hmm... +Test.run(__POS_OF__("is: === and == operator"), x === x, eq, true) +Test.run(__POS_OF__("is: === and == operator"), x == x, eq, true) +Test.run(__POS_OF__("is: === and == operator"), {"a": 1} == {"a": 1}, eq, true) + +Test.run(__POS_OF__("is: zeros"), Object.is(-0, -0), eq, true) +Test.run(__POS_OF__("is: zeros"), Object.is(-0.0, -0.0), eq, true) +Test.run(__POS_OF__("is: zeros"), Object.is(0.0, -0.0), eq, false) + +let mkBig = s => BigInt.fromString(s) +Test.run(__POS_OF__("is: bigint"), Object.is(mkBig("123456789"), mkBig("123456789")), eq, true) +Test.run(__POS_OF__("is: bigint"), Object.is(mkBig("123489"), mkBig("123456789")), eq, false) +Test.run(__POS_OF__("is: bigint"), Object.is(mkBig("000000000"), mkBig("0")), eq, true) +Test.run(__POS_OF__("is: bigint"), mkBig("123") == mkBig("123"), eq, true) +Test.run(__POS_OF__("is: bigint"), mkBig("123") === mkBig("123"), eq, true) + +// ====== assign ====== + +Test.run( + __POS_OF__("assign copies from source to target"), + Object.assign({"a": 1, "b": 2}, {"b": 3, "c": 0}), + eq, + {"a": 1, "b": 3, "c": 0}, +) + +let assignOverwritesTarget = (~title, ~source) => { + let sourceObj = {"a": source} + Test.run(__POS_OF__(`assign ${title}`), Object.assign({"a": 1}, sourceObj), eq, sourceObj) + Test.run(__POS_OF__(`assign ${title}`), Object.assign({"a": undefined}, sourceObj), eq, sourceObj) + Test.run(__POS_OF__(`assign ${title}`), Object.assign({"a": null}, sourceObj), eq, sourceObj) +} + +assignOverwritesTarget(~title="when source is undefined", ~source=undefined) +assignOverwritesTarget(~title="when source is null", ~source=null) +assignOverwritesTarget(~title="when source is a number", ~source=1) +assignOverwritesTarget(~title="when source is a string", ~source="abc") + +// ===== get ===== + +type getTestData<'obj, 'res, 'expected> = { + title: string, + source: unit => 'obj, + get: 'obj => 'res, + expected: 'expected, +} + +let runGetTest = i => + Test.run(__POS_OF__(`Object.get: ${i.title}`), i.source()->i.get, eq, i.expected) + +{ + title: "prop exists, return Some", + source: () => {"a": 1}, + get: Object.get(_, "a"), + expected: Some(1), +}->runGetTest + +{ + title: "prop NOT exist, return None", + source: () => {"a": 1}, + get: i => i->Object.get("banana"), + expected: None, +}->runGetTest + +{ + title: "prop like toString, return Some", + source: () => {"a": 1}, + get: i => i->Object.get("toString")->Option.isSome, + expected: true, +}->runGetTest + +{ + title: "prop exist but explicitly undefined, return None", + source: () => {"a": undefined}, + get: i => i->Object.get("a"), + expected: None, +}->runGetTest + +{ + title: "prop exist but explicitly null, return None", + source: () => {"a": null}, + get: i => i->Object.get("a"), + expected: Some(null), +}->runGetTest + +{ + title: "prop exists and is an array, can get it", + source: () => {"a": [1, 2, 3]}, + get: i => i->Object.get("a")->Option.map(i => i->Array.concat([4, 5]))->Option.getWithDefault([]), + expected: [1, 2, 3, 4, 5], +}->runGetTest + +// This throws an exception +// { +// title: "prop exists but casted wrong on get", +// source: () => {"a": 34}, +// get: i => i->Object.get("a")->Option.map(i => i->Array.concat([4, 5]))->Option.getWithDefault([]), +// expected: [], +// }->runGetTest + +// ===== getSymbol ===== + +let getSymbolTestWhenExists = () => { + let obj = Object.empty() + let fruit = Symbol.make("fruit") + obj->Object.setSymbol(fruit, "banana") + let retrieved = obj->Object.getSymbol(fruit) + Test.run( + __POS_OF__(`Object.getSymbol when exists return it as Some`), + retrieved, + eq, + Some("banana"), + ) +} +getSymbolTestWhenExists() + +Test.run( + __POS_OF__(`Object.getSymbol when not exists return it as None`), + Object.empty()->Object.getSymbol(Symbol.make("fruit")), + eq, + None, +) + +// ===== create ===== + +Test.run( + __POS_OF__(`Object.create clones properties`), + {"a": 1}->Object.create->Object.get("a"), + eq, + Some(1), +) + +Test.run( + __POS_OF__(`Object.create clones properties`), + {"a": 1}->Object.create->Object.get("b"), + eq, + None, +) + diff --git a/test/TestSuite.mjs b/test/TestSuite.mjs index c968bd8f..059ea368 100644 --- a/test/TestSuite.mjs +++ b/test/TestSuite.mjs @@ -4,6 +4,7 @@ import * as IntTests from "./IntTests.mjs"; import * as TestTests from "./TestTests.mjs"; import * as ArrayTests from "./ArrayTests.mjs"; import * as ErrorTests from "./ErrorTests.mjs"; +import * as ObjectTests from "./ObjectTests.mjs"; import * as PromiseTest from "./PromiseTest.mjs"; var bign = TestTests.bign; @@ -26,10 +27,24 @@ var Concurrently = PromiseTest.Concurrently; var panicTest = ErrorTests.panicTest; -var eq = IntTests.eq; - var $$catch = IntTests.$$catch; +var eq = ObjectTests.eq; + +var nums = ObjectTests.nums; + +var d = ObjectTests.d; + +var x = ObjectTests.x; + +var mkBig = ObjectTests.mkBig; + +var assignOverwritesTarget = ObjectTests.assignOverwritesTarget; + +var runGetTest = ObjectTests.runGetTest; + +var getSymbolTestWhenExists = ObjectTests.getSymbolTestWhenExists; + export { bign , TestError , @@ -41,7 +56,14 @@ export { Catching , Concurrently , panicTest , - eq , $$catch , + eq , + nums , + d , + x , + mkBig , + assignOverwritesTarget , + runGetTest , + getSymbolTestWhenExists , } /* IntTests Not a pure module */ diff --git a/test/TestSuite.res b/test/TestSuite.res index 6277bf57..ca2a63ca 100644 --- a/test/TestSuite.res +++ b/test/TestSuite.res @@ -3,3 +3,4 @@ include PromiseTest include ErrorTests include ArrayTests include IntTests +include ObjectTests