JSlang.dev

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

November 22, 2018
TestsSource
Proxies

Source Code

The source code we wrote during the event.

import assert from 'assert';

describe('Learning about the `new Proxy()`', () => {
  it('Proxy without handler throws', () => {
    assert.throws(() => { new Proxy({}) }, TypeError);
  });
  it('Proxy without a target throws', () => {
    assert.throws(() => { new Proxy() }, TypeError);
  });
  it('Proxy a function, does not throw', () => {
    assert.doesNotThrow(() => { new Proxy(function() {}, {}) });
  });
  it('A proxied function`s type is function', () => {
    const p = new Proxy(() => {}, {});
    assert.equal(typeof p, 'function');
  });
  it('A proxied function`s instanceof is Function', () => {
    const p = new Proxy(() => {}, {});
    assert(p instanceof Function);
  });
  it('A proxied object`s instanceof is Object', () => {
    const p = new Proxy({}, {});
    assert(p instanceof Object);
  });
  it('A proxied object`s equals the actual object', () => {
    const obj = {};
    const p = new Proxy(obj, {});
    assert.deepEqual(obj, p);
  });
});

describe('What can we do with it?', () => {
  it('A proxied array that adds a prefix to every pushed element', () => {
    const arr = new Proxy([], {
      get: (target, property) => {
        const push = (arg) => { 
          target.push('pre-' + arg); 
          return target; 
        };
        return property === 'push' ? push : target[property];
      }
    });
    arr.push('1');
    arr.push('2');
    assert.deepEqual(arr, ['pre-1', 'pre-2']);
  });
  it('Accessing anything on a proxied object will call handler.get', () => {
    const arr = new Proxy([], {
      get: () => 'something'
    });
    assert.equal(arr[0], 'something')
    assert.equal(arr.push, 'something')
  });
  it('Assigning anything on a proxied object will call handler.set', () => {
    const arr = new Proxy([], {
      set: (target, prop) => {
        target[1] = 'something';
        return target;
      }
    });
    arr[0] = 1;
    assert.equal(arr[1], 'something')
  });
  it('A set handler must return a truthy value', () => {
    const arr = new Proxy([], {
      set: () => 1
    });
    arr[0] = 1;
    assert.equal(arr[0], undefined)
  });
  xit('undefined is not a function', () => {
    undefined = () => {};
    assert.throws(() => { a() }, 
      (e) => {
        return e.message === 'undefined is not a function';
      });
  });
  it('Proxying a class X, returns class Y', () => {
    class X {};
    class Y {};
    const x = new Proxy(X, {
      construct: () => new Y()
    });
    assert(new x() instanceof Y);
  });

  it('Proxy an Array, and provide only `push()` and access to index=0', () => {
    const ProxiedArray = new Proxy(Array, {
      construct: () => {
        return new Proxy([], {
          set: () => true,
          get: (target, prop) => {
            const push = (arg) => target.push(arg)
            return prop === 'push' ? push : target[0];
          }
        });
      }
    });
    const arr = new ProxiedArray();
    arr.push(42);
    arr[0] = 5;
    assert.deepEqual(arr[0], 42);
    assert.equal(arr.indexOf, arr[0]);
    assert.equal(arr.toString, arr[0]);
  });

  it('Fake instanceof an object to string', () => {
    const obj = new Proxy({}, {
      getPrototypeOf: () => {
        return String.prototype;
      }
    });
    assert(obj instanceof String);
    assert.equal(typeof obj, 'object');
    assert.equal(obj.prototype, undefined);
  });
});

// what we dont know:
// - how to detect a proxied object

xit('Proxying {} and have no handlers', () => {
  const p = new Proxy();
  assert.equal(typeof p, '???');
});

// override undefined is not a function
// proxy primitive
// proxy null