<commit_before>/* @flow */

import LiveSet from '.';

import delay from 'pdelay';

test('read', () => {
  let currentValue = new Set([5,6,7]);
  const read = jest.fn(() => currentValue);
  const ls = new LiveSet({
    read,
    listen() {
      throw new Error('should not be called');
    }
  });

  expect(read).toHaveBeenCalledTimes(0);
  expect(Array.from(ls.values())).toEqual([5,6,7]);
  expect(read).toHaveBeenCalledTimes(1);

  currentValue = new Set([7,8,9]);
  expect(Array.from(ls.values())).toEqual([7,8,9]);
  expect(read).toHaveBeenCalledTimes(2);
});

test('listen, subscribe', async () => {
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([4,5]),
    listen(setValues, c) {
      setValues(this.read());
      expect(c.closed).toBe(false);
      c.add(5);
      c.add(6);
      c.add(7);
      let t = setTimeout(() => {
        expect(c.closed).toBe(false);
        c.remove(5);
        c.add(8);
        t = setTimeout(() => {
          throw new Error('should not happen');
        }, 1);
      }, 1);
      return () => {
        expect(c.closed).toBe(true);
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([4,5]);

  let changeHandlerCallCount = 0;
  const sub = ls.subscribe(changes => {
    switch (changeHandlerCallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'remove', value: 5}, {type: 'add', value: 8}]);
      expect(Array.from(ls.values())).toEqual([4,6,7,8]);
      expect(sub.closed).toBe(false);
      sub.unsubscribe();
      expect(sub.closed).toBe(true);
      break;
    default:
      throw new Error(`Should not happen. ${changeHandlerCallCount}`);
    }
  });
  expect(sub.closed).toBe(false);
  // The listen function should run immediately
  expect(Array.from(ls.values())).toEqual([4,5,6,7]);
  // The change handler should be called asynchronously
  expect(changeHandlerCallCount).toBe(0);
  expect(unsub).toHaveBeenCalledTimes(0);
  await delay(40);
  expect(changeHandlerCallCount).toBe(1);
  expect(unsub).toHaveBeenCalledTimes(1);
  expect(ls.isEnded()).toBe(false);
});

test('subscribe, end', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      expect(c.closed).toBe(false);
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        expect(c.closed).toBe(false);
        c.add(3);
        c.end();
        expect(c.closed).toBe(true);
      }, 30);
      return () => {
        expect(c.closed).toBe(true);
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([1]);

  const next = jest.fn();
  const complete = jest.fn();
  const sub = ls.subscribe(next, null, complete);
  expect(next.mock.calls).toEqual([]);
  expect(Array.from(ls.values())).toEqual([1,2]);
  await delay(0);

  expect(next.mock.calls).toEqual([]);
  expect(complete).toHaveBeenCalledTimes(0);
  expect(sub.closed).toBe(false);
  expect(unsub).toHaveBeenCalledTimes(0);
  expect(Array.from(ls.values())).toEqual([1,2]);
  expect(ls.isEnded()).toBe(false);

  await delay(60);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 3}]],
  ]);
  expect(complete).toHaveBeenCalledTimes(1);
  expect(sub.closed).toBe(true);
  expect(unsub).toHaveBeenCalledTimes(1);
  expect(ls.isEnded()).toBe(true);

  // Values should be frozen at end time.
  expect(Array.from(ls.values())).toEqual([1,2,3]);

  {
    const start = jest.fn();
    const next = jest.fn();
    const error = jest.fn();
    const complete = jest.fn();
    const sub = ls.subscribe({start, next, error, complete});
    expect(start).toHaveBeenCalledTimes(1);
    expect(start.mock.calls[0][0]).toBe(sub);
    expect(next).toHaveBeenCalledTimes(0);
    expect(error).toHaveBeenCalledTimes(0);
    expect(complete).toHaveBeenCalledTimes(1);
    expect(sub.closed).toBe(true);
  }

  {
    const start = jest.fn(sub => {
      sub.unsubscribe();
    });
    const next = jest.fn();
    const error = jest.fn();
    const complete = jest.fn();
    const sub = ls.subscribe({start, next, error, complete});
    expect(start).toHaveBeenCalledTimes(1);
    expect(start.mock.calls[0][0]).toEqual(sub);
    expect(next).toHaveBeenCalledTimes(0);
    expect(error).toHaveBeenCalledTimes(0);
    expect(complete).toHaveBeenCalledTimes(0);
    expect(sub.closed).toBe(true);
  }
});

test('error', async () => {
  const {liveSet, controller} = LiveSet.active(new Set([1]));

  const start = jest.fn(), next = jest.fn(), error = jest.fn(), complete = jest.fn();
  const sub = liveSet.subscribe({start, next, error, complete});

  controller.add(2);
  controller.error(new Error('foo'));

  expect(start).toHaveBeenCalledTimes(1);
  expect(start.mock.calls[0][0]).toEqual(sub);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(0);
  expect(sub.closed).toBe(false);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 2}]]
  ]);
  expect(error).toHaveBeenCalledTimes(1);
  expect(error.mock.calls[0][0].message).toBe('foo');
  expect(complete).toHaveBeenCalledTimes(0);
  expect(sub.closed).toBe(true);
});

test("don't receive changes that already happened when subscribing", async () => {
  const {liveSet, controller} = LiveSet.active(new Set([5]));
  controller.remove(5);
  controller.add(6);

  expect(Array.from(liveSet.values())).toEqual([6]);

  const next = jest.fn();
  liveSet.subscribe(next);

  await delay(0);

  expect(next.mock.calls).toEqual([
  ]);
});

test('subscribe, unsubscribe, subscribe', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        c.add(3);
      }, 40);
      return () => {
        clearTimeout(t);
        unsub();
      };
    }
  });

  for (let i=0; i<3; i++) {
    expect(Array.from(ls.values())).toEqual([1]);
    const changeHandler = jest.fn();
    const sub = ls.subscribe(changeHandler);
    expect(changeHandler.mock.calls).toEqual([]);
    expect(Array.from(ls.values())).toEqual([1,2]);
    await delay(1);
    expect(changeHandler.mock.calls).toEqual([]);
    expect(Array.from(ls.values())).toEqual([1,2]);
    await delay(100);
    expect(changeHandler.mock.calls).toEqual([
      [[{type: 'add', value: 3}]]
    ]);
    expect(Array.from(ls.values())).toEqual([1,2,3]);
    sub.unsubscribe();
    await delay(0);
  }
});

test('multiple subscribers, one immediate unsubscription', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        c.add(3);
        t = setTimeout(() => {
          throw new Error('should not happen');
        }, 1);
      }, 1);
      return () => {
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([1]);
  expect(listenStart).toHaveBeenCalledTimes(0);
  const sub1 = ls.subscribe({});
  // The listen function should run immediately
  expect(Array.from(ls.values())).toEqual([1,2]);
  expect(listenStart).toHaveBeenCalledTimes(1);

  let changeHandlerCallCount = 0;
  const sub2 = ls.subscribe(changes => {
    switch (changeHandlerCallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'add', value: 3}]);
      expect(Array.from(ls.values())).toEqual([1,2,3]);
      sub2.unsubscribe();
      break;
    default:
      throw new Error(`Should not happen. ${changeHandlerCallCount}`);
    }
  });

  sub1.unsubscribe();

  expect(listenStart).toHaveBeenCalledTimes(1);
  expect(unsub).toHaveBeenCalledTimes(0);

  // The change handler should be called asynchronously
  expect(changeHandlerCallCount).toBe(0);
  await delay(40);
  expect(changeHandlerCallCount).toBe(1);
  expect(unsub).toHaveBeenCalledTimes(1);
});

test('multiple subscribers', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        c.add(3);
      }, 1);
      return () => {
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([1]);
  expect(listenStart).toHaveBeenCalledTimes(0);

  let changeHandler1CallCount = 0;
  const sub1 = ls.subscribe(changes => {
    switch (changeHandler1CallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'add', value: 3}]);
      expect(Array.from(ls.values())).toEqual([1,2,3]);
      expect(sub1.closed).toBe(false);
      sub1.unsubscribe();
      expect(sub1.closed).toBe(true);
      break;
    default:
      throw new Error(`Should not happen. ${changeHandler1CallCount}`);
    }
  });

  // The listen function should run immediately
  expect(Array.from(ls.values())).toEqual([1,2]);
  expect(listenStart).toHaveBeenCalledTimes(1);

  let changeHandler2CallCount = 0;
  const sub2 = ls.subscribe(changes => {
    switch (changeHandler2CallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'add', value: 3}]);
      expect(Array.from(ls.values())).toEqual([1,2,3]);
      expect(sub2.closed).toBe(false);
      sub2.unsubscribe();
      expect(sub2.closed).toBe(true);
      break;
    default:
      throw new Error(`Should not happen. ${changeHandler2CallCount}`);
    }
  });

  expect(listenStart).toHaveBeenCalledTimes(1);
  expect(unsub).toHaveBeenCalledTimes(0);

  // The change handlers should be called asynchronously
  expect(changeHandler1CallCount).toBe(0);
  expect(changeHandler2CallCount).toBe(0);
  await delay(40);
  expect(changeHandler1CallCount).toBe(1);
  expect(changeHandler2CallCount).toBe(1);
  expect(unsub).toHaveBeenCalledTimes(1);
});

test('immediate end', async () => {
  const liveSet = new LiveSet({
    read: () => new Set([5,6]),
    listen(setValues, controller) {
      setValues(this.read());
      controller.add(7);
      controller.end();
    }
  });
  const next = jest.fn(), complete = jest.fn();
  liveSet.subscribe({next, complete});
  expect(next.mock.calls).toEqual([]);
  expect(complete).toHaveBeenCalledTimes(0);
  await delay(0);
  expect(next.mock.calls).toEqual([]);
  expect(complete).toHaveBeenCalledTimes(1);
});

