test-common.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. let describe;
  2. let test;
  3. let expect;
  4. // Stores the results of each test and suite. Has a terrible
  5. // name to avoid name collision.
  6. let __TestResults__ = {};
  7. // This array is used to communicate with the C++ program. It treats
  8. // each message in this array as a separate message. Has a terrible
  9. // name to avoid name collision.
  10. let __UserOutput__ = [];
  11. // We also rebind console.log here to use the array above
  12. console.log = (...args) => {
  13. __UserOutput__.push(args.join(" "));
  14. };
  15. // Use an IIFE to avoid polluting the global namespace as much as possible
  16. (() => {
  17. // FIXME: This is a very naive deepEquals algorithm
  18. const deepEquals = (a, b) => {
  19. if (Array.isArray(a))
  20. return Array.isArray(b) && deepArrayEquals(a, b);
  21. if (typeof a === "object")
  22. return typeof b === "object" && deepObjectEquals(a, b);
  23. return Object.is(a, b);
  24. }
  25. const deepArrayEquals = (a, b) => {
  26. if (a.length !== b.length)
  27. return false;
  28. for (let i = 0; i < a.length; ++i) {
  29. if (!deepEquals(a[i], b[i]))
  30. return false;
  31. }
  32. return true;
  33. }
  34. const deepObjectEquals = (a, b) => {
  35. if (a === null)
  36. return b === null;
  37. for (let key of Reflect.ownKeys(a)) {
  38. if (!deepEquals(a[key], b[key]))
  39. return false;
  40. }
  41. return true;
  42. }
  43. class ExpectationError extends Error {
  44. constructor(message, fileName, lineNumber) {
  45. super(message, fileName, lineNumber);
  46. this.name = "ExpectationError";
  47. }
  48. }
  49. class Expector {
  50. constructor(target, inverted) {
  51. this.target = target;
  52. this.inverted = !!inverted;
  53. }
  54. get not() {
  55. return new Expector(this.target, !this.inverted);
  56. }
  57. toBe(value) {
  58. this.__doMatcher(() => {
  59. this.__expect(Object.is(this.target, value));
  60. });
  61. }
  62. toHaveLength(length) {
  63. this.__doMatcher(() => {
  64. this.__expect(Object.is(this.target.length, length));
  65. });
  66. }
  67. toHaveProperty(property, value) {
  68. this.__doMatcher(() => {
  69. let object = this.target;
  70. if (typeof property === "string" && property.includes(".")) {
  71. let propertyArray = [];
  72. while (true) {
  73. let index = property.indexOf(".");
  74. if (index === -1) {
  75. propertyArray.push(property);
  76. break;
  77. }
  78. propertyArray.push(property.substring(0, index));
  79. property = property.substring(index, property.length);
  80. }
  81. property = propertyArray;
  82. }
  83. if (Array.isArray(property)) {
  84. for (let key of property) {
  85. if (object === undefined || object === null) {
  86. if (this.inverted)
  87. return;
  88. throw new ExpectationError();
  89. }
  90. object = object[key];
  91. }
  92. } else {
  93. object = object[property];
  94. }
  95. this.__expect(object !== undefined);
  96. if (value !== undefined)
  97. this.__expect(deepEquals(object, value));
  98. });
  99. }
  100. toBeCloseTo(number, numDigits) {
  101. if (numDigits === undefined)
  102. numDigits = 2;
  103. this.__doMatcher(() => {
  104. this.__expect(Math.abs(number - this.target) < (10 ** -numDigits / numDigits));
  105. });
  106. }
  107. toBeDefined() {
  108. this.__doMatcher(() => {
  109. this.__expect(this.target !== undefined);
  110. });
  111. }
  112. toBeFalsey() {
  113. this.__doMatcher(() => {
  114. this.__expect(!this.target);
  115. });
  116. }
  117. toBeGreaterThan(number) {
  118. this.__doMatcher(() => {
  119. this.__expect(this.target > number);
  120. });
  121. }
  122. toBeGreaterThanOrEqual(number) {
  123. this.__doMatcher(() => {
  124. this.__expect(this.target >= number);
  125. });
  126. }
  127. toBeLessThan(number) {
  128. this.__doMatcher(() => {
  129. this.__expect(this.target < number);
  130. });
  131. }
  132. toBeLessThanOrEqual(number) {
  133. this.__doMatcher(() => {
  134. this.__expect(this.target <= number);
  135. });
  136. }
  137. toBeInstanceOf(class_) {
  138. this.__doMatcher(() => {
  139. this.__expect(this.target instanceof class_);
  140. });
  141. }
  142. toBeNull() {
  143. this.__doMatcher(() => {
  144. this.__expect(this.target === null);
  145. });
  146. }
  147. toBeTruthy() {
  148. this.__doMatcher(() => {
  149. this.__expect(!!this.target);
  150. });
  151. }
  152. toBeUndefined() {
  153. this.__doMatcher(() => {
  154. this.__expect(this.target === undefined);
  155. });
  156. }
  157. toBeNaN() {
  158. this.__doMatcher(() => {
  159. this.__expect(isNaN(this.target));
  160. });
  161. }
  162. toContain(item) {
  163. this.__doMatcher(() => {
  164. // FIXME: Iterator check
  165. for (let element of this.target) {
  166. if (item === element)
  167. return;
  168. }
  169. throw new ExpectationError();
  170. });
  171. }
  172. toContainEqual(item) {
  173. this.__doMatcher(() => {
  174. // FIXME: Iterator check
  175. for (let element of this.target) {
  176. if (deepEquals(item, element))
  177. return;
  178. }
  179. throw new ExpectationError();
  180. });
  181. }
  182. toEqual(value) {
  183. this.__doMatcher(() => {
  184. this.__expect(deepEquals(this.target, value));
  185. });
  186. }
  187. toThrow(value) {
  188. this.__expect(typeof this.target === "function");
  189. this.__expect(typeof value === "string" || typeof value === "function" || value === undefined);
  190. this.__doMatcher(() => {
  191. try {
  192. this.target();
  193. this.__expect(false);
  194. } catch (e) {
  195. if (typeof value === "string") {
  196. this.__expect(e.message.includes(value));
  197. } else if (typeof value === "function") {
  198. this.__expect(e instanceof value);
  199. }
  200. }
  201. });
  202. }
  203. pass(message) {
  204. // FIXME: This does nothing. If we want to implement things
  205. // like assertion count, this will have to do something
  206. }
  207. // jest-extended
  208. fail(message) {
  209. // FIXME: message is currently ignored
  210. this.__doMatcher(() => {
  211. this.__expect(false);
  212. })
  213. }
  214. // jest-extended
  215. toThrowWithMessage(class_, message) {
  216. this.__expect(typeof this.target === "function");
  217. this.__expect(class_ !== undefined);
  218. this.__expect(message !== undefined);
  219. this.__doMatcher(() => {
  220. try {
  221. this.target();
  222. this.__expect(false);
  223. } catch (e) {
  224. this.__expect(e instanceof class_);
  225. this.__expect(e.message.includes(message));
  226. }
  227. });
  228. }
  229. // Test for syntax errors; target must be a string
  230. toEval() {
  231. this.__expect(typeof this.target === "string");
  232. if (!this.inverted) {
  233. try {
  234. new Function(this.target)();
  235. } catch (e) {
  236. throw new ExpectationError();
  237. }
  238. } else {
  239. try {
  240. new Function(this.target)();
  241. throw new ExpectationError();
  242. } catch (e) {
  243. if (e.name !== "SyntaxError")
  244. throw new ExpectationError();
  245. }
  246. }
  247. }
  248. // Must compile regardless of inverted-ness
  249. toEvalTo(value) {
  250. this.__expect(typeof this.target === "string");
  251. let result;
  252. try {
  253. result = new Function(this.target)();
  254. } catch (e) {
  255. throw new ExpectationError();
  256. }
  257. this.__doMatcher(() => {
  258. this.__expect(deepEquals(value, result));
  259. });
  260. }
  261. __doMatcher(matcher) {
  262. if (!this.inverted) {
  263. matcher();
  264. } else {
  265. let threw = false;
  266. try {
  267. matcher();
  268. } catch (e) {
  269. if (e.name === "ExpectationError")
  270. threw = true;
  271. }
  272. if (!threw)
  273. throw new ExpectationError();
  274. }
  275. }
  276. __expect(value) {
  277. if (value !== true)
  278. throw new ExpectationError();
  279. }
  280. }
  281. expect = value => new Expector(value);
  282. // describe is able to lump test results inside of it by using this context
  283. // variable. Top level tests are assumed to be in the default context
  284. const defaultSuiteMessage = "__$$TOP_LEVEL$$__";
  285. let suiteMessage = defaultSuiteMessage;
  286. describe = (message, callback) => {
  287. suiteMessage = message;
  288. callback();
  289. suiteMessage = defaultSuiteMessage;
  290. }
  291. test = (message, callback) => {
  292. if (!__TestResults__[suiteMessage])
  293. __TestResults__[suiteMessage] = {};
  294. const suite = __TestResults__[suiteMessage];
  295. if (!suite[message])
  296. suite[message] = {};
  297. try {
  298. callback();
  299. suite[message] = {
  300. passed: true,
  301. };
  302. } catch (e) {
  303. suite[message] = {
  304. passed: false,
  305. };
  306. }
  307. }
  308. })();