mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	* 🎉 initial testing with jest * 👌 update test script names & remove package-lock.json * 👌 add 'yarn test' step to circle-ci build workflow
		
			
				
	
	
		
			424 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			424 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { fail } from 'jest';
 | 
						|
 | 
						|
function runAssertions(ctx, func) {
 | 
						|
  try {
 | 
						|
    const message = func() || '';
 | 
						|
    return {
 | 
						|
      message: typeof message === 'function' ? message : () => message,
 | 
						|
      pass: true,
 | 
						|
    };
 | 
						|
  } catch (e) {
 | 
						|
    return {
 | 
						|
      pass: false,
 | 
						|
      message: () => e.message || e,
 | 
						|
    };
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function assert(expr, failMessage) {
 | 
						|
  if (!expr) {
 | 
						|
    const finalMessage =
 | 
						|
      typeof failMessage === 'function' ? failMessage() : failMessage;
 | 
						|
    throw new Error(finalMessage);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function prettyPrint(obj) {
 | 
						|
  return JSON.stringify(obj, null, 2);
 | 
						|
}
 | 
						|
 | 
						|
async function sleep(ms) {
 | 
						|
  return new Promise(resolve => {
 | 
						|
    window.setTimeout(() => {
 | 
						|
      resolve();
 | 
						|
    }, ms);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
function assertHasKeys(obj, keys, msg) {
 | 
						|
  assert(obj, 'actual is not set');
 | 
						|
  assert(typeof obj === 'object', 'actual is not an object');
 | 
						|
  assert(
 | 
						|
    (() => {
 | 
						|
      const objectKeys = Object.keys(obj);
 | 
						|
      return keys.reduce(
 | 
						|
        (acc, cur) => acc && objectKeys.indexOf(cur) > -1,
 | 
						|
        true
 | 
						|
      );
 | 
						|
    })(),
 | 
						|
    msg
 | 
						|
  );
 | 
						|
  return msg;
 | 
						|
}
 | 
						|
 | 
						|
function notFor(self) {
 | 
						|
  return self.isNot ? ' not ' : ' ';
 | 
						|
}
 | 
						|
 | 
						|
function testIsInstance(actual, ctor) {
 | 
						|
  assert(actual !== undefined, 'actual is undefined');
 | 
						|
  assert(actual !== null, 'actual is null');
 | 
						|
  assert(
 | 
						|
    actual instanceof ctor,
 | 
						|
    `Expected instance of ${Object.prototype.toString.call(
 | 
						|
      ctor
 | 
						|
    )} but got ${actual}`
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
async function runAssertionsAsync(ctx, func) {
 | 
						|
  try {
 | 
						|
    await func();
 | 
						|
    return {
 | 
						|
      message: () => '',
 | 
						|
      pass: !ctx.isNot,
 | 
						|
    };
 | 
						|
  } catch (e) {
 | 
						|
    return {
 | 
						|
      pass: false,
 | 
						|
      message: () => e.message || e,
 | 
						|
    };
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
beforeAll(() => {
 | 
						|
  expect.extend({
 | 
						|
    toHaveKeys(actual, ...expected) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        assert(expected, 'keys are not set');
 | 
						|
        const msg = () =>
 | 
						|
          `expected\n${prettyPrint(actual)}\nto have keys\n${prettyPrint(
 | 
						|
            expected
 | 
						|
          )}`;
 | 
						|
        return assertHasKeys(actual, expected, msg);
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toHaveKey(actual, expected) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        assert(expected, 'key is not set');
 | 
						|
        const msg = () =>
 | 
						|
          `expected ${prettyPrint(actual)} to have key "${expected}"`;
 | 
						|
        return assertHasKeys(actual, [expected], msg);
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeEquivalentTo(actual, expected) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `expected collection equivalent to\n${prettyPrint(
 | 
						|
            expected
 | 
						|
          )}\nbut got\n${prettyPrint(actual)}`;
 | 
						|
        assert(Array.isArray(actual), () => `${actual} is not an array`);
 | 
						|
        assert(Array.isArray(expected), () => `${expected} is not an array`);
 | 
						|
        assert(actual.length === expected.length, msg);
 | 
						|
        assert(
 | 
						|
          actual.reduce((acc, cur) => acc && expected.indexOf(cur) > -1, true),
 | 
						|
          msg
 | 
						|
        );
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBePrototypical(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(this)}prototype, but got ${prettyPrint(actual)}`;
 | 
						|
        assert(actual, msg);
 | 
						|
        assert(actual.prototype, msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeAsyncFunction(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(this)}async function but got ${prettyPrint(
 | 
						|
            actual
 | 
						|
          )}`;
 | 
						|
        assert(
 | 
						|
          Object.prototype.toString.call(actual) === '[object AsyncFunction]' ||
 | 
						|
            Object.prototype.toString.call(actual) === '[object Function]',
 | 
						|
          msg
 | 
						|
        );
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBePromiseLike(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const err = moreInfo => {
 | 
						|
          return `expected something${notFor(
 | 
						|
            this
 | 
						|
          )}promise-like, but got ${actual}${
 | 
						|
            moreInfo ? '\n\t(' : ''
 | 
						|
          }${moreInfo}${moreInfo ? ')' : ''}`;
 | 
						|
        };
 | 
						|
 | 
						|
        assert(actual, err);
 | 
						|
        assert(typeof actual === 'object', err);
 | 
						|
        assert(
 | 
						|
          actual.then && typeof actual.then === 'function',
 | 
						|
          'should have a then function'
 | 
						|
        );
 | 
						|
        return () => err();
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeConstructor(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const err = () => {
 | 
						|
          return `expected ${actual}${notFor(this)}to be a constructor`;
 | 
						|
        };
 | 
						|
 | 
						|
        assert(actual, err);
 | 
						|
        assert(actual.prototype, err);
 | 
						|
        return err;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeA(actual, ctor) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        testIsInstance(actual, ctor);
 | 
						|
        return () =>
 | 
						|
          `expected${notFor(
 | 
						|
            this
 | 
						|
          )}to get instance of ${ctor}, but received ${actual}`;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeAn(actual, ctor) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        testIsInstance(actual, ctor);
 | 
						|
        return () =>
 | 
						|
          `expected${notFor(
 | 
						|
            this
 | 
						|
          )}to get instance of ${ctor}, but received ${actual}`;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeVueComponent(actual, withName) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        assert(
 | 
						|
          typeof actual.render === 'function',
 | 
						|
          `actual does not have a render function -- are you sure it's a Vue component?`
 | 
						|
        );
 | 
						|
        assert(
 | 
						|
          actual.name === withName,
 | 
						|
          `Expected component${notFor(
 | 
						|
            this
 | 
						|
          )}to have name "${withName}", but found "${actual.name}"`
 | 
						|
        );
 | 
						|
        return () =>
 | 
						|
          `Expected${notFor(
 | 
						|
            this
 | 
						|
          )}to receive a Vue component with name ${withName}`;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeNumericInput(htmlElement) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `Expected${notFor(this)}to receive numeric input but got: ${
 | 
						|
            htmlElement.outerHTML
 | 
						|
          }`;
 | 
						|
        assert(htmlElement.type === 'number', msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toHaveCssClass(actual, cssClass) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `Expected ${actual.outerHTML}${notFor(
 | 
						|
            this
 | 
						|
          )}to have css class "${cssClass}"`;
 | 
						|
        const el = actual.$el || actual;
 | 
						|
        assert(el.classList.contains(cssClass), msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toHaveBeenCalledOnce(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        if (this.isNot) {
 | 
						|
          throw new Error(
 | 
						|
            [
 | 
						|
              "Negation of 'toHaveBeenCalledOnce' is ambiguous ",
 | 
						|
              "(do you mean 'not at all' or 'any number except 1'?)",
 | 
						|
            ].join('')
 | 
						|
          );
 | 
						|
        }
 | 
						|
        expect(actual).toHaveBeenCalledTimes(1);
 | 
						|
        return () => `Expected${notFor(this)}to have been called once`;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toHaveBeenCalledOnceWith(actual, ...args) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        expect(actual).toHaveBeenCalledTimes(1);
 | 
						|
        expect(actual).toHaveBeenCalledWith(...args);
 | 
						|
        return () =>
 | 
						|
          `Expected${notFor(this)}to have been called once with ${args}`;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeHidden(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `Expected '${actual.outerHTML}'${notFor(this)}to be hidden`;
 | 
						|
        assert(actual, 'actual does not exist');
 | 
						|
        assert(actual.style, 'actual may not be an html element?');
 | 
						|
        assert(
 | 
						|
          actual.style.display === 'none' ||
 | 
						|
            actual.style.visibility === 'hidden' ||
 | 
						|
            actual.style.visibility === 'collapse',
 | 
						|
          msg
 | 
						|
        );
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeVisible(htmlElement) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () =>
 | 
						|
          `Expected '${htmlElement.outerHTML}'${notFor(this)}to be hidden`;
 | 
						|
        assert(htmlElement, 'actual does not exist');
 | 
						|
        assert(
 | 
						|
          htmlElement.style.display !== 'none' &&
 | 
						|
            htmlElement.style.visibility !== 'hidden' &&
 | 
						|
            htmlElement.style.visibility !== 'collapse',
 | 
						|
          msg
 | 
						|
        );
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    async toBeCompleted(actual) {
 | 
						|
      return runAssertionsAsync(this, async () => {
 | 
						|
        let completed = false;
 | 
						|
        let state = 'pending';
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(this)}to complete promise (final state: ${state})`;
 | 
						|
        actual
 | 
						|
          .then(() => {
 | 
						|
            state = 'resolved';
 | 
						|
            completed = true;
 | 
						|
          })
 | 
						|
          .catch(() => {
 | 
						|
            state = 'rejected';
 | 
						|
            completed = true;
 | 
						|
          });
 | 
						|
        await sleep(50);
 | 
						|
        if (completed && this.isNot) {
 | 
						|
          fail(msg());
 | 
						|
        } else if (!completed && !this.isNot) {
 | 
						|
          fail(msg());
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
    async toBeResolved(actual, message, timeoutMs) {
 | 
						|
      return runAssertionsAsync(this, async () => {
 | 
						|
        let resolved = null;
 | 
						|
        const timeout = timeoutMs || 50;
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(this)}to resolve promise, but ${
 | 
						|
            resolved === null ? 'it never completed' : 'it rejected'
 | 
						|
          }${message ? `More info: ${message}` : ''}`;
 | 
						|
        actual
 | 
						|
          .then(() => {
 | 
						|
            resolved = true;
 | 
						|
          })
 | 
						|
          .catch(() => {
 | 
						|
            resolved = false;
 | 
						|
          });
 | 
						|
        let slept = 0;
 | 
						|
        while (resolved === null && slept < timeout) {
 | 
						|
          // eslint-disable-next-line no-await-in-loop
 | 
						|
          await sleep(50);
 | 
						|
          slept += 50;
 | 
						|
        }
 | 
						|
        if (resolved && this.isNot) {
 | 
						|
          fail(msg());
 | 
						|
        } else if (!resolved && !this.isNot) {
 | 
						|
          fail(msg());
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
    async toBeRejected(actual, message, timeoutMs) {
 | 
						|
      return runAssertionsAsync(this, async () => {
 | 
						|
        let rejected = null;
 | 
						|
        const timeout = timeoutMs || 50;
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(this)}to reject promise, but ${
 | 
						|
            rejected === null ? 'it never completed' : 'it resolved'
 | 
						|
          }${message ? `More info: ${message}` : ''}`;
 | 
						|
        actual
 | 
						|
          .then(() => {
 | 
						|
            rejected = false;
 | 
						|
          })
 | 
						|
          .catch(() => {
 | 
						|
            rejected = true;
 | 
						|
          });
 | 
						|
        let slept = 0;
 | 
						|
        while (rejected === null && slept < timeout) {
 | 
						|
          // eslint-disable-next-line no-await-in-loop
 | 
						|
          await sleep(50);
 | 
						|
          slept += 50;
 | 
						|
        }
 | 
						|
        if (rejected && this.isNot) {
 | 
						|
          fail(msg());
 | 
						|
        } else if (!rejected && !this.isNot) {
 | 
						|
          fail(msg());
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toExist(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () => `Expected ${actual}${notFor(this)}to exist`;
 | 
						|
        assert(actual !== null && actual !== undefined, msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toBeDisabled(actual) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const msg = () => `Expected ${actual}${notFor(this)}to be disabled`;
 | 
						|
        assert(actual.disabled, msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toHaveReceivedNoCallsAtAll(mockedObject) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const called = Object.getOwnPropertyNames(
 | 
						|
          Object.getPrototypeOf(mockedObject)
 | 
						|
        ).reduce((acc, cur) => {
 | 
						|
          const prop = mockedObject[cur];
 | 
						|
          if (typeof prop.mock === 'undefined') {
 | 
						|
            return acc;
 | 
						|
          }
 | 
						|
          if (prop.mock.calls && prop.mock.calls.length) {
 | 
						|
            acc.push(cur);
 | 
						|
          }
 | 
						|
          return acc;
 | 
						|
        }, []);
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(
 | 
						|
            this
 | 
						|
          )}to have received any calls, but got ${called}`;
 | 
						|
        assert(!called.length, msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    toHaveReceivedOnly(mockedObject, ...calls) {
 | 
						|
      return runAssertions(this, () => {
 | 
						|
        const called = Object.getOwnPropertyNames(
 | 
						|
          Object.getPrototypeOf(mockedObject)
 | 
						|
        ).reduce((acc, cur) => {
 | 
						|
          const prop = mockedObject[cur];
 | 
						|
          if (typeof prop.mock === 'undefined') {
 | 
						|
            return acc;
 | 
						|
          }
 | 
						|
          if (
 | 
						|
            prop.mock.calls &&
 | 
						|
            prop.mock.calls.length &&
 | 
						|
            calls.indexOf(cur) === -1
 | 
						|
          ) {
 | 
						|
            acc.push(cur);
 | 
						|
          }
 | 
						|
          return acc;
 | 
						|
        }, []);
 | 
						|
        const msg = () =>
 | 
						|
          `expected${notFor(
 | 
						|
            this
 | 
						|
          )}to have received any calls, but got ${called}`;
 | 
						|
        assert(!called.length, msg);
 | 
						|
        return msg;
 | 
						|
      });
 | 
						|
    },
 | 
						|
  });
 | 
						|
});
 |