test('pullChanges', async () => {
  const {liveSet, controller} = LiveSet.active(new Set([5,6]));
  const next = jest.fn(), next2 = jest.fn();
  const sub = liveSet.subscribe(next);
  liveSet.subscribe(next2);
  controller.add(7);
  expect(next.mock.calls).toEqual([
  ]);
  expect(next2.mock.calls).toEqual([
  ]);
  sub.pullChanges();
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  expect(next2.mock.calls).toEqual([
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  expect(next2.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  controller.add(8);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  expect(next2.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]],
    [[{type: 'add', value: 8}]]
  ]);
  expect(next2.mock.calls).toEqual([
    [[{type: 'add', value: 7}]],
    [[{type: 'add', value: 8}]]
  ]);
});

test('pullChanges and ignoring already-delivered values', async () => {
  const {liveSet, controller} = LiveSet.active(new Set([5]));
  const next = jest.fn();
  controller.add(6);
  const sub = liveSet.subscribe(next);
  controller.add(7);
  expect(next.mock.calls).toEqual([
  ]);
  sub.pullChanges();
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  controller.add(8);
  controller.add(9);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]],
    [[{type: 'add', value: 8}, {type: 'add', value: 9}]],
  ]);
});

test('values() triggers pullChanges()', () => {
  const ls = new LiveSet({
    read: () => new Set([5,6]),
    listen(setValues, controller) {
      setValues(this.read());
      return {
        unsubscribe() {},
        pullChanges() {
          controller.add(7);
        }
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([5,6]);
  ls.subscribe({});
  expect(Array.from(ls.values())).toEqual([5,6,7]);
});

test('constant', async () => {
  const ls = LiveSet.constant(new Set([5,6,7]));
  expect(ls.isEnded()).toBe(true);
  expect(Array.from(ls.values())).toEqual([5,6,7]);
  const start = jest.fn(), next = jest.fn(), error = jest.fn(), complete = jest.fn();
  const sub = ls.subscribe({start, next, error, complete});
  expect(sub.closed).toBe(true);
  expect(start).toHaveBeenCalledTimes(1);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(1);
  await delay(0);
  expect(start).toHaveBeenCalledTimes(1);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(1);
});
<commit_msg>Test immediate unsubscription from ended liveset.<commit_after>/* @flow */

import LiveSet from '.';

import delay from 'pdelay';

test('read', () => {
  let currentValue = new Set([5,6,7]);
  const read = jest.fn(() => currentValue);
  const ls = new LiveSet({
    read,
    listen() {
      throw new Error('should not be called');
    }
  });

  expect(read).toHaveBeenCalledTimes(0);
  expect(Array.from(ls.values())).toEqual([5,6,7]);
  expect(read).toHaveBeenCalledTimes(1);

  currentValue = new Set([7,8,9]);
  expect(Array.from(ls.values())).toEqual([7,8,9]);
  expect(read).toHaveBeenCalledTimes(2);
});

test('listen, subscribe', async () => {
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([4,5]),
    listen(setValues, c) {
      setValues(this.read());
      expect(c.closed).toBe(false);
      c.add(5);
      c.add(6);
      c.add(7);
      let t = setTimeout(() => {
        expect(c.closed).toBe(false);
        c.remove(5);
        c.add(8);
        t = setTimeout(() => {
          throw new Error('should not happen');
        }, 1);
      }, 1);
      return () => {
        expect(c.closed).toBe(true);
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([4,5]);

  let changeHandlerCallCount = 0;
  const sub = ls.subscribe(changes => {
    switch (changeHandlerCallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'remove', value: 5}, {type: 'add', value: 8}]);
      expect(Array.from(ls.values())).toEqual([4,6,7,8]);
      expect(sub.closed).toBe(false);
      sub.unsubscribe();
      expect(sub.closed).toBe(true);
      break;
    default:
      throw new Error(`Should not happen. ${changeHandlerCallCount}`);
    }
  });
  expect(sub.closed).toBe(false);
  // The listen function should run immediately
  expect(Array.from(ls.values())).toEqual([4,5,6,7]);
  // The change handler should be called asynchronously
  expect(changeHandlerCallCount).toBe(0);
  expect(unsub).toHaveBeenCalledTimes(0);
  await delay(40);
  expect(changeHandlerCallCount).toBe(1);
  expect(unsub).toHaveBeenCalledTimes(1);
  expect(ls.isEnded()).toBe(false);
});

test('subscribe, end', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      expect(c.closed).toBe(false);
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        expect(c.closed).toBe(false);
        c.add(3);
        c.end();
        expect(c.closed).toBe(true);
      }, 30);
      return () => {
        expect(c.closed).toBe(true);
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([1]);

  const next = jest.fn();
  const complete = jest.fn();
  const sub = ls.subscribe(next, null, complete);
  expect(next.mock.calls).toEqual([]);
  expect(Array.from(ls.values())).toEqual([1,2]);
  await delay(0);

  expect(next.mock.calls).toEqual([]);
  expect(complete).toHaveBeenCalledTimes(0);
  expect(sub.closed).toBe(false);
  expect(unsub).toHaveBeenCalledTimes(0);
  expect(Array.from(ls.values())).toEqual([1,2]);
  expect(ls.isEnded()).toBe(false);

  await delay(60);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 3}]],
  ]);
  expect(complete).toHaveBeenCalledTimes(1);
  expect(sub.closed).toBe(true);
  expect(unsub).toHaveBeenCalledTimes(1);
  expect(ls.isEnded()).toBe(true);

  // Values should be frozen at end time.
  expect(Array.from(ls.values())).toEqual([1,2,3]);

  {
    const start = jest.fn();
    const next = jest.fn();
    const error = jest.fn();
    const complete = jest.fn();
    const sub = ls.subscribe({start, next, error, complete});
    expect(start).toHaveBeenCalledTimes(1);
    expect(start.mock.calls[0][0]).toBe(sub);
    expect(next).toHaveBeenCalledTimes(0);
    expect(error).toHaveBeenCalledTimes(0);
    expect(complete).toHaveBeenCalledTimes(1);
    expect(sub.closed).toBe(true);
  }

  {
    const start = jest.fn(sub => {
      sub.unsubscribe();
    });
    const next = jest.fn();
    const error = jest.fn();
    const complete = jest.fn();
    const sub = ls.subscribe({start, next, error, complete});
    expect(start).toHaveBeenCalledTimes(1);
    expect(start.mock.calls[0][0]).toEqual(sub);
    expect(next).toHaveBeenCalledTimes(0);
    expect(error).toHaveBeenCalledTimes(0);
    expect(complete).toHaveBeenCalledTimes(0);
    expect(sub.closed).toBe(true);
  }
});

test('error', async () => {
  const {liveSet, controller} = LiveSet.active(new Set([1]));

  const start = jest.fn(), next = jest.fn(), error = jest.fn(), complete = jest.fn();
  const sub = liveSet.subscribe({start, next, error, complete});

  controller.add(2);
  controller.error(new Error('foo'));

  expect(start).toHaveBeenCalledTimes(1);
  expect(start.mock.calls[0][0]).toEqual(sub);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(0);
  expect(sub.closed).toBe(false);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 2}]]
  ]);
  expect(error).toHaveBeenCalledTimes(1);
  expect(error.mock.calls[0][0].message).toBe('foo');
  expect(complete).toHaveBeenCalledTimes(0);
  expect(sub.closed).toBe(true);
});

test("don't receive changes that already happened when subscribing", async () => {
  const {liveSet, controller} = LiveSet.active(new Set([5]));
  controller.remove(5);
  controller.add(6);

  expect(Array.from(liveSet.values())).toEqual([6]);

  const next = jest.fn();
  liveSet.subscribe(next);

  await delay(0);

  expect(next.mock.calls).toEqual([
  ]);
});

test('subscribe, unsubscribe, subscribe', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        c.add(3);
      }, 40);
      return () => {
        clearTimeout(t);
        unsub();
      };
    }
  });

  for (let i=0; i<3; i++) {
    expect(Array.from(ls.values())).toEqual([1]);
    const changeHandler = jest.fn();
    const sub = ls.subscribe(changeHandler);
    expect(changeHandler.mock.calls).toEqual([]);
    expect(Array.from(ls.values())).toEqual([1,2]);
    await delay(1);
    expect(changeHandler.mock.calls).toEqual([]);
    expect(Array.from(ls.values())).toEqual([1,2]);
    await delay(100);
    expect(changeHandler.mock.calls).toEqual([
      [[{type: 'add', value: 3}]]
    ]);
    expect(Array.from(ls.values())).toEqual([1,2,3]);
    sub.unsubscribe();
    await delay(0);
  }
});

test('multiple subscribers, one immediate unsubscription', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        c.add(3);
        t = setTimeout(() => {
          throw new Error('should not happen');
        }, 1);
      }, 1);
      return () => {
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([1]);
  expect(listenStart).toHaveBeenCalledTimes(0);
  const sub1 = ls.subscribe({});
  // The listen function should run immediately
  expect(Array.from(ls.values())).toEqual([1,2]);
  expect(listenStart).toHaveBeenCalledTimes(1);

  let changeHandlerCallCount = 0;
  const sub2 = ls.subscribe(changes => {
    switch (changeHandlerCallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'add', value: 3}]);
      expect(Array.from(ls.values())).toEqual([1,2,3]);
      sub2.unsubscribe();
      break;
    default:
      throw new Error(`Should not happen. ${changeHandlerCallCount}`);
    }
  });

  sub1.unsubscribe();

  expect(listenStart).toHaveBeenCalledTimes(1);
  expect(unsub).toHaveBeenCalledTimes(0);

  // The change handler should be called asynchronously
  expect(changeHandlerCallCount).toBe(0);
  await delay(40);
  expect(changeHandlerCallCount).toBe(1);
  expect(unsub).toHaveBeenCalledTimes(1);
});

