UNPKG

lively.lang

Version:

JavaScript utils providing useful abstractions for working with collections, functions, objects.

584 lines (483 loc) 18.3 kB
/*global beforeEach, afterEach, describe, it, setInterval, clearInterval, setTimeout,System*/ import { expect } from "mocha-es6"; import { isObject, newKeyIn, sortKeysWithBeforeAndAfterConstraints, select, extend, inspect, equals, keys, isRegExp, isFunction, extract, isEmpty, deepCopy, inherit, values, merge, clone, isBoolean, dissoc, isString, isElement, isArray, deepMerge, isNumber, isUndefined, typeStringOf, safeToString, isMutableType, shortPrintStringOf, mergePropertyInHierarchy } from "../object.js"; var isNodejs = System.get("@system-env").node; var GLOBAL = System.global; describe('object', function() { var obj1 = { foo: 23, bar: [2, {x: "'test'"}], get baz() { return "--baz getter--"; }, method: function(arg1, arg2) { return arg1 + arg2; } } var obj2 = { foo: 24, zork: "test", method2: function(arg) { return arg + 1; } } obj1.__proto__ = obj2; describe('type testing', function() { it("isElement", function() { if (typeof document === "undefined") return; var el = document.createElement("div"); expect(isElement(el)).to.equal(true); expect(isElement({})).to.equal(false); }); it("isArray", function() { expect(isArray([1,2,3])).to.equal(true); expect(isArray([])).to.equal(true); expect(isArray({})).to.equal(false); }); it("isFunction", function() { expect(isFunction(function() {})).to.equal(true); expect(isFunction({})).to.equal(false); }); it("isBoolean", function() { expect(isBoolean(false)).to.equal(true); expect(isBoolean({})).to.equal(false); }); it("isString", function() { expect(isString("bla bla")).to.equal(true); expect(isString({})).to.equal(false); }); it("isNumber", function() { expect(isNumber(23)).to.equal(true); expect(isNumber({})).to.equal(false); }); it("isUndefined", function() { expect(isUndefined(undefined)).to.equal(true); expect(isUndefined(null)).to.equal(false); expect(isUndefined("")).to.equal(false); expect(isUndefined({})).to.equal(false); }); it("isRegExp", function() { expect(isRegExp(/fooo/)).to.equal(true); expect(isRegExp({})).to.equal(false); expect(isRegExp(function() {})).to.equal(false); }); it("isObject", function() { expect(isObject({})).to.equal(true); expect(isObject("foo")).to.equal(false); expect(isObject(/123/)).to.equal(true); expect(isObject([])).to.equal(true); expect(isObject(function() {})).to.equal(false); }); it("isEmpty", function() { expect(isEmpty({})).to.equal(true); expect(isEmpty({fOO: 23})).to.equal(false); expect(isEmpty(Object.create(obj1))).to.equal(true); }); }); describe("equality", function() { it("compares structures of objects", function() { var a = {foo: {bar: {baz: 23, m: function() { return 23; }}}}, b = {foo: {bar: {baz: 23, m: function() { return 23; }}}}, c = {foo: {bar: {baz: 24, m: function() { return 23; }}}}; expect(equals(a,a)).to.equal(true); expect(equals(a,b)).to.equal(true); expect(equals(a,c)).to.equal(false); expect(equals(b,c)).to.equal(false); expect(equals(c,c)).to.equal(true); expect(equals(GLOBAL,GLOBAL)).to.equal(true); }); it("works with arrays", function() { var a = {foo: [{bar: 23}]}, b = {foo: [{bar: 23}]}, c = {foo: [{bar: 24}]}; expect(equals(a,a)).to.equal(true); expect(equals(a,b)).to.equal(true); expect(equals(a,c)).to.equal(false); expect(equals(b,c)).to.equal(false); expect(equals(c,c)).to.equal(true); }); it("works with objects nested in arrays", function() { expect(equals([{}], [{}])).to.equal(true); }); it("null equality", function() { expect(equals(0, null)).equals(false, `0 and null`); expect(equals({ foo: 0 }, { foo: null })).equals(false, `{ foo: 0 }, { foo: null }`); }); }); describe('accessing', function() { it('enumerates keys', function() { expect(keys(obj1)).to.eql(['foo', 'bar', 'baz', 'method']); }); it('enumerates values', function() { expect(values(obj1)).to.eql([obj1.foo, obj1.bar, obj1.baz, obj1.method]); }); }); describe('extend', function() { it("adds and overwrites properties", function() { var o = {baz: 99, bar: 66}; var extended = extend(o, {foo: 23, bar: {x: 3}}); expect(extended).to.equal(o, "identity issue"); expect(extended).to.eql({baz: 99, foo: 23, bar: {x: 3}}); }); it("is getter/setter aware", function() { var o = extend({}, { get foo() { return this._foo; }, set foo(v) { return this._foo = v + 1; } }); o.foo = 3; expect(o.foo).to.equal(4); }); it("sets display name", function() { var o = extend({}, {foo: function() { return "bar"; }}); expect(o.foo.displayName).to.equal("foo"); }); it("does not override existing function names", function() { var o = extend({}, {foo: function myFoo() { return "bar"; }}); expect(o.foo.name).to.equal("myFoo"); expect(o.foo).to.have.property("displayName"); expect(o.foo.displayName).to.equal("foo"); }); it("sets categories", function() { var dest = {}; extend(dest, "cat1", { m1: function() { return 3; }, m2: function() { return 4; } }, "cat2", { foo: 33 }); expect(dest.categories).to.eql({cat1: ["m1","m2"],cat2: ["foo"]}); }); }); describe("extract", function() { it("it creates a new object from a list of properties", function() { var obj1 = {foo: 23, bar: {x: 24}}; var obj2 = extract(obj1, ["foo", "bar", "baz"]); expect(obj1).to.not.equal(obj2); expect(obj1).to.eql(obj2); expect(obj2).to.not.have.property("baz"); }); it("it can map properties", function() { var obj1 = {foo: 23, bar: {x: 24}}; var obj2 = extract(obj1, ["foo", "baz"], function(k, val) { return val + 1; }); expect(obj2).to.eql({foo: 24}); }); }); describe("inspect", function() { it("prints object representation", function() { expect(inspect(obj1)).to.equal( "{\n" + " bar: [2, {\n" + " x: \"'test'\"\n" + " }],\n" + " baz: \"--baz getter--\",\n" + " foo: 23,\n" + " method: function method(arg1,arg2) {/*...*/}\n" + "}"); }); it("observes maxDepth when printing", function() { expect(inspect(obj1, {maxDepth: 1})).to.equal( "{\n" + " bar: [/*...*/],\n" + " baz: \"--baz getter--\",\n" + " foo: 23,\n" + " method: function method(arg1,arg2) {/*...*/}\n" + "}"); }); it("uses custom printer", function() { function customPrinter(val, ignore) { return typeof val === "number" ? val + 1 : ignore; } expect(inspect(obj1, {maxDepth: 1, customPrinter: customPrinter})).equal( "{\n" + " bar: [/*...*/],\n" + " baz: \"--baz getter--\",\n" + " foo: 24,\n" + " method: function method(arg1,arg2) {/*...*/}\n" + "}") }); }); describe("merge", function() { it("merges objects", function() { var obj1 = {foo: 23, bar: [2, {x: "'test'"}]}; var obj2 = {foo: 24, zork: "test"}; var merged = merge(obj1, obj2); expect(merged.foo).to.equal(24); expect(obj1.foo).to.equal(23); expect(obj2.foo).to.equal(24); expect(merged).to.have.property("bar"); expect(merged).to.have.property("zork"); }); it("merges arrays", function() { expect(merge([1,2], [6,7])).to.eql([1,2,6,7]); }); it("merges hierarchies", function() { var obj1 = {foo: {a: 23, b: 4}}, obj2 = {foo: {a: 24, c: 5}} obj1.__proto__ = obj2; var merged = mergePropertyInHierarchy(obj1, "foo"); expect(merged).to.eql({a: 23, b: 4, c: 5}); }); }); describe("deep merge", function() { it("combines objects", function() { expect(deepMerge({a: 23, b: {x: 2}}, {a: 24, b: {y: 3}})) .to.eql({a: 24, b: {x: 2, y: 3}}); }); it("combines arrays", function() { expect(deepMerge([{a: 23}, {b: {x: 2}}], [{a: 24}, {b: {y: 3}}, {c: 3}])) .to.eql([{a: 24}, {b: {x: 2, y: 3}}, {c: 3}]); }); it("identical", function() { expect(deepMerge({a: [1,2,3], c: 3}, {a: [1,2,3], c: 3})) .to.eql({a: [1,2,3], c: 3}); }); it("merges non obj props left to right", function() { expect( deepMerge( {a: [{}, 2, 3], b: 3, c: {}}, {a: [1, {}, 3], b: {}, c: 4})) .to.deep.equal( {a: [1,{},3], b: {}, c: 4}); }); it("doesnt merge array and obj", function() { expect(deepMerge({a: [1,2,3]}, {a: {"0": "x", "1": "y"}})) .to.eql({a: {"0": "x", "1": "y"}}); }); it("cannot deal with circular refs", function() { var obj1 = {}, obj2 = {}; obj1.ref = obj2; obj2.ref = obj1; expect(function() { deepMerge(obj1, obj2) }).throws(); }); }); describe("select keys", function() { it("does what it says", function() { expect(select({a: 1, b: 2, c: 3}, ["a", "b"])).eql({a: 1, b: 2}) }); }); describe("dissoc", function() { it("does what it says", function() { var o = {a: 1, b: 2, c: 3}, result = dissoc(o, ["a", "c"]); expect(o).eql({a: 1, b: 2, c: 3}) expect(result).eql({b: 2}); }); it("deals with getters / setters", function() { var o = {a: 1, get b() { return 23; }, get c() { return 24; }}, result = dissoc(o, ["a", "c"]); expect(result.__lookupGetter__("b")).to.be.a("function"); }); }); describe("inherit", function() { it("inherits", function() { var obj1 = {baz: 25}; var obj2 = inherit(obj1); expect(obj2.hasOwnProperty("baz")).to.equal(false); expect(obj2.baz).to.equal(25); obj2.baz = 26; expect(obj2.hasOwnProperty("baz")).to.equal(true); expect(obj2.baz).to.equal(26); }); }); describe("cloning and copying", function() { it("clones objects", function() { var cloned = clone(obj1); expect(cloned).to.not.equal(obj1); cloned.foo = 24; cloned.oink = "!"; expect(cloned.foo).to.equal(24); expect(obj1.foo).to.equal(23); expect(cloned.oink).to.equal("!"); expect(obj1).to.not.have.property("oink"); }); it("clones arrays", function() { var arr1 = [1,2,3], arr2 = clone(arr1); arr1.push(4); arr2.push(5); expect(arr1).to.eql([1,2,3,4]); expect(arr2).to.eql([1,2,3,5]); }); it("can deep copy", function() { var o = {a: 3, b: {c: [{}],d: undefined}, e: null, f: function(){}, g: "string"}; var copy = deepCopy(o); expect(o).to.eql(copy); expect(copy).to.eql(o); expect(o).to.not.equal(copy); expect(o.b).to.not.equal(copy.b); expect(o.b.c).to.not.equal(copy.b.c); expect(o.b.c[0]).to.not.equal(copy.b.c[0]); }); }); describe("stringify types", function() { it("typeStringOf", function() { expect(typeStringOf('some string')).to.equal('String'); expect(typeStringOf(0)).to.equal('Number'); expect(typeStringOf(null)).to.equal('null'); expect(typeStringOf(undefined)).to.equal('undefined'); expect(typeStringOf([])).to.equal('Array'); expect(typeStringOf({a: 2})).to.equal('Object'); // expect(obj.typeStringOf(new lively.morphic.Morph())).to.equal('Morph'); }); it("shortPrintStringOf", function() { expect(shortPrintStringOf([1,2])).to.equal( '[...]', 'filled arrays should be displayed as [...]'); expect(shortPrintStringOf([])).to.equal( '[]', 'empty arrays should be displayed as []'); expect(shortPrintStringOf(0)).to.equal( '0', 'numbers should be displayed as values'); // expect(obj.shortPrintStringOf(new lively.morphic.Morph())).to.equal( 'Morph', 'short typestring of a morph is still Morph'); }); it("isMutableType", function() { expect(isMutableType([1,2])).to.equal(true,'arrays are mutable'); expect(isMutableType({})).to.equal(true,'empty objects are mutable'); // expect(obj.isMutableType(new lively.morphic.Morph()).to.equal(true), 'complex objects are mutable'); expect(isMutableType(2)).to.equal(false,'numbers are immutable'); }); it("safeToString", function() { expect(safeToString(null)).to.equal('null'); expect(safeToString(undefined)).to.equal('undefined'); expect(safeToString(2)).to.equal('2'); }); }); describe("sortKeysWithBeforeAndAfterConstraints", () => { it("works", () => expect( sortKeysWithBeforeAndAfterConstraints({ foo: {}, bar: {after: ["foo"], before: ["baz"]}, baz: {after: ["foo"]}})) .equals(["foo","bar","baz"])); it("throws error on cycle", () => expect(() => sortKeysWithBeforeAndAfterConstraints({ foo: {}, bar: {after: ["foo"], before: ["baz"]}, baz: {before: ["foo"]} })).throws()) }); describe("newKeyIn", () => { it("works", () => { var obj = {foo: "b a r"}; expect(newKeyIn(obj, "foo")).equals("foo-1"); }); }); }); describe('properties', function() { var properties = lively.lang.properties; var obj; beforeEach(function() { var Foo = function() { this.a = 1; this.aa = 1; this.b = function() { return true; }; }; Foo.prototype.c = 2; Foo.prototype.cc = 2; Foo.prototype.d = function() { return true; }; obj = new Foo(); }); it("can access all properties", function() { var expected, result; expected = ["a", "c"]; result = properties.all(obj, function (name, object) { return name.length == 1; }); expect(expected).to.eql(result); expected = ["aa", "cc"]; result = properties.all(obj, function (name, object) { return name.length == 2; }); expect(expected).to.eql(result); }); it("can access own properties", function() { var expected = ["a", "aa", "b"]; var result = properties.own(obj); expect(expected).to.eql(result); }); it("allProperties again", function() { var expected, result; expected = ["a", "b", "c", "d"]; result = properties.allProperties(obj, function (object, name) { return name.length == 1; }); expect(expected).to.eql(result); expected = ["aa", "cc"]; result = properties.allProperties(obj, function (object, name) { return name.length == 2; }); expect(expected).to.eql(result); }); }); describe('Path', function() { var Path = lively.lang.Path; it("parsePath", function() { expect([]).to.eql(Path(undefined).parts()); expect([]).to.eql(Path('').parts()); expect([]).to.eql(Path('.').parts()); expect(['foo']).to.eql(Path('foo').parts()); expect(['foo', 'bar']).to.eql(Path('foo.bar').parts()); }); it("pathAccesor", function() { var obj = {foo: {bar: 42}, baz: {zork: {'x y z z y': 23}}}; expect(obj).to.equal(Path('').get(obj)); expect(42).to.equal(Path('foo.bar').get(obj)); expect(obj.baz.zork).to.equal(Path('baz.zork').get(obj)); expect(23).to.equal(Path('baz.zork.x y z z y').get(obj)); expect(undefined).to.equal(Path('non.ex.is.tan.t').get(obj)); }); it("pathIncludes", function() { var base = Path('foo.bar'); expect(base.isParentPathOf('foo.bar')).to.equal(true); // 'equal paths should be "parents"' expect(base.isParentPathOf(base)).to.equal(true); // 'equal paths should be "parents" 2' expect(base.isParentPathOf('foo.bar.baz')).to.equal(true); // 'foo.bar.baz' expect(base.isParentPathOf('foo.baz')).to.equal(false); // 'foo.baz' expect(base.isParentPathOf('.')).to.equal(false); // '.' expect(base.isParentPathOf('')).to.equal(false); // 'empty string' expect(base.isParentPathOf()).to.equal(false); // 'undefined' }); it("relativePath", function() { var base = Path('foo.bar'); expect([]).to.eql(base.relativePathTo('foo.bar').parts(), 'foo.bar'); expect(['baz', 'zork']).to.eql(base.relativePathTo('foo.bar.baz.zork').parts(), 'foo.bar.baz.zork'); }); it("concat", function() { var p1 = Path('foo.bar'), p2 = Path('baz.zork'); expect('baz.zork.foo.bar').to.equal(String(p2.concat(p1))); expect('foo.bar.baz.zork').to.equal(String(p1.concat(p2))); }); it("set", function() { var obj = {foo:[{},{bar:{}}]}, p = Path('foo.1.bar.baz'); p.set(obj, 3); expect(3).to.equal(obj.foo[1].bar.baz); }); it("ensure", function() { var obj = {}, p = Path('foo.bar.baz'); p.set(obj, 3, true); expect(3).to.equal(obj.foo.bar.baz); }); it("splitter", function() { var obj = {}, p = Path('foo/bar/baz', '/'); p.set(obj, 3, true); expect(3).to.equal(obj.foo.bar.baz); }); it("parentPathOf", function() { var pp = Path, p1 = pp("a.b"); expect(p1.isParentPathOf(p1)).to.equal(true); expect(pp("a").isParentPathOf(p1)).to.equal(true); expect(pp("").isParentPathOf(pp(""))).to.equal(true); expect(p1.isParentPathOf(pp("a"))).to.equal(false); expect(p1.isParentPathOf(pp("b.a"))).to.equal(false); }); it("withParentAndKeyDo", function() { var p1 = lively.lang.Path("a.b.c"), o = {a: {b: {c: {foo: 23}}}}; p1.withParentAndKeyDo(o, false, function(parent, key) { expect(key).equal("c"); expect(parent).eql({c: {foo: 23}}); }); }); it("defineProperty", function() { var p1 = lively.lang.Path("a.b.c"), o = {a: {b: {}}}; p1.defineProperty(o, {value: 37, writable: true, enumerable: false, configurable: true}); expect(o.a.b.c).equal(37); expect(Object.keys(o.a.b)).eql([]); }); it("overwrites string", function() { var obj = {foo: "b a r"}, p = Path('foo.b a r.baz'); p.set(obj, 3, true); expect(obj).to.eql({foo: {"b a r": {baz: 3}}}); }); });