test-common.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. class ExpectationError extends Error {
  16. constructor(message, fileName, lineNumber) {
  17. super(message, fileName, lineNumber);
  18. this.name = "ExpectationError";
  19. }
  20. }
  21. // Use an IIFE to avoid polluting the global namespace as much as possible
  22. (() => {
  23. // FIXME: This is a very naive deepEquals algorithm
  24. const deepEquals = (a, b) => {
  25. if (Array.isArray(a))
  26. return Array.isArray(b) && deepArrayEquals(a, b);
  27. if (typeof a === "object")
  28. return typeof b === "object" && deepObjectEquals(a, b);
  29. return Object.is(a, b);
  30. }
  31. const deepArrayEquals = (a, b) => {
  32. if (a.length !== b.length)
  33. return false;
  34. for (let i = 0; i < a.length; ++i) {
  35. if (!deepEquals(a[i], b[i]))
  36. return false;
  37. }
  38. return true;
  39. }
  40. const deepObjectEquals = (a, b) => {
  41. if (a === null)
  42. return b === null;
  43. for (let key of Reflect.ownKeys(a)) {
  44. if (!deepEquals(a[key], b[key]))
  45. return false;
  46. }
  47. return true;
  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. // FIXME: Take a precision argument like jest's toBeCloseTo matcher
  63. toBeCloseTo(value) {
  64. this.__expect(typeof this.target === "number");
  65. this.__expect(typeof value === "number");
  66. this.__doMatcher(() => {
  67. this.__expect(Math.abs(this.target - value) < 0.000001);
  68. })
  69. }
  70. toHaveLength(length) {
  71. this.__expect(typeof this.target.length === "number");
  72. this.__doMatcher(() => {
  73. this.__expect(Object.is(this.target.length, length));
  74. });
  75. }
  76. toHaveProperty(property, value) {
  77. this.__doMatcher(() => {
  78. let object = this.target;
  79. if (typeof property === "string" && property.includes(".")) {
  80. let propertyArray = [];
  81. while (property.includes(".")) {
  82. let index = property.indexOf(".");
  83. propertyArray.push(property.substring(0, index));
  84. if (index + 1 >= property.length)
  85. break;
  86. property = property.substring(index + 1, property.length);
  87. }
  88. propertyArray.push(property);
  89. property = propertyArray;
  90. }
  91. if (Array.isArray(property)) {
  92. for (let key of property) {
  93. this.__expect(object !== undefined && object !== null);
  94. object = object[key];
  95. }
  96. } else {
  97. object = object[property];
  98. }
  99. this.__expect(object !== undefined);
  100. if (value !== undefined)
  101. this.__expect(deepEquals(object, value));
  102. });
  103. }
  104. toBeDefined() {
  105. this.__doMatcher(() => {
  106. this.__expect(this.target !== undefined);
  107. });
  108. }
  109. toBeInstanceOf(class_) {
  110. this.__doMatcher(() => {
  111. this.__expect(this.target instanceof class_);
  112. });
  113. }
  114. toBeNull() {
  115. this.__doMatcher(() => {
  116. this.__expect(this.target === null);
  117. });
  118. }
  119. toBeUndefined() {
  120. this.__doMatcher(() => {
  121. this.__expect(this.target === undefined);
  122. });
  123. }
  124. toBeNaN() {
  125. this.__doMatcher(() => {
  126. this.__expect(isNaN(this.target));
  127. });
  128. }
  129. toBeTrue() {
  130. this.__doMatcher(() => {
  131. this.__expect(this.target === true);
  132. });
  133. }
  134. toBeFalse() {
  135. this.__doMatcher(() => {
  136. this.__expect(this.target === false);
  137. });
  138. }
  139. __validateNumericComparisonTypes(value) {
  140. this.__expect(typeof this.target === "number" || typeof this.target === "bigint");
  141. this.__expect(typeof value === "number" || typeof value === "bigint");
  142. this.__expect(typeof this.target === typeof value);
  143. }
  144. toBeLessThan(value) {
  145. this.__validateNumericComparisonTypes(value);
  146. this.__doMatcher(() => {
  147. this.__expect(this.target < value);
  148. });
  149. }
  150. toBeLessThanOrEqual(value) {
  151. this.__validateNumericComparisonTypes(value);
  152. this.__doMatcher(() => {
  153. this.__expect(this.target <= value);
  154. });
  155. }
  156. toBeGreaterThan(value) {
  157. this.__validateNumericComparisonTypes(value);
  158. this.__doMatcher(() => {
  159. this.__expect(this.target > value);
  160. });
  161. }
  162. toBeGreaterThanOrEqual(value) {
  163. this.__validateNumericComparisonTypes(value);
  164. this.__doMatcher(() => {
  165. this.__expect(this.target >= value);
  166. });
  167. }
  168. toContain(item) {
  169. this.__doMatcher(() => {
  170. // FIXME: Iterator check
  171. for (let element of this.target) {
  172. if (item === element)
  173. return;
  174. }
  175. throw new ExpectationError();
  176. });
  177. }
  178. toContainEqual(item) {
  179. this.__doMatcher(() => {
  180. // FIXME: Iterator check
  181. for (let element of this.target) {
  182. if (deepEquals(item, element))
  183. return;
  184. }
  185. throw new ExpectationError();
  186. });
  187. }
  188. toEqual(value) {
  189. this.__doMatcher(() => {
  190. this.__expect(deepEquals(this.target, value));
  191. });
  192. }
  193. toThrow(value) {
  194. this.__expect(typeof this.target === "function");
  195. this.__expect(typeof value === "string"
  196. || typeof value === "function"
  197. || typeof value === "object"
  198. || value === undefined);
  199. this.__doMatcher(() => {
  200. let threw = true;
  201. try {
  202. this.target();
  203. threw = false;
  204. } catch (e) {
  205. if (typeof value === "string") {
  206. this.__expect(e.message.includes(value));
  207. } else if (typeof value === "function") {
  208. this.__expect(e instanceof value);
  209. } else if (typeof value === "object") {
  210. this.__expect(e.message === value.message);
  211. }
  212. }
  213. this.__expect(threw);
  214. });
  215. }
  216. pass(message) {
  217. // FIXME: This does nothing. If we want to implement things
  218. // like assertion count, this will have to do something
  219. }
  220. // jest-extended
  221. fail(message) {
  222. // FIXME: message is currently ignored
  223. this.__doMatcher(() => {
  224. this.__expect(false);
  225. })
  226. }
  227. // jest-extended
  228. toThrowWithMessage(class_, message) {
  229. this.__expect(typeof this.target === "function");
  230. this.__expect(class_ !== undefined);
  231. this.__expect(message !== undefined);
  232. this.__doMatcher(() => {
  233. try {
  234. this.target();
  235. this.__expect(false);
  236. } catch (e) {
  237. this.__expect(e instanceof class_);
  238. this.__expect(e.message.includes(message));
  239. }
  240. });
  241. }
  242. // Test for syntax errors; target must be a string
  243. toEval() {
  244. this.__expect(typeof this.target === "string");
  245. if (!this.inverted) {
  246. try {
  247. new Function(this.target)();
  248. } catch (e) {
  249. throw new ExpectationError();
  250. }
  251. } else {
  252. let threw;
  253. try {
  254. new Function(this.target)();
  255. threw = false;
  256. } catch (e) {
  257. threw = true;
  258. }
  259. this.__expect(threw);
  260. }
  261. }
  262. // Must compile regardless of inverted-ness
  263. toEvalTo(value) {
  264. this.__expect(typeof this.target === "string");
  265. let result;
  266. try {
  267. result = new Function(this.target)();
  268. } catch (e) {
  269. throw new ExpectationError();
  270. }
  271. this.__doMatcher(() => {
  272. this.__expect(deepEquals(value, result));
  273. });
  274. }
  275. toHaveConfigurableProperty(property) {
  276. this.__expect(this.target !== undefined && this.target !== null);
  277. let d = Object.getOwnPropertyDescriptor(this.target, property);
  278. this.__expect(d !== undefined);
  279. this.__doMatcher(() => {
  280. this.__expect(d.configurable);
  281. });
  282. }
  283. toHaveEnumerableProperty(property) {
  284. this.__expect(this.target !== undefined && this.target !== null);
  285. let d = Object.getOwnPropertyDescriptor(this.target, property);
  286. this.__expect(d !== undefined);
  287. this.__doMatcher(() => {
  288. this.__expect(d.enumerable);
  289. });
  290. }
  291. toHaveWritableProperty(property) {
  292. this.__expect(this.target !== undefined && this.target !== null);
  293. let d = Object.getOwnPropertyDescriptor(this.target, property);
  294. this.__expect(d !== undefined);
  295. this.__doMatcher(() => {
  296. this.__expect(d.writable);
  297. });
  298. }
  299. toHaveValueProperty(property, value) {
  300. this.__expect(this.target !== undefined && this.target !== null);
  301. let d = Object.getOwnPropertyDescriptor(this.target, property);
  302. this.__expect(d !== undefined);
  303. this.__doMatcher(() => {
  304. this.__expect(d.value !== undefined);
  305. if (value !== undefined)
  306. this.__expect(deepEquals(value, d.value));
  307. });
  308. }
  309. toHaveGetterProperty(property) {
  310. this.__expect(this.target !== undefined && this.target !== null);
  311. let d = Object.getOwnPropertyDescriptor(this.target, property);
  312. this.__expect(d !== undefined);
  313. this.__doMatcher(() => {
  314. this.__expect(d.get !== undefined);
  315. });
  316. }
  317. toHaveSetterProperty(property) {
  318. this.__expect(this.target !== undefined && this.target !== null);
  319. let d = Object.getOwnPropertyDescriptor(this.target, property);
  320. this.__expect(d !== undefined);
  321. this.__doMatcher(() => {
  322. this.__expect(d.set !== undefined);
  323. });
  324. }
  325. __doMatcher(matcher) {
  326. if (!this.inverted) {
  327. matcher();
  328. } else {
  329. let threw = false;
  330. try {
  331. matcher();
  332. } catch (e) {
  333. if (e.name === "ExpectationError")
  334. threw = true;
  335. }
  336. if (!threw)
  337. throw new ExpectationError();
  338. }
  339. }
  340. __expect(value) {
  341. if (value !== true)
  342. throw new ExpectationError();
  343. }
  344. }
  345. expect = value => new Expector(value);
  346. // describe is able to lump test results inside of it by using this context
  347. // variable. Top level tests have the default suite message
  348. const defaultSuiteMessage = "__$$TOP_LEVEL$$__";
  349. let suiteMessage = defaultSuiteMessage;
  350. describe = (message, callback) => {
  351. suiteMessage = message;
  352. callback();
  353. suiteMessage = defaultSuiteMessage;
  354. }
  355. test = (message, callback) => {
  356. if (!__TestResults__[suiteMessage])
  357. __TestResults__[suiteMessage] = {};
  358. const suite = __TestResults__[suiteMessage];
  359. if (suite[message])
  360. throw new Error("Duplicate test name: " + message);
  361. try {
  362. callback();
  363. suite[message] = {
  364. result: "pass",
  365. };
  366. } catch (e) {
  367. suite[message] = {
  368. result: "fail",
  369. };
  370. }
  371. }
  372. test.skip = (message, callback) => {
  373. if (typeof callback !== "function")
  374. throw new Error("test.skip has invalid second argument (must be a function)");
  375. if (!__TestResults__[suiteMessage])
  376. __TestResults__[suiteMessage] = {};
  377. const suite = __TestResults__[suiteMessage];
  378. if (suite[message])
  379. throw new Error("Duplicate test name: " + message);
  380. suite[message] = {
  381. result: "skip",
  382. }
  383. }
  384. })();