JSlang.dev

JavaScript Deep Dives with Wolfram. Since 2015
Inclusive, Test-Driven, Spec-Focused, Collaborative.

June 27, 2019
TestsSource
Proxy Object

Source Code

The source code we wrote during the event.

import assert from 'assert';

describe('What can we proxy?', () => {
    it('an empty object, is proxyable', () => {
        assert.doesNotThrow(() => { new Proxy({}, {}); });
    });
    it('undefined, is NOT proxyable', () => {
        assert.throws(() => { new Proxy(undefined, {}); }, TypeError);
    });
    it('the number literal, is NOT proxyable', () => {
        assert.throws(() => { new Proxy(42, {}); }, TypeError);
    });
    it('the `Number(42)` object, is NOT proxyable', () => {
        assert.throws(() => { new Proxy(Number(42), {}); }, TypeError);
    });
    it('the `new Number(42)` object, is NOT proxyable', () => {
        assert.doesNotThrow(() => { new Proxy(new Number(42), {}); });
    });
    it('the `[]`, is proxyable', () => {
        assert.doesNotThrow(() => { new Proxy([], {}); });
    });
    it('the `new Promise()`, is proxyable', () => {
        assert.doesNotThrow(() => { new Proxy(new Promise(() => {}), {}); });
    });
    it('the `function()`, is proxyable', () => {
        assert.doesNotThrow(() => { new Proxy(function() {}, {}); });
    });
    it('a class, is proxyable', () => {
        const aClass = class {};
        assert.doesNotThrow(() => { new Proxy(aClass, {}); });
    });
    it('a string, is proxyable', () => {
        assert.throws(() => { new Proxy('', {}); }, TypeError);
    });
});

describe('How does the handler work', () => {
    it('make an object immutable', () => {
        class OurError extends Error {}
        const handler = {
            set: function() { throw new OurError(); }
        };
        const p = new Proxy({x: 1}, handler);
        assert.throws(() => p.x = 2, OurError);
    });
    it('a set handler throws if it is empty', () => {
        const handler = {
            set: function() {  }
        };
        const p = new Proxy({x: 1}, handler);
        assert.throws(() => p.x = 2, TypeError);
    });
    it('a set handler returns truthy value does not throw', () => {
        const handler = {
            set: function() { return true; }
        };
        const p = new Proxy({x: 1}, handler);
        assert.doesNotThrow(() => p.x = 2);
    });
    it('updating the target property and throw an error, but still set the value', () => {
        const handler = {
            set: function(obj, prop, value) { obj[prop] = value; return false; }
        };
        const p = new Proxy({x: 1}, handler);
        assert.throws(() => p.x = 2);
        assert.equal(p.x, 2);
    });
    describe('an async setter', () => {
        it('make set handler async, does not throw', () => {
            const handler = {
                set: async function(obj, prop, value) {
                    await Promise.resolve(value);
                    return true; 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 1);
        });
        it('set the value before a Promise-await', () => {
            const handler = {
                set: async function(obj, prop, value) {
                    obj[prop] = value;
                    await Promise.resolve(value);
                    return true; 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 2);
        });
        it('set the value after a Promise-await', () => {
            const handler = {
                set: async function(obj, prop, value) {
                    await Promise.resolve(value);
                    obj[prop] = value;
                    return true; 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 1);
        });
        it('set the value and return a promise, works because its truthy', () => {
            const handler = {
                set: function(obj, prop, value) {
                    obj[prop] = value;
                    return Promise.resolve(''); 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 2);
        });
        it('a promise inside the setter changes the value', () => {
            const handler = {
                set: function(obj, prop, value) {
                    obj[prop] = value; // change to 2
                    new Promise(
                        (resolve) => { obj[prop] = value + 1; resolve(); }
                    ); // change to 3
                    return true; 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 3);
        });
        it('a promise inside the setter changes the value, after a timeout', () => {
            const handler = {
                set: function(obj, prop, value) {
                    obj[prop] = value; // change to 2
                    new Promise(
                        (resolve) => { 
                            setTimeout(() => {obj[prop] = value + 1; resolve(); }, 0); 
                        }
                    ); // change to 3
                    return true; 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 2);
        });
        it('a promise inside the setter changes the value, after a timeout - and extended testing', (done) => {
            const handler = {
                set: function(obj, prop, value) {
                    obj[prop] = value; // change to 2
                    new Promise(
                        (resolve) => { 
                            setTimeout(() => {obj[prop] = value + 1; resolve(); }, 0); 
                        }
                    ); // change to 3
                    return true; 
                }
            };
            const p = new Proxy({x: 1}, handler);
            assert.doesNotThrow(() => p.x = 2);
            assert.equal(p.x, 2);
            setTimeout(() => { assert.equal(p.x, 3); done(); }, 0);
        });
    });
});

describe('', () => {
    it('what is `p.this`?', () => {
        
    });
    it('nested properties work too', () => {
        const handler = {};
        const p = new Proxy({x: { y: 2}}, handler);
        assert.equal(p.x.y, 2);
    });
    it('what does a get-handler receive as a parameter for nested props', () => {
        let whatProp = '';
        const handler = {
            get: (obj, prop) => { whatProp = prop; return obj[prop]; }
        };
        const p = new Proxy({x: { y: 2}}, handler);
        const _ =p.x.y;
        assert.equal(whatProp, 'x');
    });
    it('what does a get-handler receive as a parameter for nested props', () => {
        let whatArgs = '';
        const handler = {
            get: (obj, prop, args) => { whatArgs = args; return obj[prop]; }
        };
        const p = new Proxy({x: { y: 2}}, handler);
        const _ =p.x.y;
        assert.equal(whatArgs, p);
    });
    it('modify nested objects', () => {
        const handler = {
            get: () => { return {y: 42}; }
        };
        const p = new Proxy({x: { y: 2}}, handler);
        const fourtytwo = p.x.y;
        assert.equal(fourtytwo, 42);
    });
    it('nesting proxies', () => {
        const x = new Proxy({ y: 2}, { get: () => 1 });
        const p = {x};
        const fourtytwo = p.x.y;
        assert.equal(fourtytwo, 1);
    });
});

describe('helper, learning, trying out', () => {
    it('typeof `Number(42)`', () => {
        assert.equal(typeof Number(42), 'number');
    });
    it('typeof `new Number(42)`', () => {
        assert.equal(typeof new Number(42), 'object');
    });
    it('instanceof `new Number(42)` is "Number"', () => {
        assert(new Number(42) instanceof Number);
    });
});