test('multiple subscribers', async () => {
  const listenStart = jest.fn();
  const unsub = jest.fn();
  const ls = new LiveSet({
    read: () => new Set([1]),
    listen(setValues, c) {
      setValues(this.read());
      listenStart();
      c.add(2);
      let t = setTimeout(() => {
        c.add(3);
      }, 1);
      return () => {
        clearTimeout(t);
        unsub();
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([1]);
  expect(listenStart).toHaveBeenCalledTimes(0);

  let changeHandler1CallCount = 0;
  const sub1 = ls.subscribe(changes => {
    switch (changeHandler1CallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'add', value: 3}]);
      expect(Array.from(ls.values())).toEqual([1,2,3]);
      expect(sub1.closed).toBe(false);
      sub1.unsubscribe();
      expect(sub1.closed).toBe(true);
      break;
    default:
      throw new Error(`Should not happen. ${changeHandler1CallCount}`);
    }
  });

  // The listen function should run immediately
  expect(Array.from(ls.values())).toEqual([1,2]);
  expect(listenStart).toHaveBeenCalledTimes(1);

  let changeHandler2CallCount = 0;
  const sub2 = ls.subscribe(changes => {
    switch (changeHandler2CallCount++) {
    case 0:
      expect(changes).toEqual([{type: 'add', value: 3}]);
      expect(Array.from(ls.values())).toEqual([1,2,3]);
      expect(sub2.closed).toBe(false);
      sub2.unsubscribe();
      expect(sub2.closed).toBe(true);
      break;
    default:
      throw new Error(`Should not happen. ${changeHandler2CallCount}`);
    }
  });

  expect(listenStart).toHaveBeenCalledTimes(1);
  expect(unsub).toHaveBeenCalledTimes(0);

  // The change handlers should be called asynchronously
  expect(changeHandler1CallCount).toBe(0);
  expect(changeHandler2CallCount).toBe(0);
  await delay(40);
  expect(changeHandler1CallCount).toBe(1);
  expect(changeHandler2CallCount).toBe(1);
  expect(unsub).toHaveBeenCalledTimes(1);
});

test('immediate end', async () => {
  const liveSet = new LiveSet({
    read: () => new Set([5,6]),
    listen(setValues, controller) {
      setValues(this.read());
      controller.add(7);
      controller.end();
    }
  });
  const next = jest.fn(), complete = jest.fn();
  liveSet.subscribe({next, complete});
  expect(next.mock.calls).toEqual([]);
  expect(complete).toHaveBeenCalledTimes(0);
  await delay(0);
  expect(next.mock.calls).toEqual([]);
  expect(complete).toHaveBeenCalledTimes(1);
});

test('pullChanges', async () => {
  const {liveSet, controller} = LiveSet.active(new Set([5,6]));
  const next = jest.fn(), next2 = jest.fn();
  const sub = liveSet.subscribe(next);
  liveSet.subscribe(next2);
  controller.add(7);
  expect(next.mock.calls).toEqual([
  ]);
  expect(next2.mock.calls).toEqual([
  ]);
  sub.pullChanges();
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  expect(next2.mock.calls).toEqual([
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  expect(next2.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  controller.add(8);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  expect(next2.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]],
    [[{type: 'add', value: 8}]]
  ]);
  expect(next2.mock.calls).toEqual([
    [[{type: 'add', value: 7}]],
    [[{type: 'add', value: 8}]]
  ]);
});

test('pullChanges and ignoring already-delivered values', async () => {
  const {liveSet, controller} = LiveSet.active(new Set([5]));
  const next = jest.fn();
  controller.add(6);
  const sub = liveSet.subscribe(next);
  controller.add(7);
  expect(next.mock.calls).toEqual([
  ]);
  sub.pullChanges();
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  controller.add(8);
  controller.add(9);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]]
  ]);
  await delay(0);
  expect(next.mock.calls).toEqual([
    [[{type: 'add', value: 7}]],
    [[{type: 'add', value: 8}, {type: 'add', value: 9}]],
  ]);
});

test('values() triggers pullChanges()', () => {
  const ls = new LiveSet({
    read: () => new Set([5,6]),
    listen(setValues, controller) {
      setValues(this.read());
      return {
        unsubscribe() {},
        pullChanges() {
          controller.add(7);
        }
      };
    }
  });

  expect(Array.from(ls.values())).toEqual([5,6]);
  ls.subscribe({});
  expect(Array.from(ls.values())).toEqual([5,6,7]);
});

test('constant', async () => {
  const ls = LiveSet.constant(new Set([5,6,7]));
  expect(ls.isEnded()).toBe(true);
  expect(Array.from(ls.values())).toEqual([5,6,7]);
  const start = jest.fn(), next = jest.fn(), error = jest.fn(), complete = jest.fn();
  const sub = ls.subscribe({start, next, error, complete});
  expect(sub.closed).toBe(true);
  expect(start).toHaveBeenCalledTimes(1);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(1);
  await delay(0);
  expect(start).toHaveBeenCalledTimes(1);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(1);
});

test('immediate unsubscribe from ended liveset', async () => {
  const ls = LiveSet.constant(new Set([5,6,7]));
  const next = jest.fn(), error = jest.fn(), complete = jest.fn();
  const sub = ls.subscribe({start: sub => sub.unsubscribe(), next, error, complete});
  expect(sub.closed).toBe(true);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(0);
  await delay(0);
  expect(next).toHaveBeenCalledTimes(0);
  expect(error).toHaveBeenCalledTimes(0);
  expect(complete).toHaveBeenCalledTimes(0);
});
<|endoftext|>


<commit_before>/**
 * @file Interfaces
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfAttributesAdd*/ // Shortcut for gpf.attributes.add
/*global _gpfDefAttr*/ // gpf.define for attributes
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfEmptyFunc*/ // An empty function
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfGenerateCustomDefineHandler*/ // Class handler for class types (interfaces...)
/*global _gpfGetClassDefinition*/ // Get GPF class definition for a constructor
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*exported _gpfDefIntrf*/ // gpf.define for interfaces
/*exported _gpfI*/ // gpf.interfaces
/*exported _gpfQueryInterface*/ // gpf.interfaces.query
/*#endif*/

_gpfErrorDeclare("interfaces", {
    interfaceExpected:
        "Expected interface not implemented: {name}"
});

/**
 * Verify that the object implements the current interface
 *
 * @param {Object} inspectedObject object (or class) to inspect
 * @param {gpf.interfaces.Interface} interfaceDefinition reference interface
 * @return {Boolean}
 */
function _gpfIsImplementedBy (inspectedObject, interfaceDefinition) {
    var member,
        memberReference,
        memberValue,
        memberType;
    /*
     * IMPORTANT note: we test the object itself (i.e. own members and the prototype).
     * That's why the hasOwnProperty is skipped
     */
    /*jslint forin:true*/
    for (member in interfaceDefinition.prototype) {
        if ("constructor" === member) { // Object
            continue;
        }
        memberReference = interfaceDefinition.prototype[member];
        memberValue = inspectedObject[member];
        memberType = typeof memberValue;
        if (typeof memberReference !== memberType) {
            return false;
        }
        if ("function" === memberType && memberReference.length !== memberValue.length) {
            return false;
        }
    }
    return true;
}

/**
 * Retrieve an object implementing the expected interface from an object.
 * This is done in two tests:
 * - Either the object implements the interface, it is returned
 * - Or the object implements IUnknown, then queryInterface is used
 *
 * @param {Object} objectInstance object to inspect
 * @param {gpf.interfaces.Interface} interfaceDefinition reference interface
 * @param {Boolean} [throwError=true] throwError Throws an error if the interface is not found (otherwise, null
 * is returned)
 * @return {Object|null}
 */
function _gpfQueryInterface (objectInstance, interfaceDefinition, throwError) {
    var result = null;
    if (_gpfIsImplementedBy(objectInstance, interfaceDefinition)) {
        return objectInstance;
    } else if (_gpfIsImplementedBy(objectInstance, gpf.interfaces.IUnknown)) {
        result = objectInstance.queryInterface(interfaceDefinition);
    }
    if (null === result && (undefined === throwError || throwError)) {
        throw gpf.Error.interfaceExpected({
            name: _gpfGetClassDefinition(interfaceDefinition)._name
        });
    }
    return result;
}

var
    /**
     * gpf.attributes shortcut
     *
     * @type {Object}
     * @private
     */
    _gpfI = gpf.interfaces = {

        /**
         * Verify that the object (or class) implements the current interface
         *
         * @param {Object|Function} inspectedObject object (or class) to inspect
         * @param {gpf.interfaces.Interface} interfaceDefinition reference interface
         * @return {Boolean}
         */
        isImplementedBy: function (inspectedObject, interfaceDefinition) {
            if (inspectedObject instanceof Function) {
                inspectedObject = inspectedObject.prototype;
            }
            return _gpfIsImplementedBy(inspectedObject, interfaceDefinition);
        },

        // @inheritdoc _gpfQueryInterface
        query: _gpfQueryInterface

    };

/**
 * Defines an interface (relies on gpf.define)
 *
 * @param {String} name Interface name. If it contains a dot, it is treated as absolute contextual.
 * Otherwise, it is relative to "gpf.interfaces"
 * @param {Function|string} [base=undefined] base Base interface (or contextual name)
 * @param {Object} [definition=undefined] definition Interface definition
 * @return {Function}
 */
var _gpfDefIntrf = _gpfGenerateCustomDefineHandler("gpf.interfaces", "Interface");

/**
 * Base class for any interface
 *
 * @class gpf.interfaces.Interface
 */
_gpfDefine("gpf.interfaces.Interface");

//region IEventTarget

/* istanbul ignore next */ // Interface
_gpfDefIntrf("IEventDispatcher", {

    /**
     * Add an event listener to the dispatcher
     *
     * @param {String} event name
     * @param {gpf.events.Handler} eventsHandler
     * @return {gpf.interfaces.IEventDispatcher}
     * @chainable
     */
    addEventListener: function (event, eventsHandler) {
        _gpfIgnore(event, eventsHandler);
        return this;
    },

    /**
     * Remove an event listener from the dispatcher
     *
     * @param {String} event name
     * @param {gpf.events.Handler} eventsHandler
     * @return {gpf.interfaces.IEventDispatcher}
     * @chainable
     */
    removeEventListener: function (event, eventsHandler) {
        _gpfIgnore(event, eventsHandler);
        return this;
    },

    /**
     * Broadcast the event
     *
     * @param {String|gpf.events.Event} event name or object
     * @param {Object} [params={}] event parameters
     * @return {gpf.events.Event}
     */
    dispatchEvent: function (event, params) {
        _gpfIgnore(event, params);
    }

});

