@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
296 lines (232 loc) • 9.2 kB
JavaScript
import {expect} from "chai"
import {extend} from "../../../source/data/extend.mjs";
import {isObject} from "../../../source/types/is.mjs";
import {Observer} from "../../../source/types/observer.mjs";
import {ProxyObserver} from "../../../source/types/proxyobserver.mjs";
describe('ProxyObserver', function () {
describe('create', function () {
it('should return instanceof ProxyObserver', function () {
let o = new ProxyObserver({});
extend(o.getSubject(), {a: 4});
expect(o.getRealSubject()).is.eql({a: 4});
});
});
describe('setSubject', function () {
it('should run observer', function (done) {
let o = new ProxyObserver({a: 1});
expect(o.getRealSubject()).is.eql({a: 1});
let counter = 0;
o.attachObserver(new Observer(function () {
if (isObject(this) && this instanceof ProxyObserver) {
counter++;
// only one change as notify runs later
if (counter === 1) {
done();
return;
}
done("called for error");
return;
}
done(new Error("this is not ProxyObserver"))
}))
o.getSubject().c = 4;
expect(o.getRealSubject()).is.eql({a: 1, c: 4});
o.setSubject({b: 2});
expect(o.getRealSubject()).is.eql({b: 2});
o.getSubject().d = 5;
expect(o.getRealSubject()).is.eql({b: 2, d: 5});
});
});
describe('create', function () {
it('should return instanceof ProxyObserver', function () {
let o = new ProxyObserver({});
expect(o).is.instanceOf(ProxyObserver);
});
});
describe('proxy objects', function () {
it('Proxy objects should not be recommitted', function () {
let o = new ProxyObserver({a: {b: true}});
o.getSubject().a.b = o.getSubject();
o.getSubject().a.b.a.b = false;
//expect(o.getSubject()===p.getSubject()).to.be.true;
});
});
describe('notify observer', function () {
it('should inform observer', function (done) {
let counter = 0;
// the method is called twice, once for defineProperty and for set trap
let o = new Observer(function (a) {
if (isObject(this) && this instanceof ProxyObserver) {
counter++;
if (counter === 1) {
done();
}
return;
}
done(new Error("this is not ProxyObserver"))
}, true)
let realSubject = {
a: {
b: {
c: true
},
d: 5
}
}
let p = new ProxyObserver(realSubject);
expect(p).is.instanceOf(ProxyObserver);
expect(p.attachObserver(o)).is.instanceOf(ProxyObserver);
let s = p.getSubject();
let r = p.getRealSubject();
s.a.d = false;
s.a.b.d = 2;
});
});
/**
* @see https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/43
*/
describe('Reflect', function () {
it('check Reflect.set() with Subject', function () {
let r;
const obj = (new ProxyObserver({})).getSubject();
[
"yes!",
undefined,
"no"
].forEach(function (value) {
expect(() => {
obj['data'] = value
}).to.not.throw();
//r = Reflect.set(obj, 'data', value)
//expect(r).to.be.true;
})
});
[
['that', {that: 'checked1'}],
['that', new Proxy({that: 'checked2'}, {})],
['that', (new ProxyObserver({that: 'checked3'})).getRealSubject()],
['that', (new ProxyObserver({that: 'checked4'})).getSubject()]
].forEach(function (data) {
let key = data.shift()
let target = data.shift()
it('check Reflect.set(' + key + ',' + JSON.stringify(target) + ') should writable', function () {
let value;
[
"1",
undefined,
true,
undefined,
false,
undefined,
undefined
].forEach(function (values) {
expect(Reflect.getOwnPropertyDescriptor(
target, key)['writable'], 'writable for ' + value + ' should return true').to.be.true;
expect(Reflect.set(target, key, value), 'set for ' + value + ' should return true').to.be.true;
expect(Reflect.getOwnPropertyDescriptor(
target, key)['writable'], 'writable for ' + value + ' should return true').to.be.true;
});
})
})
});
describe('notify observer with changes', function () {
let original, observer, proxy;
before(function () {
original = {};
proxy = new ProxyObserver(original);
});
[
['a', 4, JSON.stringify({a: 4})],
['b', 9, JSON.stringify({a: 4, b: 9})]
].forEach(function (data) {
let a = data.shift()
let b = data.shift()
let c = data.shift()
it('set key ' + a + ' value ' + b + ' expect ' + c, function (done) {
let counter = 0;
observer = new Observer(function () {
counter++;
expect(JSON.stringify(this.getRealSubject())).is.equal(c);
if (counter === 1) {
this.detachObserver(observer);
done();
}
})
proxy.attachObserver(observer)
let subject = proxy.getSubject()
subject[a] = b;
});
});
})
describe('notify observer ', function () {
it('should return promise', function () {
let proxy = new ProxyObserver({});
let observer = new Observer(function () {
})
proxy.attachObserver(observer)
expect(proxy.notifyObservers()).is.instanceOf(Promise);
});
});
describe('setPrototypeOf', function () {
it('should allow setPrototypeOf and notify observer', function (done) {
const proxy = new ProxyObserver({});
const observer = new Observer(function () {
done();
});
proxy.attachObserver(observer);
const proto = {flag: true};
Object.setPrototypeOf(proxy.getSubject(), proto);
expect(Object.getPrototypeOf(proxy.getSubject())).to.equal(proto);
});
});
describe('deleteProperty', function () {
it('should notify observers when deleting a key', function (done) {
const proxy = new ProxyObserver({a: 1});
const observer = new Observer(function () {
expect(proxy.getRealSubject()).to.eql({});
done();
});
proxy.attachObserver(observer);
delete proxy.getSubject().a;
});
});
describe('symbol properties', function () {
it('should not notify observers for symbol keys', function (done) {
const proxy = new ProxyObserver({});
let called = false;
const observer = new Observer(function () {
called = true;
});
proxy.attachObserver(observer);
proxy.getSubject()[Symbol("hidden")] = "value";
setTimeout(() => {
expect(called).to.be.false;
done();
}, 0);
});
});
describe('runtime compatibility', function () {
it('should allow nested object access without DOM globals', function () {
const proxy = new ProxyObserver({options: {enabled: true}});
expect(proxy.getSubject().options.enabled).to.equal(true);
});
it('should preserve setter semantics for accessor-backed instances', function () {
let setterCalls = 0;
class Example {
set value(v) {
setterCalls++;
this._value = v;
}
get value() {
return this._value;
}
}
const instance = new Example();
const proxy = new ProxyObserver({instance});
proxy.getSubject().instance.value = 7;
expect(setterCalls).to.equal(1);
expect(instance.value).to.equal(7);
expect(Object.prototype.hasOwnProperty.call(instance, 'value')).to.be.false;
});
});
})