<commit_before>/* eslint-env jest */
/* @flow */

import { compose, callMiddleware } from '../index';

describe('compose', () => {
  const defaultContinueFn = () => Promise.resolve({});

  describe('empty brigade', () => {
    const emptyBrigade = [];

    it('should propagate the return value of calling next', async () => {
      const expectedValue = { result: 'hello' };
      const nextFn = () => Promise.resolve(expectedValue);

      const result = await compose(emptyBrigade)({}, nextFn, defaultContinueFn);
      expect(result).toBe(expectedValue);
    });

    it('should catch an exception in next', async () => {
      const expectedMessage = 'test';
      const nextFn = () => {
        throw new Error(expectedMessage);
      };

      expect.assertions(2);
      try {
        await compose(emptyBrigade)({}, nextFn, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toBe(expectedMessage);
      }
    });

    it('should catch a rejected promise in next', async () => {
      const expectedMessage = 'hello';
      const nextFn = () => Promise.reject(new Error(expectedMessage));

      expect.assertions(2);
      try {
        await compose(emptyBrigade)({}, nextFn, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toBe(expectedMessage);
      }
    });

    it('should only accept functions for next', async () => {
      const notAFunction = {};
      expect.assertions(2);
      try {
        // $FlowFixMe: override violation for test case
        await compose(emptyBrigade)({}, notAFunction, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be a function/);
      }
    });

    it('should only accept functions for terminate', async () => {
      const notAFunction = {};
      expect.assertions(2);
      try {
        // $FlowFixMe: override violation for test case
        await compose(emptyBrigade)({}, defaultContinueFn, notAFunction);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be a function/);
      }
    });
  });

  describe('parameters', () => {
    it('should only accept an array of middleware', async () => {
      const notAnArray = null;
      expect.assertions(2);
      try {
        // $FlowFixMe: override violation for test case
        await compose(notAnArray);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be an array/);
      }
    });

    it('should only accept middleware as functions', async () => {
      const notMiddleware = {};
      expect.assertions(2);
      try {
        // $FlowFixMe: override type violation for test case
        await compose([notMiddleware]);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be composed of functions/);
      }
    });
  });

  describe('deep brigade', () => {
    const deepRequest = {
      receivedCalls: []
    };
    const deepBrigade = [
      async (request, next) => {
        request.receivedCalls.push(1);
        const result = await next();
        request.receivedCalls.push(6);
        return result;
      },
      async (request, next) => {
        request.receivedCalls.push(2);
        const result = await next();
        request.receivedCalls.push(5);
        return result;
      },
      async (request, next) => {
        request.receivedCalls.push(3);
        const result = await next();
        request.receivedCalls.push(4);
        return result;
      }
    ];

    beforeEach(() => {
      deepRequest.receivedCalls = [];
    });

    it('should call middleware stack in the right order', async () => {
      await compose(deepBrigade)(deepRequest, defaultContinueFn, defaultContinueFn);
      expect(deepRequest.receivedCalls).toEqual([1, 2, 3, 4, 5, 6]);
    });

    it('should be able to be called twice', async () => {
      const brigade = compose(deepBrigade);
      await brigade(deepRequest, defaultContinueFn, defaultContinueFn);
      expect(deepRequest.receivedCalls).toEqual([1, 2, 3, 4, 5, 6]);
      deepRequest.receivedCalls = [];
      await brigade(deepRequest, defaultContinueFn, defaultContinueFn);
      expect(deepRequest.receivedCalls).toEqual([1, 2, 3, 4, 5, 6]);
    });

    it('should propagate the value of calling next', async () => {
      const exepctedValue = { result: 'hello' };
      const nextFn = () => Promise.resolve(exepctedValue);
      const brigade = compose(deepBrigade);
      const result = await brigade(deepRequest, nextFn, defaultContinueFn);
      expect(result).toBe(exepctedValue);
    });

    describe('with terminate at the end', () => {
      const brigadeWithTerminate = deepBrigade.slice(0);
      brigadeWithTerminate.push((request, next, terminate) => terminate());

      it('should propagate the value of calling terminate', async () => {
        const exepctedValue = { result: 'hello' };
        const terminateFn = () => Promise.resolve(exepctedValue);
        const brigade = compose(brigadeWithTerminate);
        const result = await brigade(deepRequest, defaultContinueFn, terminateFn);
        expect(result).toBe(exepctedValue);
      });

      it('should catch an exception in terminate', async () => {
        const expectedMessage = 'hello';
        const terminateFn = () => {
          throw new Error(expectedMessage);
        };

        expect.assertions(2);
        try {
          const brigade = compose(brigadeWithTerminate);
          await brigade(deepRequest, defaultContinueFn, terminateFn);
        } catch (e) {
          expect(e).toBeInstanceOf(Error);
          expect(e.message).toBe(expectedMessage);
        }
      });

      it('should catch a rejected promise in terminate', async () => {
        const expectedMessage = 'hello';
        const terminateFn = () => Promise.reject(new Error(expectedMessage));

        expect.assertions(2);
        try {
          const brigade = compose(brigadeWithTerminate);
          await brigade(deepRequest, defaultContinueFn, terminateFn);
        } catch (e) {
          expect(e).toBeInstanceOf(Error);
          expect(e.message).toBe(expectedMessage);
        }
      });
    });
  });

  it('should throw if middleware does not return a promise', async () => {
    const notAPromise = 0;
    const middleware = [
      // $FlowFixMe: override violation for test case
      () => notAPromise
    ];

    expect.assertions(2);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/Promise was expected/);
    }
  });

  it('should keep the request across multiple middleware', () => {
    const originalRequest = {};

    const stack = [];

    const middleware = async (receivedRequest, next) => {
      const result = await next();
      expect(receivedRequest).toBe(originalRequest);
      return result;
    };

    stack.push(middleware);
    stack.push(middleware);
    stack.push(middleware);

    return compose(stack)(originalRequest, defaultContinueFn, defaultContinueFn);
  });

  it('should reject on errors in middleware', async () => {
    const stack = [];
    const sentintel = jest.fn();

    stack.push(() => { throw new Error(); });

    await compose(stack)({}, defaultContinueFn, defaultContinueFn)
      .then(sentintel)
      .catch((err) => {
        expect(err).toBeInstanceOf(Error);
      });
    expect(sentintel).not.toHaveBeenCalled();
  });

  it('should not call next with middleware parameters', () => {
    const brigade = [];
    expect.assertions(3);
    return compose(brigade)({}, (request, next, terminate) => {
      expect(request).toBe(undefined);
      expect(next).toBe(undefined);
      expect(terminate).toBe(undefined);
      return Promise.resolve({});
    }, defaultContinueFn);
  });

  it('should not call terminate with middleware parameters', () => {
    const brigade = [(context, next, terminate) => terminate()];
    expect.assertions(3);
    return compose(brigade)({}, defaultContinueFn, (request, next, terminate) => {
      expect(request).toBe(undefined);
      expect(next).toBe(undefined);
      expect(terminate).toBe(undefined);
      return Promise.resolve({});
    });
  });

  it('should throw if terminate is called multiple times at the beginning of a brigade', async () => {
    const middleware = [
      function badDog(request, next, terminate) {
        terminate();
        return terminate();
      },
      (request, next) => next()
    ];

    expect.assertions(3);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its terminate function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if next() is called multiple times at the beginning of a brigade', async () => {
    const middleware = [
      function badDog(request, next) {
        next();
        return next();
      },
      (request, next) => next()
    ];

    expect.assertions(3);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its next function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if terminate is called multiple times at the end of a brigade', async () => {
    const middleware = [
      (request, next) => next(),
      function badDog(request, next, terminate) {
        terminate();
        return terminate();
      }
    ];

    expect.assertions(2);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its terminate function after the middleware chain has been terminated/);
    }
  });

  it('should throw if next is called multiple times at the end of a brigade', async () => {
    const middleware = [
      (request, next) => next(),
      function badDog(request, next) {
        next();
        return next();
      }
    ];

    expect.assertions(3);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its next function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if terminate is called after next', async () => {
    const middleware = [
      async function badDog(request, next, terminate) {
        next();
        return terminate();
      }
    ];

    expect.assertions(3);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its terminate function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if next is called after terminate', async () => {
    const middleware = [
      // Flow should probably catch this
      async function badDog(request, next, terminate) {
        await terminate();
        await next();
      }
    ];

    expect.assertions(3);
    try {
      await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its next function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  describe('middleware error message', () => {
    it('should identify middleware with an error', async () => {
      const middleware = [
        function badDog() {
          // $FlowFixMe: override violation for test case
          return 'woof';
        }
      ];

      expect.assertions(2);
      try {
        await compose(middleware)({}, defaultContinueFn, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/badDog/);
      }
    });
  });

  describe('callMiddleware', () => {
    it('should work', async () => {
      const middleware = [
        async (request, next) => next()
      ];

      const expectedResponse = {};
      const result = await callMiddleware(compose(middleware), {}, expectedResponse);
      expect(result).toBe(expectedResponse);
    });

    it('should allow a rejected promise to be returned', async () => {
      const expectedMessage = 'hello';
      const middleware = [
        (request, next, terminate) => {
          return Promise.reject(new Error(expectedMessage));
        }
      ];

      expect.assertions(2);
      try {
        await callMiddleware(compose(middleware), {}, {});
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toBe(expectedMessage);
      }
    });

    it('should detect if next or terminate not called', async () => {
      const middleware = [
        // Flow should probably catch this
        async () => {
        }
      ];

      expect.assertions(2);
      try {
        await callMiddleware(compose(middleware), {}, {});
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/terminated with an unexpected response/);
      }
    });

    it('should detect if the chain is broken', async () => {
      const middleware = [
        // Flow should probably catch this
        async (request, next) => {
          next();
        },
        async (request, next) => next()
      ];

      expect.assertions(2);
      try {
        await callMiddleware(compose(middleware), {}, {});
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/terminated with an unexpected response/);
      }
    });
  });
});
<commit_msg>Introduce explainatory variables for object constants<commit_after>/* eslint-env jest */
/* @flow */