//endregion

//region IUnknown

/* istanbul ignore next */ // Interface
/**
 * Provide a way for any object to implement an interface using an intermediate object (this avoids overloading the
 * object with temporary / useless members)
 */
var _gpfIUnknown = _gpfDefIntrf("IUnknown", {

    /**
     * Retrieves an object supporting the provided interface (maybe the object itself)
     *
     * @param {gpf.interfaces.Interface} interfaceDefinition The expected interface
     * @return {Object|null} The object supporting the interface (or null)
     */
    queryInterface: function (interfaceDefinition) {
        _gpfIgnore(interfaceDefinition);
        return null;
    }

});

//endregion

//region InterfaceImplement attribute

/**
 * Retrieves an object supporting the provided interface (maybe the object itself).
 * This function (added to any object declaring the attribute InterfaceImplementAttribute with a builder) uses the
 * InterfaceImplementAttribute attribute list to see if the one corresponding to the interface provides a builder and
 * calls it
 *
 * @param {gpf.interfaces.Interface} interfaceDefinition The expected interface
 * @return {Object|null} The object supporting the interface (or null)
 */
function _queryInterface (interfaceDefinition) {
    /*jshint validthis:true*/ // Called with apply
    var
        array = (new gpf.attributes.Map(this))
            .getMemberAttributes("Class")
            .filter(gpf.attributes.InterfaceImplementAttribute),
        builder;
    if (!array.every(function (attribute) {
        builder = attribute._builder;
        return attribute._interfaceDefinition !== interfaceDefinition || !builder;
    })) {
        if ("function" === typeof builder) {
            return builder(this);
        }
        // Expects a member name
        return this[builder]();
    }
    // Otherwise
    return null;
}

/**
 * Creates a wrapper calling _queryInterface and, if no result is built, the original one defined in the object
 * prototype
 *
 * @param {Function} orgQueryInterface
 * @closure
 */
function _wrapQueryInterface (orgQueryInterface) {
    /**
     * @inheritdoc gpf.interfaces.IUnknown#queryInterface
     * @this
     */
    return function (interfaceDefinition) {
        _gpfIgnore(interfaceDefinition);
        var result = _queryInterface.apply(this, arguments);
        if (null === result) {
            result = orgQueryInterface.apply(this, arguments);
        }
        return result;
    };
}

/**
 * Document the class by telling the interface is implemented and may provide a builder to access it
 *
 * @param {Function} interfaceDefinition Implemented interface definition
 * @param {Function|String} [queryInterfaceBuilder=undefined] queryInterfaceBuilder Function or member name to executed
 * if the implemented interface is requested
 *
 * @class gpf.attributes.InterfaceImplementAttribute
 * @extends gpf.attributes.ClassAttribute
 * @alias gpf.$InterfaceImplement
 */
_gpfDefAttr("$InterfaceImplement", {
    "-": {

        // Interface definition
        "[_interfaceDefinition]": [gpf.$ClassProperty(false)],
        _interfaceDefinition: _gpfEmptyFunc,

        /**
         * Builder function
         *
         * @type {Function|null}
         */
        "[_builder]": [gpf.$ClassProperty(false)],
        _builder: null,

        _addMissingInterfaceMembers: function (objPrototype) {
            var
                iProto = this._interfaceDefinition.prototype,
                member;
            // Fill the missing methods
            /*jslint forin:true*/ // Wants inherited members too
            for (member in iProto) {
                if (!(member in objPrototype)) {
                    objPrototype[member] = iProto[member];
                }
            }
        },

        _addQueryInterface: function (objPrototype) {
            if (undefined === objPrototype.queryInterface) {
                objPrototype.queryInterface = _queryInterface;
                _gpfAttributesAdd(objPrototype.constructor, "Class", [gpf.$InterfaceImplement(_gpfIUnknown)]);
            } else if (_queryInterface !== objPrototype.queryInterface) {
                /*
                 * Two situations here:
                 * - Either the class (or one of its parent) already owns the $InterfaceImplement attribute
                 * - Or the class (or one of its parent) implements its own queryInterface
                 * In that last case, wrap it to use the attribute version first
                 *
                 * In both case, we take the assumption that the class already owns
                 * gpf.$InterfaceImplement(gpf.interfaces.IUnknown)
                 */
                objPrototype.queryInterface = _wrapQueryInterface(objPrototype.queryInterface);
            }
        }

    },
    "#": {

        // @inheritdoc gpf.attributes.Attribute#_alterPrototype
        _alterPrototype: function (objPrototype) {
            if (this._builder) {
                this._addQueryInterface(objPrototype);
            } else {
                this._addMissingInterfaceMembers(objPrototype);
                // Get the interface's attributes apply them to the obj
                new gpf.attributes.Map()
                    .fillFromClassDef(_gpfGetClassDefinition(this._interfaceDefinition))
                    .forEach(function (attributes, member) {
                        _gpfAttributesAdd(objPrototype.constructor, member, attributes);
                    });
            }
        }

    },

    "+": {

        /**
         * @param {Function} interfaceDefinition Interface definition
         * @param {Function|null} [queryInterfaceBuilder=null]
         * queryInterfaceBuilder Builder function
         * @constructor
         */
        constructor: function (interfaceDefinition, queryInterfaceBuilder) {
            this._interfaceDefinition = interfaceDefinition;
            if (queryInterfaceBuilder) {
                this._builder = queryInterfaceBuilder;
            }
        }

    }

});

//endregion
<commit_msg>Documentation update<commit_after>/**
 * @file Interfaces
 */
/*#ifndef(UMD)*/
"use strict";
/*global _gpfAttributesAdd*/ // Shortcut for gpf.attributes.add
/*global _gpfDefAttr*/ // gpf.define for attributes
/*global _gpfDefine*/ // Shortcut for gpf.define
/*global _gpfEmptyFunc*/ // An empty function
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfGenerateCustomDefineHandler*/ // Class handler for class types (interfaces...)
/*global _gpfGetClassDefinition*/ // Get GPF class definition for a constructor
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*exported _gpfDefIntrf*/ // gpf.define for interfaces
/*exported _gpfI*/ // gpf.interfaces
/*exported _gpfQueryInterface*/ // gpf.interfaces.query
/*#endif*/

_gpfErrorDeclare("interfaces", {
    interfaceExpected:
        "Expected interface not implemented: {name}"
});

/**
 * Verify that the object implements the current interface
 *
 * @param {Object} inspectedObject Object (or class) to inspect
 * @param {gpf.interfaces.Interface} interfaceDefinition Reference interface
 * @return {Boolean} True if implemented
 */
function _gpfIsImplementedBy (inspectedObject, interfaceDefinition) {
    var member,
        memberReference,
        memberValue,
        memberType;
    /*
     * IMPORTANT note: we test the object itself (i.e. own members and the prototype).
     * That's why the hasOwnProperty is skipped
     */
    /*jslint forin:true*/
    for (member in interfaceDefinition.prototype) {
        if ("constructor" === member) { // Object
            continue;
        }
        memberReference = interfaceDefinition.prototype[member];
        memberValue = inspectedObject[member];
        memberType = typeof memberValue;
        if (typeof memberReference !== memberType) {
            return false;
        }
        if ("function" === memberType && memberReference.length !== memberValue.length) {
            return false;
        }
    }
    return true;
}

/**
 * Retrieve an object implementing the expected interface from an object.
 * This is done in two tests:
 * - Either the object implements the interface, it is returned
 * - Or the object implements IUnknown, then queryInterface is used
 *
 * @param {Object} objectInstance object to inspect
 * @param {gpf.interfaces.Interface} interfaceDefinition reference interface
 * @param {Boolean} [throwError=true] throwError Throws an error if the interface is not found (otherwise, null
 * is returned)
 * @return {Object|null} Object implementing the interface or null
 */
function _gpfQueryInterface (objectInstance, interfaceDefinition, throwError) {
    var result = null;
    if (_gpfIsImplementedBy(objectInstance, interfaceDefinition)) {
        return objectInstance;
    } else if (_gpfIsImplementedBy(objectInstance, gpf.interfaces.IUnknown)) {
        result = objectInstance.queryInterface(interfaceDefinition);
    }
    if (null === result && (undefined === throwError || throwError)) {
        throw gpf.Error.interfaceExpected({
            name: _gpfGetClassDefinition(interfaceDefinition)._name
        });
    }
    return result;
}

var
    /**
     * gpf.attributes shortcut
     *
     * @type {Object}
     * @private
     */
    _gpfI = gpf.interfaces = {

        /**
         * Verify that the object (or class) implements the current interface
         *
         * @param {Object|Function} inspectedObject object (or class) to inspect
         * @param {gpf.interfaces.Interface} interfaceDefinition reference interface
         * @return {Boolean} True if implemented
         */
        isImplementedBy: function (inspectedObject, interfaceDefinition) {
            if (inspectedObject instanceof Function) {
                inspectedObject = inspectedObject.prototype;
            }
            return _gpfIsImplementedBy(inspectedObject, interfaceDefinition);
        },

        // @inheritdoc _gpfQueryInterface
        query: _gpfQueryInterface

    };

/**
 * Defines an interface (relies on gpf.define)
 *
 * @param {String} name Interface name. If it contains a dot, it is treated as absolute contextual.
 * Otherwise, it is relative to "gpf.interfaces"
 * @param {Function|string} [base=undefined] base Base interface (or contextual name)
 * @param {Object} [definition=undefined] definition Interface definition
 * @return {Function}
 */
var _gpfDefIntrf = _gpfGenerateCustomDefineHandler("gpf.interfaces", "Interface");

