groupby-test262.test.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /*
  2. * Portions adapted from Test262 at revision 250f204f23a9249ff204be2baec29600faae7b75:
  3. * - test/built-ins/Object/groupBy/evenOdd.js
  4. * - test/built-ins/Object/groupBy/groupLength.js
  5. * - test/built-ins/Object/groupBy/callback-arg.js
  6. * - test/built-ins/Object/groupBy/string.js
  7. * - test/built-ins/Object/groupBy/emptyList.js
  8. * - test/built-ins/Object/groupBy/toPropertyKey.js
  9. * - test/built-ins/Object/groupBy/invalid-property-key.js
  10. * - test/built-ins/Object/groupBy/invalid-iterable.js
  11. * - test/built-ins/Object/groupBy/invalid-callback.js
  12. * - test/built-ins/Object/groupBy/callback-throws.js
  13. * - test/built-ins/Object/groupBy/null-prototype.js
  14. * - test/built-ins/Map/groupBy/evenOdd.js
  15. * - test/built-ins/Map/groupBy/groupLength.js
  16. * - test/built-ins/Map/groupBy/callback-arg.js
  17. * - test/built-ins/Map/groupBy/string.js
  18. * - test/built-ins/Map/groupBy/emptyList.js
  19. * - test/built-ins/Map/groupBy/toPropertyKey.js
  20. * - test/built-ins/Map/groupBy/negativeZero.js
  21. * - test/built-ins/Map/groupBy/invalid-iterable.js
  22. * - test/built-ins/Map/groupBy/invalid-callback.js
  23. * - test/built-ins/Map/groupBy/callback-throws.js
  24. * - test/built-ins/Map/groupBy/map-instance.js
  25. *
  26. * Copyright (c) 2023 Ecma International. All rights reserved.
  27. * Test262 portions are governed by the BSD license in LICENSE.test262.
  28. * Invalid-iterable cases use a plain object, equivalent to the upstream object's absent
  29. * Symbol.iterator; CodeMode does not support symbol-keyed properties.
  30. */
  31. import { describe, expect, test } from "bun:test"
  32. import { Effect } from "effect"
  33. import { CodeMode } from "../src/index.js"
  34. const execute = (code: string) => Effect.runPromise(CodeMode.execute({ code, tools: {} }))
  35. const value = async (code: string) => {
  36. const result = await execute(code)
  37. if (!result.ok) throw new Error(`expected success, got ${result.error.kind}: ${result.error.message}`)
  38. return result.value
  39. }
  40. describe("Object.groupBy Test262 parity", () => {
  41. test("groups values by coerced property keys", async () => {
  42. expect(
  43. await value(`
  44. const values = [1, 2, 3]
  45. const grouped = Object.groupBy(values, (value) => value % 2 === 0 ? "even" : "odd")
  46. const stringable = { toString: () => 1 }
  47. const coerced = Object.groupBy([1, "1", stringable], (value) => value)
  48. const lengths = Object.groupBy(["hello", "test", "world"], (value) => value.length)
  49. return [
  50. Object.keys(grouped), grouped.even, grouped.odd, grouped.toString === undefined,
  51. Object.keys(coerced), coerced[1].length,
  52. Object.keys(lengths), lengths[5], lengths[4],
  53. ]
  54. `),
  55. ).toEqual([["odd", "even"], [2], [1, 3], true, ["1"], 3, ["4", "5"], ["hello", "world"], ["test"]])
  56. })
  57. test("passes each value and index to the callback", async () => {
  58. expect(
  59. await value(`
  60. const values = [-0, 0, 1, 2, 3]
  61. const seen = []
  62. Object.groupBy(values, (value, index, ...extra) => {
  63. seen.push([value === values[index], index, extra.length])
  64. return null
  65. })
  66. return seen
  67. `),
  68. ).toEqual([
  69. [true, 0, 0],
  70. [true, 1, 0],
  71. [true, 2, 0],
  72. [true, 3, 0],
  73. [true, 4, 0],
  74. ])
  75. })
  76. test("supports strings and empty collections", async () => {
  77. expect(
  78. await value(`
  79. const grouped = Object.groupBy("🥰💩🙏😈", (char) => char < "🙏" ? "before" : "after")
  80. const empty = Object.groupBy([], () => { throw new Error("not called") })
  81. return [Object.keys(grouped), grouped.before, grouped.after, Object.keys(empty)]
  82. `),
  83. ).toEqual([["after", "before"], ["💩", "😈"], ["🥰", "🙏"], []])
  84. })
  85. test("rejects invalid inputs and propagates callback and key-coercion failures", async () => {
  86. expect(
  87. await value(`
  88. const messages = []
  89. try { Object.groupBy({}, () => { throw new Error("not called") }) } catch (error) { messages.push(error.name) }
  90. for (const callback of [null, undefined, {}]) {
  91. try { Object.groupBy([], callback) } catch (error) { messages.push(error.name) }
  92. }
  93. try { Object.groupBy([1], () => { throw new Error("callback") }) } catch (error) { messages.push(error.message) }
  94. try {
  95. Object.groupBy([1], () => ({
  96. toString: () => { throw new Error("property key") },
  97. }))
  98. } catch (error) { messages.push(error.message) }
  99. return messages
  100. `),
  101. ).toEqual(["TypeError", "TypeError", "TypeError", "TypeError", "callback", "property key"])
  102. })
  103. })
  104. describe("Map.groupBy Test262 parity", () => {
  105. test("returns a Map with identity-preserving groups", async () => {
  106. expect(
  107. await value(`
  108. const stringable = { toString: () => 1 }
  109. const grouped = Map.groupBy([1, "1", stringable], (value) => value)
  110. const parity = Map.groupBy([1, 2, 3], (value) => value % 2 === 0 ? "even" : "odd")
  111. const lengths = Map.groupBy(["hello", "test", "world"], (value) => value.length)
  112. return [
  113. grouped instanceof Map,
  114. grouped.size,
  115. grouped.get(1),
  116. grouped.get("1"),
  117. grouped.has(stringable),
  118. grouped.keys().length,
  119. parity.get("even"),
  120. parity.get("odd"),
  121. lengths.keys(),
  122. lengths.get(5),
  123. lengths.get(4),
  124. ]
  125. `),
  126. ).toEqual([true, 3, [1], ["1"], true, 3, [2], [1, 3], [5, 4], ["hello", "world"], ["test"]])
  127. })
  128. test("normalizes negative zero and passes each value and index", async () => {
  129. expect(
  130. await value(`
  131. const values = [-0, 0, 1, 2, 3]
  132. const seen = []
  133. const grouped = Map.groupBy(values, (value, index, ...extra) => {
  134. seen.push([value === values[index], index, extra.length])
  135. return value
  136. })
  137. return [grouped.size, grouped.get(0), seen]
  138. `),
  139. ).toEqual([
  140. 4,
  141. [-0, 0],
  142. [
  143. [true, 0, 0],
  144. [true, 1, 0],
  145. [true, 2, 0],
  146. [true, 3, 0],
  147. [true, 4, 0],
  148. ],
  149. ])
  150. })
  151. test("supports strings and empty collections", async () => {
  152. expect(
  153. await value(`
  154. const grouped = Map.groupBy("🥰💩🙏😈", (char) => char < "🙏" ? "before" : "after")
  155. const empty = Map.groupBy([], () => { throw new Error("not called") })
  156. return [grouped.keys(), grouped.get("before"), grouped.get("after"), empty.size]
  157. `),
  158. ).toEqual([["after", "before"], ["💩", "😈"], ["🥰", "🙏"], 0])
  159. })
  160. test("rejects invalid inputs and propagates callback failures", async () => {
  161. expect(
  162. await value(`
  163. const results = []
  164. try { Map.groupBy({}, () => { throw new Error("not called") }) } catch (error) { results.push(error.name) }
  165. for (const callback of [null, undefined, {}]) {
  166. try { Map.groupBy([], callback) } catch (error) { results.push(error.name) }
  167. }
  168. try { Map.groupBy([1], () => { throw new Error("callback") }) }
  169. catch (error) { results.push(error.message) }
  170. return results
  171. `),
  172. ).toEqual(["TypeError", "TypeError", "TypeError", "TypeError", "callback"])
  173. })
  174. })