import { compose, callMiddleware } from '../index';

describe('compose', () => {
  const defaultRequest = {};
  const defaultResponse = {};
  const defaultContinueFn = () => Promise.resolve(defaultResponse);

  describe('empty brigade', () => {
    const emptyBrigade = [];

    it('should propagate the return value of calling next', async () => {
      const expectedValue = { result: 'hello' };
      const nextFn = () => Promise.resolve(expectedValue);

      const result = await compose(emptyBrigade)(defaultRequest, nextFn, defaultContinueFn);
      expect(result).toBe(expectedValue);
    });

    it('should catch an exception in next', async () => {
      const expectedMessage = 'test';
      const nextFn = () => {
        throw new Error(expectedMessage);
      };

      expect.assertions(2);
      try {
        await compose(emptyBrigade)(defaultRequest, nextFn, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toBe(expectedMessage);
      }
    });

    it('should catch a rejected promise in next', async () => {
      const expectedMessage = 'hello';
      const nextFn = () => Promise.reject(new Error(expectedMessage));

      expect.assertions(2);
      try {
        await compose(emptyBrigade)(defaultRequest, nextFn, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toBe(expectedMessage);
      }
    });

    it('should only accept functions for next', async () => {
      const notAFunction = {};
      expect.assertions(2);
      try {
        // $FlowFixMe: override violation for test case
        await compose(emptyBrigade)(defaultRequest, notAFunction, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be a function/);
      }
    });

    it('should only accept functions for terminate', async () => {
      const notAFunction = {};
      expect.assertions(2);
      try {
        // $FlowFixMe: override violation for test case
        await compose(emptyBrigade)(defaultRequest, defaultContinueFn, notAFunction);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be a function/);
      }
    });
  });

  describe('parameters', () => {
    it('should only accept an array of middleware', async () => {
      const notAnArray = null;
      expect.assertions(2);
      try {
        // $FlowFixMe: override violation for test case
        await compose(notAnArray);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be an array/);
      }
    });

    it('should only accept middleware as functions', async () => {
      const notMiddleware = {};
      expect.assertions(2);
      try {
        // $FlowFixMe: override type violation for test case
        await compose([notMiddleware]);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/must be composed of functions/);
      }
    });
  });

  describe('deep brigade', () => {
    const deepRequest = {
      receivedCalls: []
    };
    const deepBrigade = [
      async (request, next) => {
        request.receivedCalls.push(1);
        const result = await next();
        request.receivedCalls.push(6);
        return result;
      },
      async (request, next) => {
        request.receivedCalls.push(2);
        const result = await next();
        request.receivedCalls.push(5);
        return result;
      },
      async (request, next) => {
        request.receivedCalls.push(3);
        const result = await next();
        request.receivedCalls.push(4);
        return result;
      }
    ];

    beforeEach(() => {
      deepRequest.receivedCalls = [];
    });

    it('should call middleware stack in the right order', async () => {
      await compose(deepBrigade)(deepRequest, defaultContinueFn, defaultContinueFn);
      expect(deepRequest.receivedCalls).toEqual([1, 2, 3, 4, 5, 6]);
    });

    it('should be able to be called twice', async () => {
      const brigade = compose(deepBrigade);
      await brigade(deepRequest, defaultContinueFn, defaultContinueFn);
      expect(deepRequest.receivedCalls).toEqual([1, 2, 3, 4, 5, 6]);
      deepRequest.receivedCalls = [];
      await brigade(deepRequest, defaultContinueFn, defaultContinueFn);
      expect(deepRequest.receivedCalls).toEqual([1, 2, 3, 4, 5, 6]);
    });

    it('should propagate the value of calling next', async () => {
      const exepctedValue = { result: 'hello' };
      const nextFn = () => Promise.resolve(exepctedValue);
      const brigade = compose(deepBrigade);
      const result = await brigade(deepRequest, nextFn, defaultContinueFn);
      expect(result).toBe(exepctedValue);
    });

    describe('with terminate at the end', () => {
      const brigadeWithTerminate = deepBrigade.slice(0);
      brigadeWithTerminate.push((request, next, terminate) => terminate());

      it('should propagate the value of calling terminate', async () => {
        const exepctedValue = { result: 'hello' };
        const terminateFn = () => Promise.resolve(exepctedValue);
        const brigade = compose(brigadeWithTerminate);
        const result = await brigade(deepRequest, defaultContinueFn, terminateFn);
        expect(result).toBe(exepctedValue);
      });

      it('should catch an exception in terminate', async () => {
        const expectedMessage = 'hello';
        const terminateFn = () => {
          throw new Error(expectedMessage);
        };

        expect.assertions(2);
        try {
          const brigade = compose(brigadeWithTerminate);
          await brigade(deepRequest, defaultContinueFn, terminateFn);
        } catch (e) {
          expect(e).toBeInstanceOf(Error);
          expect(e.message).toBe(expectedMessage);
        }
      });

      it('should catch a rejected promise in terminate', async () => {
        const expectedMessage = 'hello';
        const terminateFn = () => Promise.reject(new Error(expectedMessage));

        expect.assertions(2);
        try {
          const brigade = compose(brigadeWithTerminate);
          await brigade(deepRequest, defaultContinueFn, terminateFn);
        } catch (e) {
          expect(e).toBeInstanceOf(Error);
          expect(e.message).toBe(expectedMessage);
        }
      });
    });
  });

  it('should throw if middleware does not return a promise', async () => {
    const notAPromise = 0;
    const middleware = [
      // $FlowFixMe: override violation for test case
      () => notAPromise
    ];

    expect.assertions(2);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/Promise was expected/);
    }
  });

  it('should keep the request across multiple middleware', () => {
    const originalRequest = {};

    const stack = [];

    const middleware = async (receivedRequest, next) => {
      const result = await next();
      expect(receivedRequest).toBe(originalRequest);
      return result;
    };

    stack.push(middleware);
    stack.push(middleware);
    stack.push(middleware);

    return compose(stack)(originalRequest, defaultContinueFn, defaultContinueFn);
  });

  it('should reject on errors in middleware', async () => {
    const stack = [];
    const sentintel = jest.fn();

    stack.push(() => { throw new Error(); });

    await compose(stack)(defaultRequest, defaultContinueFn, defaultContinueFn)
      .then(sentintel)
      .catch((err) => {
        expect(err).toBeInstanceOf(Error);
      });
    expect(sentintel).not.toHaveBeenCalled();
  });

  it('should not call next with middleware parameters', () => {
    const brigade = [];
    expect.assertions(3);
    return compose(brigade)(defaultRequest, (request, next, terminate) => {
      expect(request).toBe(undefined);
      expect(next).toBe(undefined);
      expect(terminate).toBe(undefined);
      return Promise.resolve(defaultResponse);
    }, defaultContinueFn);
  });

  it('should not call terminate with middleware parameters', () => {
    const brigade = [(context, next, terminate) => terminate()];
    expect.assertions(3);
    return compose(brigade)(defaultRequest, defaultContinueFn, (request, next, terminate) => {
      expect(request).toBe(undefined);
      expect(next).toBe(undefined);
      expect(terminate).toBe(undefined);
      return Promise.resolve(defaultResponse);
    });
  });

  it('should throw if terminate is called multiple times at the beginning of a brigade', async () => {
    const middleware = [
      function badDog(request, next, terminate) {
        terminate();
        return terminate();
      },
      (request, next) => next()
    ];

    expect.assertions(3);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its terminate function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if next() is called multiple times at the beginning of a brigade', async () => {
    const middleware = [
      function badDog(request, next) {
        next();
        return next();
      },
      (request, next) => next()
    ];

    expect.assertions(3);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its next function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if terminate is called multiple times at the end of a brigade', async () => {
    const middleware = [
      (request, next) => next(),
      function badDog(request, next, terminate) {
        terminate();
        return terminate();
      }
    ];

    expect.assertions(2);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its terminate function after the middleware chain has been terminated/);
    }
  });

  it('should throw if next is called multiple times at the end of a brigade', async () => {
    const middleware = [
      (request, next) => next(),
      function badDog(request, next) {
        next();
        return next();
      }
    ];

    expect.assertions(3);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its next function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if terminate is called after next', async () => {
    const middleware = [
      async function badDog(request, next, terminate) {
        next();
        return terminate();
      }
    ];

    expect.assertions(3);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its terminate function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  it('should throw if next is called after terminate', async () => {
    const middleware = [
      // Flow should probably catch this
      async function badDog(request, next, terminate) {
        await terminate();
        await next();
      }
    ];

    expect.assertions(3);
    try {
      await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
    } catch (e) {
      expect(e).toBeInstanceOf(Error);
      expect(e.message).toMatch(/has called its next function after the middleware chain has been terminated/);
      expect(e.message).toMatch(/badDog/);
    }
  });

  describe('middleware error message', () => {
    it('should identify middleware with an error', async () => {
      const middleware = [
        function badDog() {
          // $FlowFixMe: override violation for test case
          return 'woof';
        }
      ];

      expect.assertions(2);
      try {
        await compose(middleware)(defaultRequest, defaultContinueFn, defaultContinueFn);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/badDog/);
      }
    });
  });

  describe('callMiddleware', () => {
    it('should work', async () => {
      const middleware = [
        async (request, next) => next()
      ];

      const expectedResponse = {};
      const result = await callMiddleware(compose(middleware), defaultRequest, expectedResponse);
      expect(result).toBe(expectedResponse);
    });

    it('should allow a rejected promise to be returned', async () => {
      const expectedMessage = 'hello';
      const middleware = [
        (request, next, terminate) => {
          return Promise.reject(new Error(expectedMessage));
        }
      ];

      expect.assertions(2);
      try {
        await callMiddleware(compose(middleware), defaultRequest, defaultResponse);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toBe(expectedMessage);
      }
    });

    it('should detect if next or terminate not called', async () => {
      const middleware = [
        // Flow should probably catch this
        async () => {
        }
      ];

      expect.assertions(2);
      try {
        await callMiddleware(compose(middleware), defaultRequest, defaultResponse);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/terminated with an unexpected response/);
      }
    });

    it('should detect if the chain is broken', async () => {
      const middleware = [
        // Flow should probably catch this
        async (request, next) => {
          next();
        },
        async (request, next) => next()
      ];

      expect.assertions(2);
      try {
        await callMiddleware(compose(middleware), defaultRequest, defaultResponse);
      } catch (e) {
        expect(e).toBeInstanceOf(Error);
        expect(e.message).toMatch(/terminated with an unexpected response/);
      }
    });
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import { mount } from 'enzyme';

import Tabl3 from '../index';
import conector from '../conector/ajax';
import removeParamFromQS from '../core/removeParamFromQS';

jest.mock('../conector/ajax');

describe("removeParamFromQS [http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a]", function () {
  test('remove limit', () => {
    expect(removeParamFromQS('name__icontains', 'http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a')).toEqual('http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name');
  });
  test('remove limit false', () => {
    expect(removeParamFromQS('name__', 'http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a')).toEqual('http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a');
  });
});

let component = {};
let table2new = {};

describe("Tabl3 GET [http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a]", function () {
  test('create Table & asign className', () => {
    component2 = mount(
      <Tabl3
        config={{
          ajax: {
            url: 'http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a',
            method: 'GET',
            liveHeaders: () => {
              return {
                Authorization: 'JWT 298KJHkj1KJH',
              };
            },
          },
          conector: conector,
          debug: {
            inputSearch: true,
            paginator: true,
            initiaAjax: true,
            dataset: true,
          },
          onBeforeSend: e => ((e) => { }),
          onAfterSend: e => ((e) => { }),
          errors: {
            onAjaxError: e => ((e) => { /* console.log(arguments);*/ }),
          },
          table: {
            className: 'table table-hover table-condensed',
            resetButton: {
              className: 'btn btn-sm btn-warning',
              title: 'Restablecer',
              onReset: e => ((e) => { }),
            },
            thead: {
              className: '',
              actions: {
                className: '',
                cssTH: {
                  width: '130px',
                  minWidth: '130px',
                },
                component: instance => (<button>{instance.id}</button>),
              },
            },
          },
          paginator: {
            prevLink: 3,
            nextLink: 3,
          },
          columns: [
            {
              title: 'Nombre',
              name: 'name',
              textEmpty: 'Sin deatlle',
              inputPlaceholder: 'Buscar',
              input: 'name__icontains',
            },
            /* end fake columns */
          ],
        }}/>
    );
    table2new2 = component2;
    expect(table2new2.find('table').props().className).toEqual('table-2-new table table-hover table-condensed');
  });
});

describe("Tabl3 GET [http://127.0.0.1:8000/colors/?limit=4]", function () {
  test('create Table & asign className', () => {
    component = mount(
      <Tabl3
        config={{
          ajax: {
            url: 'http://127.0.0.1:8000/colors/?limit=4',
            method: 'GET',
            liveHeaders: () => {
              return {
                Authorization: 'JWT 298KJHkj1KJH',
              };
            },
          },
          conector: conector,
          debug: {
            inputSearch: true,
            paginator: true,
            initiaAjax: true,
            dataset: true,
          },
          onBeforeSend: e => ((e) => { }),
          onAfterSend: e => ((e) => { }),
          errors: {
            onAjaxError: e => ((e) => { /* console.log(arguments);*/ }),
          },
          table: {
            className: 'table table-hover table-condensed',
            resetButton: {
              className: 'btn btn-sm btn-warning',
              title: 'Restablecer',
              onReset: e => ((e) => { }),
            },
            thead: {
              className: '',
              actions: {
                className: '',
                cssTH: {
                  width: '130px',
                  minWidth: '130px',
                },
                component: instance => (<button>{instance.id}</button>),
              },
            },
          },
          paginator: {
            prevLink: 3,
            nextLink: 3,
          },
          columns: [
            {
              title: 'Id',
              name: 'id',
              textEmpty: 'Sin nombre',
              cssTH: {
                width: '250px',
                minWidth: '250px',
              },
              input: 'id',
              inputPlaceholder: 'Ingrese nemotécnico a buscar',
              inputClassName: 'form-control',
            },
            {
              title: 'Nombre',
              name: 'name',
              textEmpty: 'Sin deatlle',
              inputPlaceholder: 'Buscar',
              input: 'name__icontains',
            },
            /* end fake columns */
          ],
        }}/>
    );
    table2new = component;
    expect(table2new.find('table').props().className).toEqual('table-2-new table table-hover table-condensed');
  });

  test('state.config.ajax.url === http://127.0.0.1:8000/colors/?limit=4', () => {
    expect(table2new.props().config.paginator.nextLink).toEqual(3);
    expect(table2new.props().config.paginator.prevLink).toEqual(3);
    expect(table2new.props().config.ajax.url).toEqual('http://127.0.0.1:8000/colors/?limit=4');
  });

  test('create paginator <ul>', () => {
    expect(table2new.find('ul').length).toEqual(1);
  });
  test('test create paginator by service mock (6 link)', () => {
    expect(table2new.find('ul').children().length).toEqual(6);
  });
  test('test create td in tbody (12 = 4 objects x3 columns)', () => {
    expect(table2new.find('tbody').find('td').length).toEqual(12);
    expect(table2new.find('tbody').find('tr').children().length).toEqual(12);
  });
  test('test create header (3 th)', () => {
    expect(table2new.find('th').length).toEqual(3);
  });
  test('count total: 13', () => {
    const instanceTable = table2new.instance();
    expect(instanceTable.state.paginator.count).toEqual(13);
  });
});

let component2 = {};
let table2new2 = {};

describe("Tabl3 GET [http://127.0.0.1:8000/colors/?limit=4]", function () {
  test('create Table & click paginator', () => {
    component2 = mount(
      <Tabl3
        config={{
          ajax: {
            url: 'http://127.0.0.1:8000/colors/?limit=4',
            method: 'GET',
            simulateError: true,
            liveHeaders: () => {
              return {
                Authorization: 'JWT 298KJHkj1KJH',
              };
            },
          },
          conector: conector,
          debug: {
            inputSearch: true,
            paginator: true,
            initiaAjax: true,
            dataset: true,
          },
          onBeforeSend: e => ((e) => { }),
          onAfterSend: e => ((e) => { }),
          errors: {
            onAjaxError: e => ((e) => { /* console.log(arguments);*/ }),
          },
          table: {
            className: 'table table-hover table-condensed',
            resetButton: {
              className: 'btn btn-sm btn-warning',
              title: 'Restablecer',
              onReset: e => ((e) => { }),
            },
            thead: {
              className: '',
              actions: {
                className: '',
                cssTH: {
                  width: '130px',
                  minWidth: '130px',
                },
                component: instance => (<button>{instance.id}</button>),
              },
            },
          },
          paginator: {
            prevLink: 3,
            nextLink: 3,
          },
          columns: [
            {
              title: 'Nombre',
              name: 'name',
              textEmpty: 'Sin deatlle',
              inputPlaceholder: 'Buscar',
              input: 'name__icontains',
            },
            /* end fake columns */
          ],
        }}/>
    );
    const table2new2Instance = component2.instance();
    console.log(table2new2Instance.updateState('PAGINATOR_GOTO_PAGE', 1));
    });
});

describe("Tabl3 Generate Error", function () {
  test('error', () => {
    const tableError = mount(
      <Tabl3 config={{
        table: {
          className: 'table table-hover table-condensed',
          resetButton: {
            className: 'btn btn-sm btn-warning',
            title: 'Restablecer',
            onReset: e => ((e) => { }),
          },
          thead: {
            className: '',
            actions: {
              className: '',
              cssTH: {
                width: '130px',
                minWidth: '130px',
              },
              component: instance => (<button>{instance.id}</button>),
            },
          },
        },
        paginator: {
          prevLink: 3,
          nextLink: 3,
        },
        columns: [
          {
            title: 'Nombre',
            name: 'name',
            textEmpty: 'Sin deatlle',
            inputPlaceholder: 'Buscar',
            input: 'name__icontains',
          },
          /* end fake columns */
        ],
      }}/>);
      const instanceTableError = tableError.instance();
    expect(instanceTableError.initError).toEqual(true);
  });
});
<commit_msg>up coverage<commit_after>import React from 'react';
import { mount } from 'enzyme';

import Tabl3 from '../index';
import conector from '../conector/ajax';
import removeParamFromQS from '../core/removeParamFromQS';

jest.mock('../conector/ajax');

describe("removeParamFromQS [http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a]", function () {
  test('remove limit', () => {
    expect(removeParamFromQS('name__icontains', 'http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a')).toEqual('http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name');
  });
  test('remove limit false', () => {
    expect(removeParamFromQS('name__', 'http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a')).toEqual('http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a');
  });
});

let component = {};
let table2new = {};

describe("Tabl3 GET [http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a]", function () {
  test('create Table & asign className', () => {
    component2 = mount(
      <Tabl3
        config={{
          ajax: {
            url: 'http://127.0.0.1:8000/colors/?limit=4&offset=4&ordering=-name&name__icontains=a',
            method: 'GET',
            liveHeaders: () => {
              return {
                Authorization: 'JWT 298KJHkj1KJH',
              };
            },
          },
          conector: conector,
          debug: {
            inputSearch: true,
            paginator: true,
            initiaAjax: true,
            dataset: true,
          },
          onBeforeSend: e => ((e) => { }),
          onAfterSend: e => ((e) => { }),
          errors: {
            onAjaxError: e => ((e) => { /* console.log(arguments);*/ }),
          },
          table: {
            className: 'table table-hover table-condensed',
            resetButton: {
              className: 'btn btn-sm btn-warning',
              title: 'Restablecer',
              onReset: e => ((e) => { }),
            },
            thead: {
              className: '',
              actions: {
                className: '',
                cssTH: {
                  width: '130px',
                  minWidth: '130px',
                },
                component: instance => (<button>{instance.id}</button>),
              },
            },
          },
          paginator: {
            prevLink: 3,
            nextLink: 3,
          },
          columns: [
            {
              title: 'Nombre',
              name: 'name',
              textEmpty: 'Sin deatlle',
              inputPlaceholder: 'Buscar',
              input: 'name__icontains',
            },
            /* end fake columns */
          ],
        }}/>
    );
    table2new2 = component2;
    expect(table2new2.find('table').props().className).toEqual('table-2-new table table-hover table-condensed');
  });
});

describe("Tabl3 GET [http://127.0.0.1:8000/colors/?limit=4]", function () {
  test('create Table & asign className', () => {
    component = mount(
      <Tabl3
        config={{
          ajax: {
            url: 'http://127.0.0.1:8000/colors/?limit=4',
            method: 'GET',
            liveHeaders: () => {
              return {
                Authorization: 'JWT 298KJHkj1KJH',
              };
            },
          },
          conector: conector,
          debug: {
            inputSearch: true,
            paginator: true,
            initiaAjax: true,
            dataset: true,
          },
          onBeforeSend: e => ((e) => { }),
          onAfterSend: e => ((e) => { }),
          errors: {
            onAjaxError: e => ((e) => { /* console.log(arguments);*/ }),
          },
          table: {
            className: 'table table-hover table-condensed',
            resetButton: {
              className: 'btn btn-sm btn-warning',
              title: 'Restablecer',
              onReset: e => ((e) => { }),
            },
            thead: {
              className: '',
              actions: {
                className: '',
                cssTH: {
                  width: '130px',
                  minWidth: '130px',
                },
                component: instance => (<button>{instance.id}</button>),
              },
            },
          },
          paginator: {
            prevLink: 3,
            nextLink: 3,
          },
          columns: [
            {
              title: 'Id',
              name: 'id',
              textEmpty: 'Sin nombre',
              cssTH: {
                width: '250px',
                minWidth: '250px',
              },
              input: 'id',
              inputPlaceholder: 'Ingrese nemotécnico a buscar',
              inputClassName: 'form-control',
            },
            {
              title: 'Nombre',
              name: 'name',
              textEmpty: 'Sin deatlle',
              inputPlaceholder: 'Buscar',
              input: 'name__icontains',
            },
            /* end fake columns */
          ],
        }}/>
    );
    table2new = component;
    expect(table2new.find('table').props().className).toEqual('table-2-new table table-hover table-condensed');
  });

  test('state.config.ajax.url === http://127.0.0.1:8000/colors/?limit=4', () => {
    expect(table2new.props().config.paginator.nextLink).toEqual(3);
    expect(table2new.props().config.paginator.prevLink).toEqual(3);
    expect(table2new.props().config.ajax.url).toEqual('http://127.0.0.1:8000/colors/?limit=4');
  });

  test('create paginator <ul>', () => {
    expect(table2new.find('ul').length).toEqual(1);
  });
  test('test create paginator by service mock (6 link)', () => {
    expect(table2new.find('ul').children().length).toEqual(6);
  });
  test('test create td in tbody (12 = 4 objects x3 columns)', () => {
    expect(table2new.find('tbody').find('td').length).toEqual(12);
    expect(table2new.find('tbody').find('tr').children().length).toEqual(12);
  });
  test('test create header (3 th)', () => {
    expect(table2new.find('th').length).toEqual(3);
  });
  test('count total: 13', () => {
    const instanceTable = table2new.instance();
    expect(instanceTable.state.paginator.count).toEqual(13);
  });
});

let component2 = {};
let table2new2 = {};

describe("Tabl3 GET [http://127.0.0.1:8000/colors/?limit=4]", function () {
  test('create Table & click paginator', () => {
    component2 = mount(
      <Tabl3
        config={{
          ajax: {
            url: 'http://127.0.0.1:8000/colors/?limit=4',
            method: 'GET',
            simulateError: true,
            liveHeaders: () => {
              return {
                Authorization: 'JWT 298KJHkj1KJH',
              };
            },
          },
          conector: conector,
          debug: {
            inputSearch: true,
            paginator: true,
            initiaAjax: true,
            dataset: true,
          },
          onBeforeSend: e => ((e) => { }),
          onAfterSend: e => ((e) => { }),
          errors: {
            onAjaxError: e => ((e) => { /* console.log(arguments);*/ }),
          },
          table: {
            className: 'table table-hover table-condensed',
            resetButton: {
              className: 'btn btn-sm btn-warning',
              title: 'Restablecer',
              onReset: e => ((e) => { }),
            },
            thead: {
              className: '',
              actions: {
                className: '',
                cssTH: {
                  width: '130px',
                  minWidth: '130px',
                },
                component: instance => (<button>{instance.id}</button>),
              },
            },
          },
          paginator: {
            prevLink: 3,
            nextLink: 3,
          },
          columns: [
            {
              title: 'Nombre',
              name: 'name',
              textEmpty: 'Sin deatlle',
              inputPlaceholder: 'Buscar',
              input: 'name__icontains',
            },
            /* end fake columns */
          ],
        }}/>
    );
    const table2new2Instance = component2.instance();
    console.log(table2new2Instance.updateState('PAGINATOR_GOTO_PAGE', 1));
    });
});

describe("Tabl3 Generate Error", function () {
  test('error', () => {
    const tableError = mount(
      <Tabl3 config={{
        table: {
          className: 'table table-hover table-condensed',
          resetButton: {
            className: 'btn btn-sm btn-warning',
            title: 'Restablecer',
            onReset: e => ((e) => { }),
          },
          thead: {
            className: '',
            actions: {
              className: '',
              cssTH: {
                width: '130px',
                minWidth: '130px',
              },
              component: instance => (<button>{instance.id}</button>),
            },
          },
        },
        paginator: {
          prevLink: 3,
          nextLink: 3,
        },
        columns: [
          {
            title: 'Nombre',
            name: 'name',
            textEmpty: 'Sin deatlle',
            inputPlaceholder: 'Buscar',
            input: 'name__icontains',
          },
          /* end fake columns */
        ],
      }}/>);
      const instanceTableError = tableError.instance();
    expect(instanceTableError.initError).toEqual(true);
  });
});

describe("Tabl3 onAjaxError", function () {

  const inputTest = (handlerChangeInput, instance, value) => {
    const hdc = handlerChangeInput;
    return (
      <input value={value} onChange={hdc} />
    )
  };
  test('render custom imput', () => {
    const tableError = mount(
      <Tabl3 config={{
        ajax: {
          url: 'http://127.0.0.1:8000/colors/?limit=4',
          method: 'GET',
          simulateError: true,
          liveHeaders: () => {
            return {
              Authorization: 'JWT 298KJHkj1KJH',
            };
          },
        },
        conector: conector,
        table: {
          className: 'table table-hover table-condensed',
          resetButton: {
            className: 'btn btn-sm btn-warning',
            title: 'Restablecer',
            onReset: e => ((e) => { }),
          },
          thead: {
            className: '',
            actions: {
              className: '',
              cssTH: {
                width: '130px',
                minWidth: '130px',
              },
              component: instance => (<button>{instance.id}</button>),
            },
          },
        },
        onAjaxError: e => ((e) => { }),
        paginator: {
          prevLink: 3,
          nextLink: 3,
        },
        columns: [
          {
            title: 'Nombre',
            name: 'name',
            textEmpty: 'Sin deatlle',
            inputPlaceholder: 'Buscar',
            input: 'name__icontains',
            componentInput: inputTest,
          },
          /* end fake columns */
        ],
      }}/>);
      const instanceTableError = tableError.instance();
    expect(instanceTableError.initError).toEqual(false);
  });
});
<|endoftext|>


<commit_before>import { sqlectron } from '../../browser/remote';

export const CLOSE_CONNECTION = 'CLOSE_CONNECTION';
export const CONNECTION_REQUEST = 'CONNECTION_REQUEST';
export const CONNECTION_SUCCESS = 'CONNECTION_SUCCESS';
export const CONNECTION_FAILURE = 'CONNECTION_FAILURE';
export const CONNECTION_REQUIRE_SSH_PASSPHRASE = 'CONNECTION_REQUIRE_SSH_PASSPHRASE';
export const TEST_CONNECTION_REQUEST = 'TEST_CONNECTION_REQUEST';
export const TEST_CONNECTION_SUCCESS = 'TEST_CONNECTION_SUCCESS';
export const TEST_CONNECTION_FAILURE = 'TEST_CONNECTION_FAILURE';

let serverSession;
export function getCurrentDBConn ({ queries } = {}) {
  if (!serverSession) {
    throw new Error('There is no server available');
  }

  const currentQuery = queries.queriesById[queries.currentQueryId];
  if (!currentQuery) {
    return null;
  }

  return getDBConnByName(currentQuery.database);
}

export function getDBConnByName(database) {
  if (!serverSession) {
    throw new Error('There is no server available');
  }

  const dbConn = serverSession.db(database);
  if (!dbConn) {
    throw new Error('This database is not available');
  }

  return dbConn;
}

export function connect (id, databaseName, reconnecting = false, sshPassphrase) {
  return async (dispatch, getState) => {
    let server;
    let dbConn;
    let database;
    let defaultDatabase;

    try {
      const { config } = getState();
      const cryptoSecret = config.data.crypto.secret;

      const servers = await sqlectron.servers.getAll();
      server = servers.find(srv => srv.id === id);
      if (!server) {
        throw new Error('Server configuration not found');
      }

      server = sqlectron.servers.decryptSecrects(server, cryptoSecret);

      // Terrible workaround to avoid a state issue of data loading from the main process.
      // For some reason chaging a value here in client from a data coming from the main process
      // don't have any effect. We need to clone this data and use the new state.
      server = JSON.parse(JSON.stringify(server));

      defaultDatabase = sqlectron.db.CLIENTS.find(c => c.key === server.client).defaultDatabase;
      database = databaseName || server.database || defaultDatabase;

      dispatch({
        type: CONNECTION_REQUEST,
        server,
        database,
        reconnecting,
        isServerConnection: !databaseName,
      });

      if (!serverSession) {
        if (server.ssh) {
          if (server.ssh.privateKeyWithPassphrase && typeof sshPassphrase === 'undefined') {
            dispatch({ type: CONNECTION_REQUIRE_SSH_PASSPHRASE });
            return;
          }

          if (server.ssh.privateKeyWithPassphrase) {
            server.ssh.passphrase = sshPassphrase;
          }
        }
        serverSession = sqlectron.db.createServer(server);
      }

      dbConn = serverSession.db(database);
      if (dbConn) {
        dispatch({ type: CONNECTION_SUCCESS, server, database, config, reconnecting });
        return;
      }

      dbConn = serverSession.createConnection(database);
      await dbConn.connect();

      dispatch({ type: CONNECTION_SUCCESS, server, database, config, reconnecting });
    } catch (error) {
      dispatch({ type: CONNECTION_FAILURE, server, database, error });
      if (dbConn) {
        dbConn.disconnect();
      }
      const currentConn = getState().connections;
      if (!currentConn.databases.length) {
        this.disconnect();
      }
    }
  };
}

export function disconnect () {
  if (serverSession) {
    serverSession.end();
  }

  serverSession = null;

  return { type: CLOSE_CONNECTION };
}

export function reconnect (id, database) {
  serverSession.end();
  serverSession = null;
  return connect(id, database, true);
}

export function test (server) {
  return async (dispatch) => {
    dispatch({ type: TEST_CONNECTION_REQUEST, server });
    let dbClient;
    try {
      const testServerSession = sqlectron.db.createServer(server);
      dbClient = testServerSession.createConnection(server.database);

      await dbClient.connect(server, server.database);
      dispatch({ type: TEST_CONNECTION_SUCCESS, server });
    } catch (error) {
      dispatch({ type: TEST_CONNECTION_FAILURE, server, error });
    } finally {
      if (dbClient) {
        dbClient.disconnect();
      }
    }
  };
}
<commit_msg>Typo fix connections.js<commit_after>import { sqlectron } from '../../browser/remote';

export const CLOSE_CONNECTION = 'CLOSE_CONNECTION';
export const CONNECTION_REQUEST = 'CONNECTION_REQUEST';
export const CONNECTION_SUCCESS = 'CONNECTION_SUCCESS';
export const CONNECTION_FAILURE = 'CONNECTION_FAILURE';
export const CONNECTION_REQUIRE_SSH_PASSPHRASE = 'CONNECTION_REQUIRE_SSH_PASSPHRASE';
export const TEST_CONNECTION_REQUEST = 'TEST_CONNECTION_REQUEST';
export const TEST_CONNECTION_SUCCESS = 'TEST_CONNECTION_SUCCESS';
export const TEST_CONNECTION_FAILURE = 'TEST_CONNECTION_FAILURE';

let serverSession;
export function getCurrentDBConn ({ queries } = {}) {
  if (!serverSession) {
    throw new Error('There is no server available');
  }

  const currentQuery = queries.queriesById[queries.currentQueryId];
  if (!currentQuery) {
    return null;
  }

  return getDBConnByName(currentQuery.database);
}

export function getDBConnByName(database) {
  if (!serverSession) {
    throw new Error('There is no server available');
  }

  const dbConn = serverSession.db(database);
  if (!dbConn) {
    throw new Error('This database is not available');
  }

  return dbConn;
}

export function connect (id, databaseName, reconnecting = false, sshPassphrase) {
  return async (dispatch, getState) => {
    let server;
    let dbConn;
    let database;
    let defaultDatabase;

    try {
      const { config } = getState();
      const cryptoSecret = config.data.crypto.secret;

      const servers = await sqlectron.servers.getAll();
      server = servers.find(srv => srv.id === id);
      if (!server) {
        throw new Error('Server configuration not found');
      }

      server = sqlectron.servers.decryptSecrects(server, cryptoSecret);

      // Terrible workaround to avoid a state issue of data loading from the main process.
      // For some reason changing a value here in client from a data coming from the main process
      // doesn't have any effect. We need to clone this data and use the new state.
      server = JSON.parse(JSON.stringify(server));

      defaultDatabase = sqlectron.db.CLIENTS.find(c => c.key === server.client).defaultDatabase;
      database = databaseName || server.database || defaultDatabase;

      dispatch({
        type: CONNECTION_REQUEST,
        server,
        database,
        reconnecting,
        isServerConnection: !databaseName,
      });

      if (!serverSession) {
        if (server.ssh) {
          if (server.ssh.privateKeyWithPassphrase && typeof sshPassphrase === 'undefined') {
            dispatch({ type: CONNECTION_REQUIRE_SSH_PASSPHRASE });
            return;
          }

          if (server.ssh.privateKeyWithPassphrase) {
            server.ssh.passphrase = sshPassphrase;
          }
        }
        serverSession = sqlectron.db.createServer(server);
      }

      dbConn = serverSession.db(database);
      if (dbConn) {
        dispatch({ type: CONNECTION_SUCCESS, server, database, config, reconnecting });
        return;
      }

      dbConn = serverSession.createConnection(database);
      await dbConn.connect();

      dispatch({ type: CONNECTION_SUCCESS, server, database, config, reconnecting });
    } catch (error) {
      dispatch({ type: CONNECTION_FAILURE, server, database, error });
      if (dbConn) {
        dbConn.disconnect();
      }
      const currentConn = getState().connections;
      if (!currentConn.databases.length) {
        this.disconnect();
      }
    }
  };
}

export function disconnect () {
  if (serverSession) {
    serverSession.end();
  }

  serverSession = null;

  return { type: CLOSE_CONNECTION };
}

export function reconnect (id, database) {
  serverSession.end();
  serverSession = null;
  return connect(id, database, true);
}

export function test (server) {
  return async (dispatch) => {
    dispatch({ type: TEST_CONNECTION_REQUEST, server });
    let dbClient;
    try {
      const testServerSession = sqlectron.db.createServer(server);
      dbClient = testServerSession.createConnection(server.database);

      await dbClient.connect(server, server.database);
      dispatch({ type: TEST_CONNECTION_SUCCESS, server });
    } catch (error) {
      dispatch({ type: TEST_CONNECTION_FAILURE, server, error });
    } finally {
      if (dbClient) {
        dbClient.disconnect();
      }
    }
  };
}
<|endoftext|>


<commit_before>import * as SETTINGS from 'constants/settings';
import * as PAGES from 'constants/pages';
import * as ICONS from 'constants/icons';
import { createSelector } from 'reselect';
import { selectCurrentPage, selectHistoryStack } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';

export const selectState = state => state.app || {};

export const selectPlatform = createSelector(selectState, state => state.platform);

export const selectUpdateUrl = createSelector(selectPlatform, platform => {
  switch (platform) {
    case 'darwin':
      return 'https://lbry.io/get/lbry.dmg';
    case 'linux':
      return 'https://lbry.io/get/lbry.deb';
    case 'win32':
      return 'https://lbry.io/get/lbry.exe';
    default:
      throw Error('Unknown platform');
  }
});

export const selectHasClickedComment = createSelector(
  selectState,
  state => state.hasClickedComment
);

export const selectRemoteVersion = createSelector(selectState, state => state.remoteVersion);

export const selectIsUpgradeAvailable = createSelector(
  selectState,
  state => state.isUpgradeAvailable
);

export const selectUpgradeFilename = createSelector(
  selectPlatform,
  selectRemoteVersion,
  (platform, version) => {
    switch (platform) {
      case 'darwin':
        return `LBRY_${version}.dmg`;
      case 'linux':
        return `LBRY_${version}.deb`;
      case 'win32':
        return `LBRY_${version}.exe`;
      default:
        throw Error('Unknown platform');
    }
  }
);

export const selectDownloadProgress = createSelector(selectState, state => state.downloadProgress);

export const selectDownloadComplete = createSelector(
  selectState,
  state => state.upgradeDownloadCompleted
);

export const selectIsUpgradeSkipped = createSelector(selectState, state => state.isUpgradeSkipped);

export const selectUpgradeDownloadPath = createSelector(selectState, state => state.downloadPath);

export const selectUpgradeDownloadItem = createSelector(selectState, state => state.downloadItem);

export const selectAutoUpdateDownloaded = createSelector(
  selectState,
  state => state.autoUpdateDownloaded
);

export const selectAutoUpdateDeclined = createSelector(
  selectState,
  state => state.autoUpdateDeclined
);

export const selectDaemonVersionMatched = createSelector(
  selectState,
  state => state.daemonVersionMatched
);

export const selectSnackBar = createSelector(selectState, state => state.snackBar || {});

export const selectSnackBarSnacks = createSelector(
  selectSnackBar,
  snackBar => snackBar.snacks || []
);

export const selectBadgeNumber = createSelector(selectState, state => state.badgeNumber);

export const selectCurrentLanguage = createSelector(
  selectState,
  () => app.i18n.getLocale() || 'en'
);

export const selectVolume = createSelector(selectState, state => state.volume);

export const selectUpgradeTimer = createSelector(selectState, state => state.checkUpgradeTimer);

export const selectNavLinks = createSelector(
  selectCurrentPage,
  selectHistoryStack,
  makeSelectClientSetting(SETTINGS.FIRST_RUN_COMPLETED),
  makeSelectClientSetting(SETTINGS.INVITE_ACKNOWLEDGED),
  (currentPage, historyStack, firstRunCompleted, inviteAcknowledged) => {
    // Determine if any links should show a tooltip for a guided tour
    // It will only show one at a time, in the order they are set.
    const guidedTourItem = [
      {
        page: PAGES.INVITE,
        hasBeenCompleted: inviteAcknowledged,
        guide: 'Check this out!',
      },
      // Add more items below for tooltip guides that will happen after a user has completed the invite guide
    ].filter(({ hasBeenCompleted }) => !hasBeenCompleted)[0];

    const isWalletPage = page =>
      page === PAGES.WALLET ||
      page === PAGES.SEND ||
      page === PAGES.GET_CREDITS ||
      page === PAGES.REWARDS ||
      page === PAGES.HISTORY ||
      page === PAGES.BACKUP;

    const isCurrentlyWalletPage = isWalletPage(currentPage);
    const previousStack = historyStack.slice().reverse();

    const getPreviousSubLinkPath = checkIfValidPage => {
      for (let i = 0; i < previousStack.length; i += 1) {
        const currentStackItem = previousStack[i];

        // Trim off the "/" from the path
        const pageInStack = currentStackItem.path.slice(1);
        if (checkIfValidPage(pageInStack)) {
          return currentStackItem.path;
        }
      }

      return undefined;
    };

    // Gets the last active sublink in a section
    const getActiveSublink = category => {
      if (category === PAGES.WALLET) {
        const previousPath = getPreviousSubLinkPath(isWalletPage);
        return previousPath || `/${PAGES.WALLET}`;
      }

      return undefined;
    };

    // Is this path the first unacknowledged item in the guided tour list
    const getGuideIfNecessary = page => {
      if (!firstRunCompleted) {
        return null;
      }
      return guidedTourItem && guidedTourItem.page === page ? guidedTourItem.guide : null;
    };

    const buildLink = (label, page) => ({
      label,
      path: `/${page}`,
      active: currentPage === page,
      guide: getGuideIfNecessary(page),
    });

    const walletSubLinks = [
      {
        ...buildLink('Overview', PAGES.WALLET),
      },
      {
        ...buildLink('Send & Receive', PAGES.SEND),
      },
      {
        ...buildLink('Transactions', PAGES.HISTORY),
      },
      {
        ...buildLink('Get Credits', PAGES.GET_CREDITS),
      },
      {
        ...buildLink('Rewards', PAGES.REWARDS),
      },
      {
        ...buildLink('Backup', PAGES.BACKUP),
      },
    ];

    const navLinks = {
      primary: [
        {
          ...buildLink('Explore', PAGES.DISCOVER),
          icon: ICONS.HOME,
        },
        {
          ...buildLink('Subscriptions', PAGES.SUBSCRIPTIONS),
          icon: ICONS.SUBSCRIPTION,
        },
      ],
      secondary: [
        {
          label: 'Wallet',
          icon: ICONS.WALLET,
          subLinks: walletSubLinks,
          path: isCurrentlyWalletPage ? `/${PAGES.WALLET}` : getActiveSublink(PAGES.WALLET),
          active: isWalletPage(currentPage),
        },
        {
          ...buildLink('Invite', PAGES.INVITE),
          icon: ICONS.INVITE,
        },
        {
          ...buildLink('Downloads', PAGES.DOWNLOADED),
          icon: ICONS.LOCAL,
        },
        {
          ...buildLink('Publishes', PAGES.PUBLISHED),
          icon: ICONS.PUBLISHED,
        },
        {
          ...buildLink('History', PAGES.USER_HISTORY),
          icon: ICONS.HISTORY,
        },
        {
          ...buildLink('Settings', PAGES.SETTINGS),
          icon: ICONS.SETTINGS,
        },
        {
          ...buildLink('Help', PAGES.HELP),
          icon: ICONS.HELP,
        },
      ],
    };

    return navLinks;
  }
);

export const selectModal = createSelector(selectState, state => {
  if (!state.modal) {
    return null;
  }

  return {
    id: state.modal,
    modalProps: state.modalProps,
  };
});

export const selectEnhancedLayout = createSelector(selectState, state => state.enhancedLayout);
<commit_msg>update: add rewards to sidebar links<commit_after>import * as SETTINGS from 'constants/settings';
import * as PAGES from 'constants/pages';
import * as ICONS from 'constants/icons';
import { createSelector } from 'reselect';
import { selectCurrentPage, selectHistoryStack } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';

export const selectState = state => state.app || {};

export const selectPlatform = createSelector(selectState, state => state.platform);

export const selectUpdateUrl = createSelector(selectPlatform, platform => {
  switch (platform) {
    case 'darwin':
      return 'https://lbry.io/get/lbry.dmg';
    case 'linux':
      return 'https://lbry.io/get/lbry.deb';
    case 'win32':
      return 'https://lbry.io/get/lbry.exe';
    default:
      throw Error('Unknown platform');
  }
});

export const selectHasClickedComment = createSelector(
  selectState,
  state => state.hasClickedComment
);

export const selectRemoteVersion = createSelector(selectState, state => state.remoteVersion);

export const selectIsUpgradeAvailable = createSelector(
  selectState,
  state => state.isUpgradeAvailable
);

export const selectUpgradeFilename = createSelector(
  selectPlatform,
  selectRemoteVersion,
  (platform, version) => {
    switch (platform) {
      case 'darwin':
        return `LBRY_${version}.dmg`;
      case 'linux':
        return `LBRY_${version}.deb`;
      case 'win32':
        return `LBRY_${version}.exe`;
      default:
        throw Error('Unknown platform');
    }
  }
);

export const selectDownloadProgress = createSelector(selectState, state => state.downloadProgress);

export const selectDownloadComplete = createSelector(
  selectState,
  state => state.upgradeDownloadCompleted
);

export const selectIsUpgradeSkipped = createSelector(selectState, state => state.isUpgradeSkipped);

export const selectUpgradeDownloadPath = createSelector(selectState, state => state.downloadPath);

export const selectUpgradeDownloadItem = createSelector(selectState, state => state.downloadItem);

export const selectAutoUpdateDownloaded = createSelector(
  selectState,
  state => state.autoUpdateDownloaded
);

export const selectAutoUpdateDeclined = createSelector(
  selectState,
  state => state.autoUpdateDeclined
);

export const selectDaemonVersionMatched = createSelector(
  selectState,
  state => state.daemonVersionMatched
);

export const selectSnackBar = createSelector(selectState, state => state.snackBar || {});

export const selectSnackBarSnacks = createSelector(
  selectSnackBar,
  snackBar => snackBar.snacks || []
);

export const selectBadgeNumber = createSelector(selectState, state => state.badgeNumber);

export const selectCurrentLanguage = createSelector(
  selectState,
  () => app.i18n.getLocale() || 'en'
);

export const selectVolume = createSelector(selectState, state => state.volume);

export const selectUpgradeTimer = createSelector(selectState, state => state.checkUpgradeTimer);

export const selectNavLinks = createSelector(
  selectCurrentPage,
  selectHistoryStack,
  makeSelectClientSetting(SETTINGS.FIRST_RUN_COMPLETED),
  makeSelectClientSetting(SETTINGS.INVITE_ACKNOWLEDGED),
  (currentPage, historyStack, firstRunCompleted, inviteAcknowledged) => {
    // Determine if any links should show a tooltip for a guided tour
    // It will only show one at a time, in the order they are set.
    const guidedTourItem = [
      {
        page: PAGES.INVITE,
        hasBeenCompleted: inviteAcknowledged,
        guide: 'Check this out!',
      },
      // Add more items below for tooltip guides that will happen after a user has completed the invite guide
    ].filter(({ hasBeenCompleted }) => !hasBeenCompleted)[0];

    const isWalletPage = page =>
      page === PAGES.WALLET ||
      page === PAGES.SEND ||
      page === PAGES.GET_CREDITS ||
      page === PAGES.HISTORY ||
      page === PAGES.BACKUP;

    const isCurrentlyWalletPage = isWalletPage(currentPage);
    const previousStack = historyStack.slice().reverse();

    const getPreviousSubLinkPath = checkIfValidPage => {
      for (let i = 0; i < previousStack.length; i += 1) {
        const currentStackItem = previousStack[i];

        // Trim off the "/" from the path
        const pageInStack = currentStackItem.path.slice(1);
        if (checkIfValidPage(pageInStack)) {
          return currentStackItem.path;
        }
      }

      return undefined;
    };

    // Gets the last active sublink in a section
    const getActiveSublink = category => {
      if (category === PAGES.WALLET) {
        const previousPath = getPreviousSubLinkPath(isWalletPage);
        return previousPath || `/${PAGES.WALLET}`;
      }

      return undefined;
    };

    // Is this path the first unacknowledged item in the guided tour list
    const getGuideIfNecessary = page => {
      if (!firstRunCompleted) {
        return null;
      }
      return guidedTourItem && guidedTourItem.page === page ? guidedTourItem.guide : null;
    };

    const buildLink = (label, page) => ({
      label,
      path: `/${page}`,
      active: currentPage === page,
      guide: getGuideIfNecessary(page),
    });

    const walletSubLinks = [
      {
        ...buildLink('Overview', PAGES.WALLET),
      },
      {
        ...buildLink('Send & Receive', PAGES.SEND),
      },
      {
        ...buildLink('Transactions', PAGES.HISTORY),
      },
      {
        ...buildLink('Get Credits', PAGES.GET_CREDITS),
      },
      {
        ...buildLink('Backup', PAGES.BACKUP),
      },
    ];

    const navLinks = {
      primary: [
        {
          ...buildLink('Explore', PAGES.DISCOVER),
          icon: ICONS.HOME,
        },
        {
          ...buildLink('Subscriptions', PAGES.SUBSCRIPTIONS),
          icon: ICONS.SUBSCRIPTION,
        },
      ],
      secondary: [
        {
          label: 'Wallet',
          icon: ICONS.WALLET,
          subLinks: walletSubLinks,
          path: isCurrentlyWalletPage ? `/${PAGES.WALLET}` : getActiveSublink(PAGES.WALLET),
          active: isWalletPage(currentPage),
        },
        {
          ...buildLink('Invite', PAGES.INVITE),
          icon: ICONS.INVITE,
        },
        {
          ...buildLink('Rewards', PAGES.REWARDS),
          // This probably shouldn't use the "FEATURED" icon, but not sure what else to use
          icon: ICONS.FEATURED,
        },
        {
          ...buildLink('Downloads', PAGES.DOWNLOADED),
          icon: ICONS.LOCAL,
        },
        {
          ...buildLink('Publishes', PAGES.PUBLISHED),
          icon: ICONS.PUBLISHED,
        },
        {
          ...buildLink('History', PAGES.USER_HISTORY),
          icon: ICONS.HISTORY,
        },
        {
          ...buildLink('Settings', PAGES.SETTINGS),
          icon: ICONS.SETTINGS,
        },
        {
          ...buildLink('Help', PAGES.HELP),
          icon: ICONS.HELP,
        },
      ],
    };

    return navLinks;
  }
);

export const selectModal = createSelector(selectState, state => {
  if (!state.modal) {
    return null;
  }

  return {
    id: state.modal,
    modalProps: state.modalProps,
  };
});

export const selectEnhancedLayout = createSelector(selectState, state => state.enhancedLayout);
<|endoftext|>


<commit_before>import _ from 'lodash'
import {ReduceStore} from 'flux/utils'

import dispatcher from '../dispatcher'

class WindowStore extends ReduceStore<Object>
{
    getInitialState(): Object
    {
        return {
            panes: {}
        }
    }

    reduce(state: Object, action: Object)
    {
        switch (action.type) {
        case 'pane-resize': {
            return Object.assign({}, state, {
                panes: Object.assign(state.panes, {
                    [action.payload.paneId]: action.payloadSize,
                })
            })
        }
        }
        return state
    }
}

export default new WindowStore(dispatcher)
<commit_msg>Remove unnecessory `window-store`<commit_after><|endoftext|>


<commit_before>define(["./gameobject", "./bullet", "./../rendering/animation", "./../rendering/animationsheet"], function moduleTank(GameObject, Bullet, Animation, AnimationSheet) {
    // possible constraints:
    // - your direction should be normalized
    // - your speed is limited to maxSpeed (cannot adjust speed?)
    // - your velocity is direction.mulFloat(dt*speed)
    var Tank = GameObject.subclass("Tank", {
        initialize: function($super, world, pos, vel, dir, config) {
            $super(world, "tank", pos, new Vector2(2, 2), 1, vel, config.speed);

            this.turretDirection = dir;
            this.turretAnimation = new Animation(new AnimationSheet("turret.png", 18, 18), 0.4, [0,1,2,3]);

            this.bullets = config.bullets;
            this.bulletRicochets = config.bulletRicochets;
            this.bulletSpeed = config.bulletSpeed;

            this.initConstraints();
        },
        initConstraints: function() {
            var that = this,
                map = this.world.map;

            // constraint:
            // - do not be on a wall tile
            var doNotStayInWalls = bbb.assert({
                // collision solving is already provided by the babelsberg.assert
                // method and its ability the revert to a valid state
                onError: function(error) {
                    if(!error instanceof ContinuousAssertError) {
                        throw error;
                    }
                },
                ctx: {
                    that: that,
                    map: map
                }
            }, function() {
                // collision detection against the current tile
                return that.getTile(that.position).canWalkThrough();
            });

            // constraint idea:
            // avoid tanks to go out of bounds due to low framerate
            // TODO: ! static rectangle
            var mapMaxX = map.tileSize.x * map.tiles[0].length;
            var mapMaxY = map.tileSize.y * map.tiles.length;
            var outOfBounds = bbb.assert({
                onError: function(error) {
                    if(!(error instanceof ContinuousAssertError)) {
                        throw error;
                    }
                },
                ctx: {
                    that: that,
                    mapMaxX: mapMaxX,
                    mapMaxY: mapMaxY
                }
            }, function() {
                return that.position.x >= 0 && that.position.x < mapMaxX &&
                    that.position.y >= 0 && that.position.y < mapMaxY;
            });

            this.constraints.push(doNotStayInWalls, outOfBounds);

            // assumption: tanks are inserted first into the world
            this.world.getGameObjects().each(function(other) {
                if(!(other.name == "tank" || other.name == "bullet")) { return; }

                // constraint:
                // - solve collisions
                that.onCollisionWith(other, function(that, tank) {
                    var desiredDistance = that.radius + tank.radius,
                        distVector = tank.position.sub(that.position),
                        realDistance = distVector.length(),
                        moveVector = distVector.mulFloat((desiredDistance - realDistance) / 1.9);
                    tank.position.addSelf(moveVector);
                    that.position.subSelf(moveVector);
                    //console.log(realDistance, "post", that.position.distance(tank.position, that.radius + tank.radius));
                });
            });
        },

        update: function($super, dt) {
            $super(dt);
            this.turretAnimation.update(dt);

            this.controls && this.controls.update(dt);
        },

        draw: function($super, renderer) {
            $super(renderer);
            this.turretAnimation.draw(
                renderer, this.getWorldAABB(), new Vector2(1,0).getDirectedAngle(this.turretDirection)
            );
        },

        fireBullet: function(world, dt) {
            if(this.bullets == 0) { return; }

            var direction = this.turretDirection.normalizedCopy();
            var position = this.position.add(direction.mulFloat(this.radius));

            if(!this.getTile(position).canFlyThrough()) { return; }

            this.bullets--;
            var bullet = new Bullet(world,
                position,
                direction,
                this,
                this.bulletRicochets,
                this.bulletSpeed
            );
            world.getGameObjects().each(function(other) {
                if(other === this) { return; }
                bullet.onCollisionWith(other, Bullet.detonate);
            }, this);
            world.spawn(bullet);
        },
        destroy: function($super) {
            $super();
        }
    });

    Tank.subclass("PlayerTank", {
        initialize: function($super, world, pos, vel, dir, config) {
            $super(world, pos, vel, dir, config);

            this.animation = new Animation(new AnimationSheet("tank.png", 18, 18), 0.4, [0,1,2,3]);
        }
    });

    Tank.subclass("CPUTank", {
        initialize: function($super, world, pos, vel, dir, config) {
            $super(world, pos, vel, dir, config);

            this.animation = new Animation(new AnimationSheet("tank.png", 18, 18), 0.4, [4,5,6,7]);

            // constraint:
            // - keep velocity direction and turret direction in sync
            /*
            var that = this;
            var turretConstraint = bbb.always({
                solver: new DBPlanner(),
                ctx: {
                    that: that
                },
                methods: function() {
                    that.turretDirection.formula([that.velocity, that.velocity.x, that.velocity.y], function(velocity, velocityX, velocityY) {
                        return velocity;
                    });
                }
            }, function() {
                return that.turretDirection.equals(that.velocity);
            });

            this.constraints.push(turretConstraint);
            */
        }
    });

    Tank.SPEED_IMMOBILE = 0;
    Tank.SPEED_NORMAL = 16 / 6;
    Tank.SPEED_SLOW = 0.5 * Tank.SPEED_NORMAL;
    Tank.SPEED_FAST = 1.5 * Tank.SPEED_NORMAL;

    return Tank;
});
<commit_msg>added guard to Bullet spawner<commit_after>define(["./gameobject", "./bullet", "./../rendering/animation", "./../rendering/animationsheet"], function moduleTank(GameObject, Bullet, Animation, AnimationSheet) {
    // possible constraints:
    // - your direction should be normalized
    // - your speed is limited to maxSpeed (cannot adjust speed?)
    // - your velocity is direction.mulFloat(dt*speed)
    var Tank = GameObject.subclass("Tank", {
        initialize: function($super, world, pos, vel, dir, config) {
            $super(world, "tank", pos, new Vector2(2, 2), 1, vel, config.speed);

            this.turretDirection = dir;
            this.turretAnimation = new Animation(new AnimationSheet("turret.png", 18, 18), 0.4, [0,1,2,3]);

            this.bullets = config.bullets;
            this.bulletRicochets = config.bulletRicochets;
            this.bulletSpeed = config.bulletSpeed;

            this.initConstraints();
        },
        initConstraints: function() {
            var that = this,
                map = this.world.map;

            // constraint:
            // - do not be on a wall tile
            var doNotStayInWalls = bbb.assert({
                // collision solving is already provided by the babelsberg.assert
                // method and its ability the revert to a valid state
                onError: function(error) {
                    if(!error instanceof ContinuousAssertError) {
                        throw error;
                    }
                },
                ctx: {
                    that: that,
                    map: map
                }
            }, function() {
                // collision detection against the current tile
                return that.getTile(that.position).canWalkThrough();
            });

            // constraint idea:
            // avoid tanks to go out of bounds due to low framerate
            // TODO: ! static rectangle
            var mapMaxX = map.tileSize.x * map.tiles[0].length;
            var mapMaxY = map.tileSize.y * map.tiles.length;
            var outOfBounds = bbb.assert({
                onError: function(error) {
                    if(!(error instanceof ContinuousAssertError)) {
                        throw error;
                    }
                },
                ctx: {
                    that: that,
                    mapMaxX: mapMaxX,
                    mapMaxY: mapMaxY
                }
            }, function() {
                return that.position.x >= 0 && that.position.x < mapMaxX &&
                    that.position.y >= 0 && that.position.y < mapMaxY;
            });

            this.constraints.push(doNotStayInWalls, outOfBounds);

            // assumption: tanks are inserted first into the world
            this.world.getGameObjects().each(function(other) {
                // constraint:
                // - solve collisions
                that.onCollisionWith(other, function(that, tank) {
                    var desiredDistance = that.radius + tank.radius,
                        distVector = tank.position.sub(that.position),
                        realDistance = distVector.length(),
                        moveVector = distVector.mulFloat((desiredDistance - realDistance) / 1.9);
                    tank.position.addSelf(moveVector);
                    that.position.subSelf(moveVector);
                    //console.log(realDistance, "post", that.position.distance(tank.position, that.radius + tank.radius));
                });
            });
        },

        update: function($super, dt) {
            $super(dt);
            this.turretAnimation.update(dt);

            this.controls && this.controls.update(dt);
        },

        draw: function($super, renderer) {
            $super(renderer);
            this.turretAnimation.draw(
                renderer, this.getWorldAABB(), new Vector2(1,0).getDirectedAngle(this.turretDirection)
            );
        },

        fireBullet: function(world, dt) {
            if(this.bullets == 0) { return; }

            var direction = this.turretDirection.normalizedCopy();
            var position = this.position.add(direction.mulFloat(this.radius));

            if(!this.getTile(position).canFlyThrough()) { return; }

            this.bullets--;
            var bullet = new Bullet(world,
                position,
                direction,
                this,
                this.bulletRicochets,
                this.bulletSpeed
            );
            world.getGameObjects().each(function(other) {
                if(other === this) { return; }
                if(!(other.name == "tank" || other.name == "bullet")) { return; }

                bullet.onCollisionWith(other, Bullet.detonate);
            }, this);
            world.spawn(bullet);
        },
        destroy: function($super) {
            $super();
        }
    });

    Tank.subclass("PlayerTank", {
        initialize: function($super, world, pos, vel, dir, config) {
            $super(world, pos, vel, dir, config);

            this.animation = new Animation(new AnimationSheet("tank.png", 18, 18), 0.4, [0,1,2,3]);
        }
    });

    Tank.subclass("CPUTank", {
        initialize: function($super, world, pos, vel, dir, config) {
            $super(world, pos, vel, dir, config);

            this.animation = new Animation(new AnimationSheet("tank.png", 18, 18), 0.4, [4,5,6,7]);

            // constraint:
            // - keep velocity direction and turret direction in sync
            /*
            var that = this;
            var turretConstraint = bbb.always({
                solver: new DBPlanner(),
                ctx: {
                    that: that
                },
                methods: function() {
                    that.turretDirection.formula([that.velocity, that.velocity.x, that.velocity.y], function(velocity, velocityX, velocityY) {
                        return velocity;
                    });
                }
            }, function() {
                return that.turretDirection.equals(that.velocity);
            });

            this.constraints.push(turretConstraint);
            */
        }
    });

    Tank.SPEED_IMMOBILE = 0;
    Tank.SPEED_NORMAL = 16 / 6;
    Tank.SPEED_SLOW = 0.5 * Tank.SPEED_NORMAL;
    Tank.SPEED_FAST = 1.5 * Tank.SPEED_NORMAL;

    return Tank;
});
<|endoftext|>