/**
 * Base class for any interface
 *
 * @class gpf.interfaces.Interface
 */
_gpfDefine("gpf.interfaces.Interface");

//region IEventTarget

/* istanbul ignore next */ // Interface
_gpfDefIntrf("IEventDispatcher", {

    /**
     * Add an event listener to the dispatcher
     *
     * @param {String} event Event name
     * @param {gpf.events.Handler} eventsHandler Event handler
     * @return {gpf.interfaces.IEventDispatcher} Self
     * @chainable
     */
    addEventListener: function (event, eventsHandler) {
        _gpfIgnore(event, eventsHandler);
        return this;
    },

    /**
     * Remove an event listener from the dispatcher
     *
     * @param {String} event Event name
     * @param {gpf.events.Handler} eventsHandler Event handler
     * @return {gpf.interfaces.IEventDispatcher} Self
     * @chainable
     */
    removeEventListener: function (event, eventsHandler) {
        _gpfIgnore(event, eventsHandler);
        return this;
    },

    /**
     * Broadcast the event
     *
     * @param {String|gpf.events.Event} event Event name or object
     * @param {Object} [params={}] Event parameters
     * @return {gpf.events.Event} Event object
     */
    dispatchEvent: function (event, params) {
        return _gpfIgnore(event, params);
    }

});

//endregion

//region IUnknown

/* istanbul ignore next */ // Interface
/**
 * Provide a way for any object to implement an interface using an intermediate object (this avoids overloading the
 * object with temporary / useless members)
 */
var _gpfIUnknown = _gpfDefIntrf("IUnknown", {

    /**
     * Retrieves an object supporting the provided interface (maybe the object itself)
     *
     * @param {gpf.interfaces.Interface} interfaceDefinition The expected interface
     * @return {Object|null} The object supporting the interface (or null)
     */
    queryInterface: function (interfaceDefinition) {
        _gpfIgnore(interfaceDefinition);
        return null;
    }

});

//endregion

//region InterfaceImplement attribute

/**
 * Retrieves an object supporting the provided interface (maybe the object itself).
 * This function (added to any object declaring the attribute InterfaceImplementAttribute with a builder) uses the
 * InterfaceImplementAttribute attribute list to see if the one corresponding to the interface provides a builder and
 * calls it
 *
 * @param {gpf.interfaces.Interface} interfaceDefinition The expected interface
 * @return {Object|null} The object supporting the interface (or null)
 * @this Object
 */
function _queryInterface (interfaceDefinition) {
    /*jshint validthis:true*/ // Called with apply
    var
        array = (new gpf.attributes.Map(this))
            .getMemberAttributes("Class")
            .filter(gpf.attributes.InterfaceImplementAttribute),
        builder;
    if (!array.every(function (attribute) {
        builder = attribute._builder;
        return attribute._interfaceDefinition !== interfaceDefinition || !builder;
    })) {
        if ("function" === typeof builder) {
            return builder(this);
        }
        // Expects a member name
        return this[builder]();
    }
    // Otherwise
    return null;
}

/**
 * Creates a wrapper calling _queryInterface and, if no result is built, the original one defined in the object
 * prototype
 *
 * @param {Function} orgQueryInterface Interface to retreive
 * @return {Function} Default queryInterface implementation
 * @closure
 */
function _wrapQueryInterface (orgQueryInterface) {
    return function (interfaceDefinition) {
        /*eslint-disable no-invalid-this*/
        _gpfIgnore(interfaceDefinition);
        var result = _queryInterface.apply(this, arguments);
        if (null === result) {
            result = orgQueryInterface.apply(this, arguments);
        }
        return result;
        /*eslint-enable no-invalid-this*/
    };
}

/**
 * Document the class by telling the interface is implemented and may provide a builder to access it
 *
 * @param {Function} interfaceDefinition Implemented interface definition
 * @param {Function|String} [queryInterfaceBuilder=undefined] queryInterfaceBuilder Function or member name to executed
 * if the implemented interface is requested
 *
 * @class gpf.attributes.InterfaceImplementAttribute
 * @extends gpf.attributes.ClassAttribute
 * @alias gpf.$InterfaceImplement
 */
_gpfDefAttr("$InterfaceImplement", {
    "-": {

        // Interface definition
        "[_interfaceDefinition]": [gpf.$ClassProperty(false)],
        _interfaceDefinition: _gpfEmptyFunc,

        /**
         * Builder function
         *
         * @type {Function|null}
         */
        "[_builder]": [gpf.$ClassProperty(false)],
        _builder: null,

        _addMissingInterfaceMembers: function (objPrototype) {
            var
                iProto = this._interfaceDefinition.prototype,
                member;
            // Fill the missing methods
            /*jslint forin:true*/ // Wants inherited members too
            for (member in iProto) {
                if (!(member in objPrototype)) {
                    objPrototype[member] = iProto[member];
                }
            }
        },

        _addQueryInterface: function (objPrototype) {
            if (undefined === objPrototype.queryInterface) {
                objPrototype.queryInterface = _queryInterface;
                _gpfAttributesAdd(objPrototype.constructor, "Class", [gpf.$InterfaceImplement(_gpfIUnknown)]);
            } else if (_queryInterface !== objPrototype.queryInterface) {
                /*
                 * Two situations here:
                 * - Either the class (or one of its parent) already owns the $InterfaceImplement attribute
                 * - Or the class (or one of its parent) implements its own queryInterface
                 * In that last case, wrap it to use the attribute version first
                 *
                 * In both case, we take the assumption that the class already owns
                 * gpf.$InterfaceImplement(gpf.interfaces.IUnknown)
                 */
                objPrototype.queryInterface = _wrapQueryInterface(objPrototype.queryInterface);
            }
        }

    },
    "#": {

        // @inheritdoc gpf.attributes.Attribute#_alterPrototype
        _alterPrototype: function (objPrototype) {
            if (this._builder) {
                this._addQueryInterface(objPrototype);
            } else {
                this._addMissingInterfaceMembers(objPrototype);
                // Get the interface's attributes apply them to the obj
                new gpf.attributes.Map()
                    .fillFromClassDef(_gpfGetClassDefinition(this._interfaceDefinition))
                    .forEach(function (attributes, member) {
                        _gpfAttributesAdd(objPrototype.constructor, member, attributes);
                    });
            }
        }

    },

    "+": {

        /**
         * @param {Function} interfaceDefinition Interface definition
         * @param {Function|null} [queryInterfaceBuilder=null]
         * queryInterfaceBuilder Builder function
         * @constructor
         */
        constructor: function (interfaceDefinition, queryInterfaceBuilder) {
            this._interfaceDefinition = interfaceDefinition;
            if (queryInterfaceBuilder) {
                this._builder = queryInterfaceBuilder;
            }
        }

    }

});

//endregion
<|endoftext|>


<commit_before>'use strict'

const fs=require('fs')

module.exports=function(packageJson,langs,lang,pageTitle,cssUrls,jsUrls){
	const langData={
		en: {
			name: "English",
			repo: "source code",
			bugs: "report bugs",
			nojs: "JavaScript is disabled. This page requires JavaScript to function properly.",
		},
		ru: {
			name: "Русский",
			repo: "исходный код",
			bugs: "сообщения об ошибках",
			nojs: "JavaScript отключен. Данной странице требуется JavaScript для нормального функционирования.",
		},
	}
	const templateCss=fs.readFileSync(`${__dirname}/template.css`,'utf8')
	const allCssUrls=[...cssUrls,`../../lib/${packageJson.name}.css`]
	const allJsUrls=['http://code.jquery.com/jquery-2.2.1.min.js',...jsUrls,`../../lib/${packageJson.name}.js`]
	return [
		"<!DOCTYPE html>",
		`<html lang=${lang}>`,
		"",
		"<meta charset=utf-8>",
		`<title>${pageTitle}</title>`,
		allCssUrls.map(cssUrl=>`<link rel=stylesheet href=${cssUrl}>`).join("\n"),
		allJsUrls.map(jsUrl=>`<script src=${jsUrl}></script>`).join("\n"),
		"",
		"",
		"",
		"",
		...langs.length<=1?[]:[
			"
",
			...langs.map(otherLang=>{
				if (lang==otherLang) {
					return `<li class=active>${langData[otherLang].name}</li>`
				} else {
					return `<li><a href=../../${otherLang}/base/ lang=${otherLang} hreflang=${otherLang}>${langData[otherLang].name}</a></li>`
				}
			}),
			"
",
		],
		"
",
		`<li><a href=https://github.com/${packageJson.repository}>${langData[lang].repo}</a></li>`,
		`<li><a href=${packageJson.bugs.url}>${langData[lang].bugs}</a></li>`,
		"
",
		"",
		`<div class=${packageJson.name}>${langData[lang].nojs}</div>`,
		"",
		"",
	].join("\n")
}
<commit_msg>updated jQuery<commit_after>'use strict'

const fs=require('fs')

module.exports=function(packageJson,langs,lang,pageTitle,cssUrls,jsUrls){
	const langData={
		en: {
			name: "English",
			repo: "source code",
			bugs: "report bugs",
			nojs: "JavaScript is disabled. This page requires JavaScript to function properly.",
		},
		ru: {
			name: "Русский",
			repo: "исходный код",
			bugs: "сообщения об ошибках",
			nojs: "JavaScript отключен. Данной странице требуется JavaScript для нормального функционирования.",
		},
	}
	const templateCss=fs.readFileSync(`${__dirname}/template.css`,'utf8')
	const allCssUrls=[...cssUrls,`../../lib/${packageJson.name}.css`]
	const allJsUrls=['https://code.jquery.com/jquery-2.2.3.min.js',...jsUrls,`../../lib/${packageJson.name}.js`]
	return [
		"<!DOCTYPE html>",
		`<html lang=${lang}>`,
		"",
		"<meta charset=utf-8>",
		`<title>${pageTitle}</title>`,
		allCssUrls.map(cssUrl=>`<link rel=stylesheet href=${cssUrl}>`).join("\n"),
		allJsUrls.map(jsUrl=>`<script src=${jsUrl}></script>`).join("\n"),
		"",
		"",
		"",
		"",
		...langs.length<=1?[]:[
			"
",
			...langs.map(otherLang=>{
				if (lang==otherLang) {
					return `<li class=active>${langData[otherLang].name}</li>`
				} else {
					return `<li><a href=../../${otherLang}/base/ lang=${otherLang} hreflang=${otherLang}>${langData[otherLang].name}</a></li>`
				}
			}),
			"
",
		],
		"
",
		`<li><a href=https://github.com/${packageJson.repository}>${langData[lang].repo}</a></li>`,
		`<li><a href=${packageJson.bugs.url}>${langData[lang].bugs}</a></li>`,
		"
",
		"",
		`<div class=${packageJson.name}>${langData[lang].nojs}</div>`,
		"",
		"",
	].join("\n")
}
<|endoftext|>


<commit_before>require('./connect');
<commit_msg>Set Primus as a known global<commit_after>mocha.globals(['Primus']);

require('./connect');
<|endoftext|>


<commit_before>'use strict';
var path = require('path');
var assert = require('yeoman-assert');
var helpers = require('yeoman-generator').test;

describe('generator-typings:app', function () {
  before(function (done) {
    this.timeout(30000);
    helpers.run(path.join(__dirname, '../generators/app'))
      .withPrompts({
        sourceUri: 'facebook/reactDOM',
        projectUri: 'unional/typed-reactDOM',
        isNpm: false,
        username: 'unional'
      })
      .on('end', done);
  });

  it('creates files', function () {
    assert.file([
      '.vscode/settings.json',
      'test/test.ts',
      '.editorconfig',
      '.gitignore',
      'LICENSE',
      'index.d.ts',
      'README.md',
      'package.json',
      'typings.json',
      'tsconfig.json'
    ]);
  });
});
<commit_msg>Increase timeout<commit_after>'use strict';
var path = require('path');
var assert = require('yeoman-assert');
var helpers = require('yeoman-generator').test;

describe('app', function () {
  before(function (done) {
    this.timeout(60000);
    helpers.run(path.join(__dirname, '../generators/app'))
      .withPrompts({
        sourceUri: 'visionmedia/batch',
        projectUri: 'typed-typings/npm-batch',
        // Set to false to avoid longer test time.
        isNpm: false,
        username: 'unional',
        license: 'MIT',
        name: 'unional'
      })
      .on('end', done);
  });

  it('creates files', function () {
    assert.file([
      '.vscode/settings.json',
      'test/test.ts',
      '.editorconfig',
      '.gitignore',
      'LICENSE',
      'index.d.ts',
      'README.md',
      'package.json',
      'typings.json',
      'tsconfig.json'
    ]);
  });
});
<|endoftext|>


<commit_before>'use strict';

var expect = require('expect.js'),
	Steppy = require('twostep').Steppy,
	childProcess = require('child_process');

var isWin = process.platform.substring(0, 3) === 'win';

describe('Binary', function() {

	var binPath;
	if (!isWin) {
		binPath = 'bin/nrun';
	} else {
		binPath = 'node bin/nrun';
	}

	var exec = function() {
		var callback = arguments[arguments.length - 1];
		arguments[arguments.length - 1] = function(err, stdout, stderr) {
			callback(err, {stdout: stdout, stderr: stderr});
		};
		childProcess.exec.apply(childProcess.exec, arguments);
	};

	it('call without arguments should return list of scripts', function(done) {
		Steppy(
			function() {
				exec(binPath, this.slot());
			},
			function(err, binData) {
				expect(binData.stderr).not.ok();
				expect(binData.stdout).eql(
					[
						'Scripts for "nrun":',
						'  test',
						'    mocha test/ --bail --reporter spec',
						'  testls',
						'    ls'
					].join('\n') + '\n'
				);
				this.pass(null);
			},
			done
		);
	});

	it('call with single arg calls target script', function(done) {
		Steppy(
			function() {
				exec(binPath + ' testls', this.slot());
				exec('ls', this.slot());
			},
			function(err, binData, lsData) {
				expect(binData.stderr).not.ok();
				expect(binData.stdout).eql(
					[
						'> nrun testls',
						'> ls ',
						lsData.stdout
					].join('\n')
				);
				this.pass(null);
			},
			done
		);
	});

	it('call with more args calls target script args', function(done) {
		Steppy(
			function() {
				exec(binPath + ' testls -l -h', this.slot());
				exec('ls -l -h', this.slot());
			},
			function(err, binData, lsData) {
				var escaped;
				if (isWin) {
					escaped = '> ls -l -h';
				} else {
					escaped = '> ls "-l" "-h"';
				}

				expect(binData.stderr).not.ok();
				expect(binData.stdout).eql(
					[
						'> nrun testls',
						escaped,
						lsData.stdout
					].join('\n')
				);
				this.pass(null);
			},
			done
		);
	});

	it('call with unexisted script throw error to stderr', function(done) {
		exec(binPath + ' unexisted', function(err, binData) {
			expect(err).ok();
			expect(err.message).match(/Unknown script: unexisted/);
			done();
		});
	});
});
<commit_msg>remove trailing tabs<commit_after>'use strict';

var expect = require('expect.js'),
	Steppy = require('twostep').Steppy,
	childProcess = require('child_process');

var isWin = process.platform.substring(0, 3) === 'win';

describe('Binary', function() {

	var binPath;
	if (!isWin) {
		binPath = 'bin/nrun';
	} else {
		binPath = 'node bin/nrun';
	}

	var exec = function() {
		var callback = arguments[arguments.length - 1];
		arguments[arguments.length - 1] = function(err, stdout, stderr) {
			callback(err, {stdout: stdout, stderr: stderr});
		};
		childProcess.exec.apply(childProcess.exec, arguments);
	};

	it('call without arguments should return list of scripts', function(done) {
		Steppy(
			function() {
				exec(binPath, this.slot());
			},
			function(err, binData) {
				expect(binData.stderr).not.ok();
				expect(binData.stdout).eql(
					[
						'Scripts for "nrun":',
						'  test',
						'    mocha test/ --bail --reporter spec',
						'  testls',
						'    ls'
					].join('\n') + '\n'
				);
				this.pass(null);
			},
			done
		);
	});

	it('call with single arg calls target script', function(done) {
		Steppy(
			function() {
				exec(binPath + ' testls', this.slot());
				exec('ls', this.slot());
			},
			function(err, binData, lsData) {
				expect(binData.stderr).not.ok();
				expect(binData.stdout).eql(
					[
						'> nrun testls',
						'> ls ',
						lsData.stdout
					].join('\n')
				);
				this.pass(null);
			},
			done
		);
	});

	it('call with more args calls target script args', function(done) {
		Steppy(
			function() {
				exec(binPath + ' testls -l -h', this.slot());
				exec('ls -l -h', this.slot());
			},
			function(err, binData, lsData) {
				var escaped;
				if (isWin) {
					escaped = '> ls -l -h';
				} else {
					escaped = '> ls "-l" "-h"';
				}

				expect(binData.stderr).not.ok();
				expect(binData.stdout).eql(
					[
						'> nrun testls',
						escaped,
						lsData.stdout
					].join('\n')
				);
				this.pass(null);
			},
			done
		);
	});

	it('call with unexisted script throw error to stderr', function(done) {
		exec(binPath + ' unexisted', function(err, binData) {
			expect(err).ok();
			expect(err.message).match(/Unknown script: unexisted/);
			done();
		});
	});
});
<|endoftext|>


<commit_before>var assert = require('assert'),
  sandbox = require('sandboxed-module'),
  vows = require('vows');

vows.describe('cli').addBatch({
  '': {
    topic: function () {
      return {};
    },
    '': function (topic) {
    }
  }
}).exportTo(module);<commit_msg>Dummy commit to trigger travis-ci build.<commit_after>var assert = require('assert'),
  sandbox = require('sandboxed-module'),
  vows = require('vows');

vows.describe('cli').addBatch({
  ' ': {
    topic: function () {
      return {};
    },
    '': function (topic) {
    }
  }
}).exportTo(module);<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var Layer = this.Layer = Group.extend({
	/** @lends Layer# */

	// DOCS: improve constructor code example.
	/**
	 * Creates a new Layer item and places it at the end of the
	 * {@link Project#layers} array. The newly created layer will be activated,
	 * so all newly created items will be placed within it.
	 *
	 * @param {Array} [children] An array of items that will be added to the
	 * newly created layer.
	 *
	 * @example
	 * var layer = new Layer();
	 *
	 * @class The Layer item represents a layer in a Paper.js project.
	 *
	 * The layer which is currently active can be accessed through
	 * {@link Project#activeLayer}.
	 * An array of all layers in a project can be accessed through
	 * {@link Project#layers}.
	 *
	 * @extends Group
	 * @constructs Layer
	 */
	initialize: function(items) {
		this._project = paper.project;
		// Push it onto project.layers and set index:
		this._index = this._project.layers.push(this) - 1;
		this.base.apply(this, arguments);
		this.activate();
	},

	/**
	* Removes the layer from its project's layers list
	* or its parent's children list.
	*/
	_remove: function(deselect, notify) {
		if (this._parent)
			return this.base(deselect, notify);
		if (this._index != null) {
			if (deselect)
				this.setSelected(false);
			Base.splice(this._project.layers, null, this._index, 1);
			// Tell project we need a redraw. This is similar to _changed()
			// mechanism.
			this._project._needsRedraw();
			return true;
		}
		return false;
	},

	getNextSibling: function() {
		return this._parent ? this.base()
				: this._project.layers[this._index + 1] || null;
	},

	getPreviousSibling: function() {
		return this._parent ? this.base()
				: this._project.layers[this._index - 1] || null;
	},

	// DOCS: improve Layer#activate() example.
	/**
	 * Activates the layer.
	 *
	 * @example
	 * var layer = new Layer();
	 * layer.activate();
	 * console.log(project.activeLayer == layer); // true
	 */
	activate: function() {
		this._project.activeLayer = this;
	}
}, new function () {
	function insert(above) {
		return function(item) {
			// If the item is a layer and contained within Project#layers, use
			// our own version of move().
			if (item instanceof Layer && !item._parent
						&& this._remove(false, true)) {
				Base.splice(item._project.layers, [this],
						item._index + (above ? 1 : -1), 0);
				this._setProject(item._project);
				return true;
			}
			return this.base(item);
		};
	}

	return {
		insertAbove: insert(true),

		insertBelow: insert(false)
	};
});
<commit_msg>Use faster Function#call() when arguments are clear.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

var Layer = this.Layer = Group.extend({
	/** @lends Layer# */

	// DOCS: improve constructor code example.
	/**
	 * Creates a new Layer item and places it at the end of the
	 * {@link Project#layers} array. The newly created layer will be activated,
	 * so all newly created items will be placed within it.
	 *
	 * @param {Array} [children] An array of items that will be added to the
	 * newly created layer.
	 *
	 * @example
	 * var layer = new Layer();
	 *
	 * @class The Layer item represents a layer in a Paper.js project.
	 *
	 * The layer which is currently active can be accessed through
	 * {@link Project#activeLayer}.
	 * An array of all layers in a project can be accessed through
	 * {@link Project#layers}.
	 *
	 * @extends Group
	 * @constructs Layer
	 */
	initialize: function(items) {
		this._project = paper.project;
		// Push it onto project.layers and set index:
		this._index = this._project.layers.push(this) - 1;
		this.base.call(this, items);
		this.activate();
	},

	/**
	* Removes the layer from its project's layers list
	* or its parent's children list.
	*/
	_remove: function(deselect, notify) {
		if (this._parent)
			return this.base(deselect, notify);
		if (this._index != null) {
			if (deselect)
				this.setSelected(false);
			Base.splice(this._project.layers, null, this._index, 1);
			// Tell project we need a redraw. This is similar to _changed()
			// mechanism.
			this._project._needsRedraw();
			return true;
		}
		return false;
	},

	getNextSibling: function() {
		return this._parent ? this.base()
				: this._project.layers[this._index + 1] || null;
	},

	getPreviousSibling: function() {
		return this._parent ? this.base()
				: this._project.layers[this._index - 1] || null;
	},

	// DOCS: improve Layer#activate() example.
	/**
	 * Activates the layer.
	 *
	 * @example
	 * var layer = new Layer();
	 * layer.activate();
	 * console.log(project.activeLayer == layer); // true
	 */
	activate: function() {
		this._project.activeLayer = this;
	}
}, new function () {
	function insert(above) {
		return function(item) {
			// If the item is a layer and contained within Project#layers, use
			// our own version of move().
			if (item instanceof Layer && !item._parent
						&& this._remove(false, true)) {
				Base.splice(item._project.layers, [this],
						item._index + (above ? 1 : -1), 0);
				this._setProject(item._project);
				return true;
			}
			return this.base(item);
		};
	}

	return {
		insertAbove: insert(true),

		insertBelow: insert(false)
	};
});
<|endoftext|>


<commit_before>/**
 * Test dependencies
 */
var exec = require("child_process").exec

var test = require("tape")

var utils = require("./utils")
var cssnext = require("..")

/**
 * CLI tests
 */
var input = utils.readFixture("cli")
var output = utils.readFixture("cli.expected")

var cssnextBin = "node bin/cssnext" // node bin is used to help for windows

test("cli", function(t) {
  var planned = 0

  exec(cssnextBin + " test/fixtures/cli.css test/fixtures/cli.output--io.css", function(err) {
    if (err) { throw err }
    var res = utils.readFixture("cli.output--io")
    t.equal(res, output, "should read from a file and write to a file")
    utils.remove("cli.output--io")
  })
  planned+=1

  exec(cssnextBin + " test/fixtures/cli.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, output, "should read from a file and write to stdout")
  })
  planned+=1

  var childProcess = exec(cssnextBin, function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, output, "should read from stdin and write to stdout")
  })
  childProcess.stdin.write(new Buffer(input))
  childProcess.stdin.end()
  planned+=1

  exec(cssnextBin + " test/fixtures/cli.wtf.css", function(err, stdout, stderr) {
    t.ok(err && err.code === 1, "should return an error when input file is unreadable")
    t.ok(utils.contains(stderr, "Unable to read file"), "should show that the input file is not found")
  })
  planned+=2

  exec(cssnextBin + " test/fixtures/cli.error.css", function(err, stdout, stderr) {
    t.ok(err && err.code === 2, "should throw an error")
    t.ok(utils.contains(stderr, "encounters an error"), "should output a readable error")
    t.ok(utils.contains(stderr, "If this error looks like a bug, please report it here"), "should show the url where to report bugs")
  })
  planned+=3

  exec(cssnextBin + " --verbose test/fixtures/cli.css test/fixtures/cli.output--verbose.css", function(err, stdout) {
    if (err) { throw err }
    t.ok(utils.contains(stdout, "Output written"), "should log on --verbose")
    utils.remove("cli.output--verbose")
  })
  planned+=1

  exec(cssnextBin + " --no-import test/fixtures/import.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("import"), "should not import on --no-import")
  })
  planned+=1

  exec(cssnextBin + " --no-url test/fixtures/url.css", {cwd: process.cwd()}, function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("url/dep"), "should not adjust url on --no-url")
  })
  planned+=1

  exec(cssnextBin + " --compress test/fixtures/compress.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("compress.default.expected").trim(), "should compress on --compress")
  })
  planned+=1

  exec(cssnextBin + " --sourcemap test/fixtures/sourcemap.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("sourcemap.expected").trim(), "should add sourcemap on --sourcemap")
  })
  planned+=1

  var toSpace = require("to-space-case")
  var toSlug = require("to-slug-case")
  var features = Object.keys(cssnext.features)
  var no = "--no-" + features.map(function(feature) { return toSlug(feature)}).join(" --no-")
  features.forEach(function(feature) {
    var slug = toSlug(feature)
    var output = utils.readFixture("features/" + slug)
    exec(cssnextBin + " " + no + " test/fixtures/features/" + slug + ".css", function(err, stdout) {
      if (err) { throw err }
      t.equal(stdout, output, "should not modify input of '" + toSpace(feature) + "' fixture if all features are disabled")
    })
  })
  planned+=features.length

  exec(cssnextBin + " --watch", function(err, stdout, stderr) {
    t.ok(err && err.code === 3, "should return an error when <input> or <output> are missing when `--watch` option passed")
    t.ok(utils.contains(stderr, "--watch option need"), "should show an explanation when <input> or <output> are missing when `--watch` option passed")
  })
  planned+=2

  exec(cssnextBin + " --watch test/fixtures/cli.css", function(err, stdout, stderr) {
    t.ok(err && err.code === 3, "should return an error when <output> is missing when `--watch` option passed")
    t.ok(utils.contains(stderr, "--watch option need"), "should show an explanation when <output> is missing when `--watch` option passed")
  })
  planned+=2

  var watchProcess = exec(cssnextBin + " --watch test/fixtures/cli.error.css test/fixtures/cli.output--watch.css", function(err) {
    t.ok(err && err.signal === "SIGTERM", "should only be killed by an interrupt when `--watch` option passed")
    if (err && !err.killed) { throw err }
  })
  var msgWatch = "should output error messages when `--watch` option passed"
  var watchTimeout = setTimeout(function() {
    t.fail(msgWatch)
    watchProcess.kill("SIGTERM")
  }, 5000)
  watchProcess.stderr.on("data", function(data) {
    if (utils.contains(data, "encounters an error")) {
      t.pass(msgWatch)
      clearTimeout(watchTimeout)
      watchProcess.kill("SIGTERM")
    }
  })
  planned+=2

  t.plan(planned)
})
<commit_msg>Avoid CLI test about --watch on CI since they do not like them<commit_after>/**
 * Test dependencies
 */
var exec = require("child_process").exec

var test = require("tape")

var utils = require("./utils")
var cssnext = require("..")

/**
 * CLI tests
 */
var input = utils.readFixture("cli")
var output = utils.readFixture("cli.expected")

var cssnextBin = "node bin/cssnext" // node bin is used to help for windows

test("cli", function(t) {
  var planned = 0

  exec(cssnextBin + " test/fixtures/cli.css test/fixtures/cli.output--io.css", function(err) {
    if (err) { throw err }
    var res = utils.readFixture("cli.output--io")
    t.equal(res, output, "should read from a file and write to a file")
    utils.remove("cli.output--io")
  })
  planned+=1

  exec(cssnextBin + " test/fixtures/cli.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, output, "should read from a file and write to stdout")
  })
  planned+=1

  var childProcess = exec(cssnextBin, function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, output, "should read from stdin and write to stdout")
  })
  childProcess.stdin.write(new Buffer(input))
  childProcess.stdin.end()
  planned+=1

  exec(cssnextBin + " test/fixtures/cli.wtf.css", function(err, stdout, stderr) {
    t.ok(err && err.code === 1, "should return an error when input file is unreadable")
    t.ok(utils.contains(stderr, "Unable to read file"), "should show that the input file is not found")
  })
  planned+=2

  exec(cssnextBin + " test/fixtures/cli.error.css", function(err, stdout, stderr) {
    t.ok(err && err.code === 2, "should throw an error")
    t.ok(utils.contains(stderr, "encounters an error"), "should output a readable error")
    t.ok(utils.contains(stderr, "If this error looks like a bug, please report it here"), "should show the url where to report bugs")
  })
  planned+=3

  exec(cssnextBin + " --verbose test/fixtures/cli.css test/fixtures/cli.output--verbose.css", function(err, stdout) {
    if (err) { throw err }
    t.ok(utils.contains(stdout, "Output written"), "should log on --verbose")
    utils.remove("cli.output--verbose")
  })
  planned+=1

  exec(cssnextBin + " --no-import test/fixtures/import.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("import"), "should not import on --no-import")
  })
  planned+=1

  exec(cssnextBin + " --no-url test/fixtures/url.css", {cwd: process.cwd()}, function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("url/dep"), "should not adjust url on --no-url")
  })
  planned+=1

  exec(cssnextBin + " --compress test/fixtures/compress.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("compress.default.expected").trim(), "should compress on --compress")
  })
  planned+=1

  exec(cssnextBin + " --sourcemap test/fixtures/sourcemap.css", function(err, stdout) {
    if (err) { throw err }
    t.equal(stdout, utils.readFixture("sourcemap.expected").trim(), "should add sourcemap on --sourcemap")
  })
  planned+=1

  var toSpace = require("to-space-case")
  var toSlug = require("to-slug-case")
  var features = Object.keys(cssnext.features)
  var no = "--no-" + features.map(function(feature) { return toSlug(feature)}).join(" --no-")
  features.forEach(function(feature) {
    var slug = toSlug(feature)
    var output = utils.readFixture("features/" + slug)
    exec(cssnextBin + " " + no + " test/fixtures/features/" + slug + ".css", function(err, stdout) {
      if (err) { throw err }
      t.equal(stdout, output, "should not modify input of '" + toSpace(feature) + "' fixture if all features are disabled")
    })
  })
  planned+=features.length

  exec(cssnextBin + " --watch", function(err, stdout, stderr) {
    t.ok(err && err.code === 3, "should return an error when <input> or <output> are missing when `--watch` option passed")
    t.ok(utils.contains(stderr, "--watch option need"), "should show an explanation when <input> or <output> are missing when `--watch` option passed")
  })
  planned+=2

  exec(cssnextBin + " --watch test/fixtures/cli.css", function(err, stdout, stderr) {
    t.ok(err && err.code === 3, "should return an error when <output> is missing when `--watch` option passed")
    t.ok(utils.contains(stderr, "--watch option need"), "should show an explanation when <output> is missing when `--watch` option passed")
  })
  planned+=2

  // I don't success to call the kill() process from node and both Travis CI and Appveyor
  // so we avoid this test on this environnements
  if (!process.env.TRAVIS && !process.env.APPVEYOR) {
    var watchProcess = exec(cssnextBin + " --watch test/fixtures/cli.error.css test/fixtures/cli.output--watch.css", function(err) {
      t.ok(err && err.signal === "SIGTERM", "should only be killed by an interrupt when `--watch` option passed")
      if (err && !err.killed) { throw err }
    })
    var msgWatch = "should output error messages when `--watch` option passed"
    var watchTimeout = setTimeout(function() {
      t.fail(msgWatch)
      watchProcess.kill()
    }, 5000)
    watchProcess.stderr.on("data", function(data) {
      if (utils.contains(data, "encounters an error")) {
        t.pass(msgWatch)
        clearTimeout(watchTimeout)
        watchProcess.kill()
      }
    })
    planned+=2
  }

  t.plan(planned)
})
<|endoftext|>


<commit_before>var fs = require("fs");
var path = require('path');
Blend.defineClass('Builder.commands.build.Base', {
    extend: 'Builder.commands.Base',
    requires: [
        'Builder.analyzer.Dependency'
    ],
    projectConfig: null,
    runInternal: null,
    depAnalyzer: null,
    cache: null,
    /**
     * Command entry point
     */
    run: function () {
        var me = this, res;
        me.callParent.apply(me, arguments);
        res = me.loadProjectConfiguration();
        if (res.isvalid === true) {
            me.setDefaultPaths();
            return me.runInternal();
        } else {
            Logger.error(res.error);
            me.exit();
            return false;
        }
    },
    /**
     * Creates a dependency anayzer object
     */
    createDepAnalyzer: function () {
        var me = this;
        me.depAnalyzer = me.depAnalyzer || Blend.create('Builder.analyzer.Dependency', {
            projectConfig: me.projectConfig,
            env: {
                projectFolder: me.projectFolder,
                sourceFolder: me.sourceFolder,
                resourcesFolder: me.resourcesFolder,
                sassFolder: me.sassFolder,
                sdkFolder: Blend.getSDKFolder()
            }
        });
    },
    /**
     * Prepares the paths of the environment in which this builder is running
     * @returns {undefined}
     */
    setDefaultPaths: function () {
        var me = this,
                p = me.options.path.split(path.sep),
                last = p[p.length - 1];
        if (last.indexOf('.json') !== -1) {
            p.pop();
        }
        p = p.join(path.sep);
        me.callParent.apply(me, [p])
    },
    /*
     * Validates the project configuration file
     */
    validateConfig: function () {
        var me = this, valid = false, error = null,
                cfg = me.projectConfig;
        if (Blend.isString(cfg.mainClass)) {
            if (Blend.isString(cfg.name)) {
                if (Blend.isString(cfg.indexTemplate)) {
                    if (Blend.isString(cfg.theme)) {
                        if (me.isValidAppType(cfg)) {
                            valid = true;
                        } else {
                            error = "Invalid or missing application type. Use either webapp or touch app";
                        }
                    } else {
                        error = 'Missing or invalid [theme]';
                    }
                } else {
                    error = 'Missing or invalid [indexTemplate]';
                }
            } else {
                error = 'Missing or invalid [name]';
            }
        } else {
            error = 'Missing or invalid [mainClass]';
        }
        return {
            isvalid: valid,
            error: (error ? error + ' in project configuration;' : null)
        };
    },
    /**
     * Check if the provided application type is valid
     */
    isValidAppType: function (cfg) {
        return (!Blend.isNullOrUndef(cfg.type) && (cfg.type === 'webapp' || cfg.type === 'touchapp'));
    },
    /**
     * Loads the project configuration file
     * @returns {Boolean}
     */
    loadProjectConfiguration: function () {
        var me = this;
        try {
            me.projectConfig = require(me.options.path);
            return me.validateConfig();
        } catch (e) {
            Logger.error(e);
            me.exit();
            return false;
        }
    },
    /**
     * Exit the program if not in watch mode
     * @returns {undefined}
     */
    exit: function (force) {
        var me = this;
        force = force || false;
        if (me.options.watch !== true || force === true) {
            process.exit(1);
        }
    },
    /**
     * Retuns the extension of the index template for the watcher
     * @returns {unresolved}
     */
    getIndexTemplateExtension: function () {
        var me = this;
        return path.extname(me.projectConfig.indexTemplate);
    }
});

<commit_msg>Enhanced initDepAnalyzer to update the projectConfig on each run<commit_after>var fs = require("fs");
var path = require('path');
Blend.defineClass('Builder.commands.build.Base', {
    extend: 'Builder.commands.Base',
    requires: [
        'Builder.analyzer.Dependency'
    ],
    projectConfig: null,
    runInternal: null,
    depAnalyzer: null,
    cache: null,
    /**
     * Command entry point
     */
    run: function () {
        var me = this, res;
        me.callParent.apply(me, arguments);
        res = me.loadProjectConfiguration();
        if (res.isvalid === true) {
            me.setDefaultPaths();
            return me.runInternal();
        } else {
            Logger.error(res.error);
            me.exit();
            return false;
        }
    },
    /**
     * Creates a dependency anayzer object
     */
    initDepAnalyzer: function () {
        var me = this;
        if (!me.depAnalyzer) {
            me.depAnalyzer = Blend.create('Builder.analyzer.Dependency', {
                projectConfig: me.projectConfig,
                env: {
                    projectFolder: me.projectFolder,
                    sourceFolder: me.sourceFolder,
                    resourcesFolder: me.resourcesFolder,
                    sassFolder: me.sassFolder,
                    sdkFolder: Blend.getSDKFolder()
                }
            });
        } else {
            me.depAnalyzer.projectConfig = me.projectConfig;
        }
    },
    /**
     * Prepares the paths of the environment in which this builder is running
     * @returns {undefined}
     */
    setDefaultPaths: function () {
        var me = this,
                p = me.options.path.split(path.sep),
                last = p[p.length - 1];
        if (last.indexOf('.json') !== -1) {
            p.pop();
        }
        p = p.join(path.sep);
        me.callParent.apply(me, [p])
    },
    /*
     * Validates the project configuration file
     */
    validateConfig: function () {
        var me = this, valid = false, error = null,
                cfg = me.projectConfig;
        if (Blend.isString(cfg.mainClass)) {
            if (Blend.isString(cfg.name)) {
                if (Blend.isString(cfg.indexTemplate)) {
                    if (Blend.isString(cfg.theme)) {
                        if (me.isValidAppType(cfg)) {
                            valid = true;
                        } else {
                            error = "Invalid or missing application type. Use either webapp or touch app";
                        }
                    } else {
                        error = 'Missing or invalid [theme]';
                    }
                } else {
                    error = 'Missing or invalid [indexTemplate]';
                }
            } else {
                error = 'Missing or invalid [name]';
            }
        } else {
            error = 'Missing or invalid [mainClass]';
        }
        return {
            isvalid: valid,
            error: (error ? error + ' in project configuration;' : null)
        };
    },
    /**
     * Check if the provided application type is valid
     */
    isValidAppType: function (cfg) {
        return (!Blend.isNullOrUndef(cfg.type) && (cfg.type === 'webapp' || cfg.type === 'touchapp'));
    },
    /**
     * Loads the project configuration file
     * @returns {Boolean}
     */
    loadProjectConfiguration: function () {
        var me = this;
        try {
            me.projectConfig = require(me.options.path);
            return me.validateConfig();
        } catch (e) {
            Logger.error(e);
            me.exit();
            return false;
        }
    },
    /**
     * Exit the program if not in watch mode
     * @returns {undefined}
     */
    exit: function (force) {
        var me = this;
        force = force || false;
        if (me.options.watch !== true || force === true) {
            process.exit(1);
        }
    },
    /**
     * Retuns the extension of the index template for the watcher
     * @returns {unresolved}
     */
    getIndexTemplateExtension: function () {
        var me = this;
        return path.extname(me.projectConfig.indexTemplate);
    }
});

<|endoftext|>