@fgv/ts-random 5.1.0-8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/config/jest.config.json +13 -0
  2. package/config/rig.json +4 -0
  3. package/config/typedoc.json +7 -0
  4. package/docs/Chars/README.md +152 -0
  5. package/docs/Chars/variables/all.md +9 -0
  6. package/docs/Chars/variables/alphanumeric.md +9 -0
  7. package/docs/Chars/variables/base64.md +9 -0
  8. package/docs/Chars/variables/base64Url.md +9 -0
  9. package/docs/Chars/variables/base64UrlNoPadding.md +9 -0
  10. package/docs/Chars/variables/digits.md +9 -0
  11. package/docs/Chars/variables/hex.md +9 -0
  12. package/docs/Chars/variables/hexDigits.md +9 -0
  13. package/docs/Chars/variables/hexDigitsUpper.md +9 -0
  14. package/docs/Chars/variables/letters.md +9 -0
  15. package/docs/Chars/variables/lowerCase.md +9 -0
  16. package/docs/Chars/variables/printable.md +9 -0
  17. package/docs/Chars/variables/symbols.md +9 -0
  18. package/docs/Chars/variables/upperCase.md +9 -0
  19. package/docs/Chars/variables/whitespace.md +9 -0
  20. package/docs/Generator/README.md +153 -0
  21. package/docs/Generator/classes/PseudoRandomGenerator.clone.md +17 -0
  22. package/docs/Generator/classes/PseudoRandomGenerator.create.md +24 -0
  23. package/docs/Generator/classes/PseudoRandomGenerator.createChild.md +24 -0
  24. package/docs/Generator/classes/PseudoRandomGenerator.ensureRng.md +25 -0
  25. package/docs/Generator/classes/PseudoRandomGenerator.getGlobalRng.md +17 -0
  26. package/docs/Generator/classes/PseudoRandomGenerator.md +258 -0
  27. package/docs/Generator/classes/PseudoRandomGenerator.nextBoolean.md +24 -0
  28. package/docs/Generator/classes/PseudoRandomGenerator.nextFloat.md +17 -0
  29. package/docs/Generator/classes/PseudoRandomGenerator.nextInRange.md +25 -0
  30. package/docs/Generator/classes/PseudoRandomGenerator.nextInt.md +24 -0
  31. package/docs/Generator/classes/PseudoRandomGenerator.nextString.md +25 -0
  32. package/docs/Generator/classes/PseudoRandomGenerator.pickNext.md +24 -0
  33. package/docs/Generator/classes/PseudoRandomGenerator.pickRandom.md +24 -0
  34. package/docs/Generator/classes/PseudoRandomGenerator.pickSequence.md +24 -0
  35. package/docs/Generator/classes/PseudoRandomGenerator.pickSequential.md +24 -0
  36. package/docs/Generator/classes/PseudoRandomGenerator.rng.md +11 -0
  37. package/docs/Generator/classes/PseudoRandomGenerator.setGlobalRng.md +24 -0
  38. package/docs/Generator/classes/SeededRandomSource.clone.md +17 -0
  39. package/docs/Generator/classes/SeededRandomSource.counter.md +11 -0
  40. package/docs/Generator/classes/SeededRandomSource.create.md +24 -0
  41. package/docs/Generator/classes/SeededRandomSource.createChild.md +24 -0
  42. package/docs/Generator/classes/SeededRandomSource.hashSeed.md +24 -0
  43. package/docs/Generator/classes/SeededRandomSource.hashStateAndLabel.md +25 -0
  44. package/docs/Generator/classes/SeededRandomSource.lineage.md +9 -0
  45. package/docs/Generator/classes/SeededRandomSource.md +187 -0
  46. package/docs/Generator/classes/SeededRandomSource.mulberryStep.md +24 -0
  47. package/docs/Generator/classes/SeededRandomSource.next.md +17 -0
  48. package/docs/Generator/classes/SeededRandomSource.seed.md +9 -0
  49. package/docs/Generator/interfaces/INextResult.md +61 -0
  50. package/docs/Generator/interfaces/INextResult.nextState.md +9 -0
  51. package/docs/Generator/interfaces/INextResult.value.md +9 -0
  52. package/docs/Generator/interfaces/IPseudoRandomGeneratorCreateParams.global.md +9 -0
  53. package/docs/Generator/interfaces/IPseudoRandomGeneratorCreateParams.md +78 -0
  54. package/docs/Generator/interfaces/IPseudoRandomGeneratorCreateParams.seed.md +9 -0
  55. package/docs/Generator/interfaces/IPseudoRandomGeneratorCreateParams.step.md +9 -0
  56. package/docs/Generator/interfaces/IRandomSequencePickParams.candidates.md +9 -0
  57. package/docs/Generator/interfaces/IRandomSequencePickParams.count.md +9 -0
  58. package/docs/Generator/interfaces/IRandomSequencePickParams.how.md +9 -0
  59. package/docs/Generator/interfaces/IRandomSequencePickParams.md +79 -0
  60. package/docs/Generator/interfaces/ISeedPair.md +61 -0
  61. package/docs/Generator/interfaces/ISeedPair.seed.md +9 -0
  62. package/docs/Generator/interfaces/ISeedPair.state.md +9 -0
  63. package/docs/Generator/interfaces/ISeededRandomSourceConstructorParams.counter.md +9 -0
  64. package/docs/Generator/interfaces/ISeededRandomSourceConstructorParams.lineage.md +9 -0
  65. package/docs/Generator/interfaces/ISeededRandomSourceConstructorParams.md +112 -0
  66. package/docs/Generator/interfaces/ISeededRandomSourceConstructorParams.seed.md +9 -0
  67. package/docs/Generator/interfaces/ISeededRandomSourceConstructorParams.state.md +9 -0
  68. package/docs/Generator/interfaces/ISeededRandomSourceConstructorParams.step.md +9 -0
  69. package/docs/Generator/interfaces/ISeededRandomSourceCreateParams.md +61 -0
  70. package/docs/Generator/interfaces/ISeededRandomSourceCreateParams.seed.md +9 -0
  71. package/docs/Generator/interfaces/ISeededRandomSourceCreateParams.step.md +9 -0
  72. package/docs/Generator/interfaces/ISequentialPickParams.candidates.md +9 -0
  73. package/docs/Generator/interfaces/ISequentialPickParams.count.md +9 -0
  74. package/docs/Generator/interfaces/ISequentialPickParams.how.md +9 -0
  75. package/docs/Generator/interfaces/ISequentialPickParams.md +79 -0
  76. package/docs/Generator/type-aliases/GeneratorGlobalThis.md +11 -0
  77. package/docs/Generator/type-aliases/RandomStepFunction.md +11 -0
  78. package/docs/Generator/type-aliases/SequencePickParams.md +11 -0
  79. package/docs/GeneratorData/Chars/README.md +152 -0
  80. package/docs/GeneratorData/Chars/variables/all.md +9 -0
  81. package/docs/GeneratorData/Chars/variables/alphanumeric.md +9 -0
  82. package/docs/GeneratorData/Chars/variables/base64.md +9 -0
  83. package/docs/GeneratorData/Chars/variables/base64Url.md +9 -0
  84. package/docs/GeneratorData/Chars/variables/base64UrlNoPadding.md +9 -0
  85. package/docs/GeneratorData/Chars/variables/digits.md +9 -0
  86. package/docs/GeneratorData/Chars/variables/hex.md +9 -0
  87. package/docs/GeneratorData/Chars/variables/hexDigits.md +9 -0
  88. package/docs/GeneratorData/Chars/variables/hexDigitsUpper.md +9 -0
  89. package/docs/GeneratorData/Chars/variables/letters.md +9 -0
  90. package/docs/GeneratorData/Chars/variables/lowerCase.md +9 -0
  91. package/docs/GeneratorData/Chars/variables/printable.md +9 -0
  92. package/docs/GeneratorData/Chars/variables/symbols.md +9 -0
  93. package/docs/GeneratorData/Chars/variables/upperCase.md +9 -0
  94. package/docs/GeneratorData/Chars/variables/whitespace.md +9 -0
  95. package/docs/GeneratorData/README.md +35 -0
  96. package/docs/GeneratorData/Words/README.md +125 -0
  97. package/docs/GeneratorData/Words/variables/Adjectives.md +7 -0
  98. package/docs/GeneratorData/Words/variables/Animals.md +7 -0
  99. package/docs/GeneratorData/Words/variables/Cities.md +7 -0
  100. package/docs/GeneratorData/Words/variables/Colors.md +7 -0
  101. package/docs/GeneratorData/Words/variables/Countries.md +7 -0
  102. package/docs/GeneratorData/Words/variables/Domains.md +7 -0
  103. package/docs/GeneratorData/Words/variables/FamilyNames.md +9 -0
  104. package/docs/GeneratorData/Words/variables/FirstNames.md +9 -0
  105. package/docs/GeneratorData/Words/variables/FirstNamesFemale.md +9 -0
  106. package/docs/GeneratorData/Words/variables/FirstNamesMale.md +9 -0
  107. package/docs/GeneratorData/Words/variables/Gerunds.md +7 -0
  108. package/docs/GeneratorData/Words/variables/jobs.md +7 -0
  109. package/docs/GeneratorData/variables/Adjectives.md +7 -0
  110. package/docs/GeneratorData/variables/Animals.md +7 -0
  111. package/docs/GeneratorData/variables/Cities.md +7 -0
  112. package/docs/GeneratorData/variables/Colors.md +7 -0
  113. package/docs/GeneratorData/variables/Countries.md +7 -0
  114. package/docs/GeneratorData/variables/Domains.md +7 -0
  115. package/docs/GeneratorData/variables/FamilyNames.md +9 -0
  116. package/docs/GeneratorData/variables/FirstNames.md +9 -0
  117. package/docs/GeneratorData/variables/FirstNamesFemale.md +9 -0
  118. package/docs/GeneratorData/variables/FirstNamesMale.md +9 -0
  119. package/docs/GeneratorData/variables/Gerunds.md +7 -0
  120. package/docs/GeneratorData/variables/all.md +9 -0
  121. package/docs/GeneratorData/variables/alphanumeric.md +9 -0
  122. package/docs/GeneratorData/variables/base64.md +9 -0
  123. package/docs/GeneratorData/variables/base64Url.md +9 -0
  124. package/docs/GeneratorData/variables/base64UrlNoPadding.md +9 -0
  125. package/docs/GeneratorData/variables/digits.md +9 -0
  126. package/docs/GeneratorData/variables/hex.md +9 -0
  127. package/docs/GeneratorData/variables/hexDigits.md +9 -0
  128. package/docs/GeneratorData/variables/hexDigitsUpper.md +9 -0
  129. package/docs/GeneratorData/variables/jobs.md +7 -0
  130. package/docs/GeneratorData/variables/letters.md +9 -0
  131. package/docs/GeneratorData/variables/lowerCase.md +9 -0
  132. package/docs/GeneratorData/variables/printable.md +9 -0
  133. package/docs/GeneratorData/variables/symbols.md +9 -0
  134. package/docs/GeneratorData/variables/upperCase.md +9 -0
  135. package/docs/GeneratorData/variables/whitespace.md +9 -0
  136. package/docs/README.md +35 -0
  137. package/docs/Words/README.md +125 -0
  138. package/docs/Words/variables/Adjectives.md +7 -0
  139. package/docs/Words/variables/Animals.md +7 -0
  140. package/docs/Words/variables/Cities.md +7 -0
  141. package/docs/Words/variables/Colors.md +7 -0
  142. package/docs/Words/variables/Countries.md +7 -0
  143. package/docs/Words/variables/Domains.md +7 -0
  144. package/docs/Words/variables/FamilyNames.md +9 -0
  145. package/docs/Words/variables/FirstNames.md +9 -0
  146. package/docs/Words/variables/FirstNamesFemale.md +9 -0
  147. package/docs/Words/variables/FirstNamesMale.md +9 -0
  148. package/docs/Words/variables/Gerunds.md +7 -0
  149. package/docs/Words/variables/jobs.md +7 -0
  150. package/docs/classes/PseudoRandomGenerator.clone.md +17 -0
  151. package/docs/classes/PseudoRandomGenerator.create.md +24 -0
  152. package/docs/classes/PseudoRandomGenerator.createChild.md +24 -0
  153. package/docs/classes/PseudoRandomGenerator.ensureRng.md +25 -0
  154. package/docs/classes/PseudoRandomGenerator.getGlobalRng.md +17 -0
  155. package/docs/classes/PseudoRandomGenerator.md +258 -0
  156. package/docs/classes/PseudoRandomGenerator.nextBoolean.md +24 -0
  157. package/docs/classes/PseudoRandomGenerator.nextFloat.md +17 -0
  158. package/docs/classes/PseudoRandomGenerator.nextInRange.md +25 -0
  159. package/docs/classes/PseudoRandomGenerator.nextInt.md +24 -0
  160. package/docs/classes/PseudoRandomGenerator.nextString.md +25 -0
  161. package/docs/classes/PseudoRandomGenerator.pickNext.md +24 -0
  162. package/docs/classes/PseudoRandomGenerator.pickRandom.md +24 -0
  163. package/docs/classes/PseudoRandomGenerator.pickSequence.md +24 -0
  164. package/docs/classes/PseudoRandomGenerator.pickSequential.md +24 -0
  165. package/docs/classes/PseudoRandomGenerator.rng.md +11 -0
  166. package/docs/classes/PseudoRandomGenerator.setGlobalRng.md +24 -0
  167. package/docs/classes/SeededRandomSource.clone.md +17 -0
  168. package/docs/classes/SeededRandomSource.counter.md +11 -0
  169. package/docs/classes/SeededRandomSource.create.md +24 -0
  170. package/docs/classes/SeededRandomSource.createChild.md +24 -0
  171. package/docs/classes/SeededRandomSource.hashSeed.md +24 -0
  172. package/docs/classes/SeededRandomSource.hashStateAndLabel.md +25 -0
  173. package/docs/classes/SeededRandomSource.lineage.md +9 -0
  174. package/docs/classes/SeededRandomSource.md +187 -0
  175. package/docs/classes/SeededRandomSource.mulberryStep.md +24 -0
  176. package/docs/classes/SeededRandomSource.next.md +17 -0
  177. package/docs/classes/SeededRandomSource.seed.md +9 -0
  178. package/docs/interfaces/INextResult.md +61 -0
  179. package/docs/interfaces/INextResult.nextState.md +9 -0
  180. package/docs/interfaces/INextResult.value.md +9 -0
  181. package/docs/interfaces/IPseudoRandomGeneratorCreateParams.global.md +9 -0
  182. package/docs/interfaces/IPseudoRandomGeneratorCreateParams.md +78 -0
  183. package/docs/interfaces/IPseudoRandomGeneratorCreateParams.seed.md +9 -0
  184. package/docs/interfaces/IPseudoRandomGeneratorCreateParams.step.md +9 -0
  185. package/docs/interfaces/IRandomSequencePickParams.candidates.md +9 -0
  186. package/docs/interfaces/IRandomSequencePickParams.count.md +9 -0
  187. package/docs/interfaces/IRandomSequencePickParams.how.md +9 -0
  188. package/docs/interfaces/IRandomSequencePickParams.md +79 -0
  189. package/docs/interfaces/ISeedPair.md +61 -0
  190. package/docs/interfaces/ISeedPair.seed.md +9 -0
  191. package/docs/interfaces/ISeedPair.state.md +9 -0
  192. package/docs/interfaces/ISeededRandomSourceConstructorParams.counter.md +9 -0
  193. package/docs/interfaces/ISeededRandomSourceConstructorParams.lineage.md +9 -0
  194. package/docs/interfaces/ISeededRandomSourceConstructorParams.md +112 -0
  195. package/docs/interfaces/ISeededRandomSourceConstructorParams.seed.md +9 -0
  196. package/docs/interfaces/ISeededRandomSourceConstructorParams.state.md +9 -0
  197. package/docs/interfaces/ISeededRandomSourceConstructorParams.step.md +9 -0
  198. package/docs/interfaces/ISeededRandomSourceCreateParams.md +61 -0
  199. package/docs/interfaces/ISeededRandomSourceCreateParams.seed.md +9 -0
  200. package/docs/interfaces/ISeededRandomSourceCreateParams.step.md +9 -0
  201. package/docs/interfaces/ISequentialPickParams.candidates.md +9 -0
  202. package/docs/interfaces/ISequentialPickParams.count.md +9 -0
  203. package/docs/interfaces/ISequentialPickParams.how.md +9 -0
  204. package/docs/interfaces/ISequentialPickParams.md +79 -0
  205. package/docs/type-aliases/GeneratorGlobalThis.md +11 -0
  206. package/docs/type-aliases/RandomStepFunction.md +11 -0
  207. package/docs/type-aliases/SequencePickParams.md +11 -0
  208. package/docs/variables/Adjectives.md +7 -0
  209. package/docs/variables/Animals.md +7 -0
  210. package/docs/variables/Cities.md +7 -0
  211. package/docs/variables/Colors.md +7 -0
  212. package/docs/variables/Countries.md +7 -0
  213. package/docs/variables/Domains.md +7 -0
  214. package/docs/variables/FamilyNames.md +9 -0
  215. package/docs/variables/FirstNames.md +9 -0
  216. package/docs/variables/FirstNamesFemale.md +9 -0
  217. package/docs/variables/FirstNamesMale.md +9 -0
  218. package/docs/variables/Gerunds.md +7 -0
  219. package/docs/variables/all.md +9 -0
  220. package/docs/variables/alphanumeric.md +9 -0
  221. package/docs/variables/base64.md +9 -0
  222. package/docs/variables/base64Url.md +9 -0
  223. package/docs/variables/base64UrlNoPadding.md +9 -0
  224. package/docs/variables/digits.md +9 -0
  225. package/docs/variables/hex.md +9 -0
  226. package/docs/variables/hexDigits.md +9 -0
  227. package/docs/variables/hexDigitsUpper.md +9 -0
  228. package/docs/variables/jobs.md +7 -0
  229. package/docs/variables/letters.md +9 -0
  230. package/docs/variables/lowerCase.md +9 -0
  231. package/docs/variables/printable.md +9 -0
  232. package/docs/variables/symbols.md +9 -0
  233. package/docs/variables/upperCase.md +9 -0
  234. package/docs/variables/whitespace.md +9 -0
  235. package/package.json +49 -0
  236. package/src/generator/generators.ts +273 -0
  237. package/src/generator/index.ts +22 -0
  238. package/src/generator/randomSource.ts +224 -0
  239. package/src/generator-data/charClasses.ts +95 -0
  240. package/src/generator-data/index.ts +22 -0
  241. package/src/generator-data/words/adjectives.ts +247 -0
  242. package/src/generator-data/words/animals.ts +75 -0
  243. package/src/generator-data/words/cities.ts +229 -0
  244. package/src/generator-data/words/colors.ts +56 -0
  245. package/src/generator-data/words/countries.ts +217 -0
  246. package/src/generator-data/words/domains.ts +47 -0
  247. package/src/generator-data/words/gerunds.ts +126 -0
  248. package/src/generator-data/words/index.ts +29 -0
  249. package/src/generator-data/words/jobs.ts +175 -0
  250. package/src/generator-data/words/names.ts +717 -0
  251. package/src/index.ts +7 -0
  252. package/src/test/unit/generators.test.ts +697 -0
  253. package/src/test/unit/randomSource.test.ts +526 -0
  254. package/tsconfig.json +7 -0
@@ -0,0 +1,697 @@
1
+ import '@fgv/ts-utils-jest';
2
+
3
+ import { IRandomSequencePickParams, ISequentialPickParams, PseudoRandomGenerator } from '../../generator';
4
+
5
+ function createGenerator(seed: number | string = 42): PseudoRandomGenerator {
6
+ return PseudoRandomGenerator.create({ seed }).orThrow();
7
+ }
8
+
9
+ describe('PseudoRandomGenerator', () => {
10
+ // Clean up global state between tests
11
+ afterEach(() => {
12
+ PseudoRandomGenerator.setGlobalRng(undefined);
13
+ });
14
+
15
+ describe('create', () => {
16
+ test('succeeds with no params', () => {
17
+ expect(PseudoRandomGenerator.create()).toSucceedAndSatisfy((gen) => {
18
+ expect(gen.rng).toBeDefined();
19
+ expect(gen.rng.counter).toBe(0);
20
+ });
21
+ });
22
+
23
+ test('succeeds with a seed', () => {
24
+ expect(PseudoRandomGenerator.create({ seed: 123 })).toSucceedAndSatisfy((gen) => {
25
+ expect(gen.rng.seed).toBe('123');
26
+ });
27
+ });
28
+
29
+ test('succeeds with a string seed', () => {
30
+ expect(PseudoRandomGenerator.create({ seed: 'hello' })).toSucceedAndSatisfy((gen) => {
31
+ expect(gen.rng.seed).toBe('hello');
32
+ });
33
+ });
34
+
35
+ test('same seed produces identical sequences', () => {
36
+ const gen1 = createGenerator('deterministic');
37
+ const gen2 = createGenerator('deterministic');
38
+ const values1 = Array.from({ length: 10 }, () => gen1.nextFloat());
39
+ const values2 = Array.from({ length: 10 }, () => gen2.nextFloat());
40
+ expect(values1).toEqual(values2);
41
+ });
42
+
43
+ test('different seeds produce different sequences', () => {
44
+ const gen1 = createGenerator('alpha');
45
+ const gen2 = createGenerator('beta');
46
+ const values1 = Array.from({ length: 4 }, () => gen1.nextFloat());
47
+ const values2 = Array.from({ length: 4 }, () => gen2.nextFloat());
48
+ expect(values1).not.toEqual(values2);
49
+ });
50
+
51
+ test('sets global rng when global is true', () => {
52
+ expect(PseudoRandomGenerator.getGlobalRng()).toBeUndefined();
53
+ expect(PseudoRandomGenerator.create({ seed: 1, global: true })).toSucceedAndSatisfy((gen) => {
54
+ expect(PseudoRandomGenerator.getGlobalRng()).toBe(gen);
55
+ });
56
+ });
57
+
58
+ test('does not set global rng when global is false', () => {
59
+ PseudoRandomGenerator.create({ seed: 1, global: false }).orThrow();
60
+ expect(PseudoRandomGenerator.getGlobalRng()).toBeUndefined();
61
+ });
62
+
63
+ test('does not set global rng when global is omitted', () => {
64
+ PseudoRandomGenerator.create({ seed: 1 }).orThrow();
65
+ expect(PseudoRandomGenerator.getGlobalRng()).toBeUndefined();
66
+ });
67
+ });
68
+
69
+ describe('clone', () => {
70
+ test('produces identical sequence from clone point', () => {
71
+ const gen = createGenerator(7);
72
+ gen.nextFloat();
73
+ gen.nextFloat();
74
+
75
+ const clone = gen.clone();
76
+ const original = Array.from({ length: 5 }, () => gen.nextFloat());
77
+ const cloned = Array.from({ length: 5 }, () => clone.nextFloat());
78
+ expect(original).toEqual(cloned);
79
+ });
80
+
81
+ test('clone is independent of parent', () => {
82
+ const gen = createGenerator(7);
83
+ const clone = gen.clone();
84
+
85
+ gen.nextFloat();
86
+ gen.nextFloat();
87
+ gen.nextFloat();
88
+
89
+ expect(gen.rng.counter).toBe(3);
90
+ expect(clone.rng.counter).toBe(0);
91
+ });
92
+ });
93
+
94
+ describe('createChild', () => {
95
+ test('child has independent state from parent', () => {
96
+ const gen = createGenerator(7);
97
+ gen.nextFloat();
98
+
99
+ const child = gen.createChild('branch');
100
+ expect(child.rng.counter).toBe(0);
101
+ expect(child.rng.lineage).toEqual(['1:branch']);
102
+ });
103
+
104
+ test('same label from same parent state produces identical child', () => {
105
+ const gen1 = createGenerator(7);
106
+ const gen2 = createGenerator(7);
107
+ gen1.nextFloat();
108
+ gen2.nextFloat();
109
+
110
+ const child1 = gen1.createChild('x');
111
+ const child2 = gen2.createChild('x');
112
+ const values1 = Array.from({ length: 5 }, () => child1.nextFloat());
113
+ const values2 = Array.from({ length: 5 }, () => child2.nextFloat());
114
+ expect(values1).toEqual(values2);
115
+ });
116
+
117
+ test('different labels produce different children', () => {
118
+ const gen = createGenerator(7);
119
+ gen.nextFloat();
120
+
121
+ const childA = gen.createChild('a');
122
+ const childB = gen.createChild('b');
123
+ const valuesA = Array.from({ length: 4 }, () => childA.nextFloat());
124
+ const valuesB = Array.from({ length: 4 }, () => childB.nextFloat());
125
+ expect(valuesA).not.toEqual(valuesB);
126
+ });
127
+ });
128
+
129
+ describe('nextFloat', () => {
130
+ test('returns values in [0, 1)', () => {
131
+ const gen = createGenerator(42);
132
+ for (let i = 0; i < 500; i++) {
133
+ const value = gen.nextFloat();
134
+ expect(value).toBeGreaterThanOrEqual(0);
135
+ expect(value).toBeLessThan(1);
136
+ }
137
+ });
138
+
139
+ test('advances the rng counter', () => {
140
+ const gen = createGenerator(1);
141
+ expect(gen.rng.counter).toBe(0);
142
+ gen.nextFloat();
143
+ expect(gen.rng.counter).toBe(1);
144
+ gen.nextFloat();
145
+ gen.nextFloat();
146
+ expect(gen.rng.counter).toBe(3);
147
+ });
148
+ });
149
+
150
+ describe('nextInt', () => {
151
+ test('returns non-negative integers less than extent', () => {
152
+ const gen = createGenerator(42);
153
+ for (let i = 0; i < 500; i++) {
154
+ const value = gen.nextInt(100);
155
+ expect(value).toBeGreaterThanOrEqual(0);
156
+ expect(value).toBeLessThan(100);
157
+ expect(Number.isInteger(value)).toBe(true);
158
+ }
159
+ });
160
+
161
+ test('returns large non-negative integer when extent is omitted', () => {
162
+ const gen = createGenerator(42);
163
+ const value = gen.nextInt();
164
+ expect(value).toBeGreaterThanOrEqual(0);
165
+ expect(value).toBeLessThan(Number.MAX_SAFE_INTEGER);
166
+ expect(Number.isInteger(value)).toBe(true);
167
+ });
168
+
169
+ test('returns 0 when extent is 0', () => {
170
+ const gen = createGenerator(42);
171
+ expect(gen.nextInt(0)).toBe(0);
172
+ });
173
+
174
+ test('extent of 0 still advances the rng', () => {
175
+ const gen1 = createGenerator(42);
176
+ const gen2 = createGenerator(42);
177
+
178
+ gen1.nextInt(0);
179
+ gen2.nextInt(10);
180
+
181
+ // Both advanced one step, so next values should match
182
+ expect(gen1.nextFloat()).toBe(gen2.nextFloat());
183
+ });
184
+
185
+ test('returns 0 when extent is 1', () => {
186
+ const gen = createGenerator(42);
187
+ for (let i = 0; i < 50; i++) {
188
+ expect(gen.nextInt(1)).toBe(0);
189
+ }
190
+ });
191
+
192
+ test('handles negative extent', () => {
193
+ const gen = createGenerator(42);
194
+ for (let i = 0; i < 500; i++) {
195
+ const value = gen.nextInt(-100);
196
+ expect(value).toBeLessThanOrEqual(0);
197
+ expect(value).toBeGreaterThan(-100);
198
+ expect(Number.isInteger(value)).toBe(true);
199
+ }
200
+ });
201
+
202
+ test('falls back to large range for Infinity extent', () => {
203
+ const gen = createGenerator(42);
204
+ const value = gen.nextInt(Infinity);
205
+ expect(value).toBeGreaterThanOrEqual(0);
206
+ expect(Number.isInteger(value)).toBe(true);
207
+ });
208
+
209
+ test('falls back to large range for NaN extent', () => {
210
+ const gen = createGenerator(42);
211
+ const value = gen.nextInt(NaN);
212
+ expect(value).toBeGreaterThanOrEqual(0);
213
+ expect(Number.isInteger(value)).toBe(true);
214
+ });
215
+
216
+ test('falls back to large range for -Infinity extent', () => {
217
+ const gen = createGenerator(42);
218
+ const value = gen.nextInt(-Infinity);
219
+ expect(value).toBeGreaterThanOrEqual(0);
220
+ expect(Number.isInteger(value)).toBe(true);
221
+ });
222
+ });
223
+
224
+ describe('nextInRange', () => {
225
+ test('returns values in [min, max] range', () => {
226
+ const gen = createGenerator(42);
227
+ for (let i = 0; i < 500; i++) {
228
+ const value = gen.nextInRange(10, 20);
229
+ expect(value).toBeGreaterThanOrEqual(10);
230
+ expect(value).toBeLessThanOrEqual(20);
231
+ expect(Number.isInteger(value)).toBe(true);
232
+ }
233
+ });
234
+
235
+ test('returns min when min equals max', () => {
236
+ const gen = createGenerator(42);
237
+ for (let i = 0; i < 20; i++) {
238
+ expect(gen.nextInRange(5, 5)).toBe(5);
239
+ }
240
+ });
241
+
242
+ test('handles swapped min and max', () => {
243
+ const gen = createGenerator(42);
244
+ for (let i = 0; i < 500; i++) {
245
+ const value = gen.nextInRange(20, 10);
246
+ expect(value).toBeGreaterThanOrEqual(10);
247
+ expect(value).toBeLessThanOrEqual(20);
248
+ expect(Number.isInteger(value)).toBe(true);
249
+ }
250
+ });
251
+
252
+ test('defaults min to 0 when undefined', () => {
253
+ const gen = createGenerator(42);
254
+ for (let i = 0; i < 200; i++) {
255
+ const value = gen.nextInRange(undefined, 10);
256
+ expect(value).toBeGreaterThanOrEqual(0);
257
+ expect(value).toBeLessThanOrEqual(10);
258
+ }
259
+ });
260
+
261
+ test('defaults max to MAX_SAFE_INTEGER when undefined', () => {
262
+ const gen = createGenerator(42);
263
+ const value = gen.nextInRange(0, undefined);
264
+ expect(value).toBeGreaterThanOrEqual(0);
265
+ expect(Number.isInteger(value)).toBe(true);
266
+ });
267
+
268
+ test('both min and max undefined gives full non-negative range', () => {
269
+ const gen = createGenerator(42);
270
+ const value = gen.nextInRange(undefined, undefined);
271
+ expect(value).toBeGreaterThanOrEqual(0);
272
+ expect(Number.isInteger(value)).toBe(true);
273
+ });
274
+
275
+ test('handles negative ranges', () => {
276
+ const gen = createGenerator(42);
277
+ for (let i = 0; i < 500; i++) {
278
+ const value = gen.nextInRange(-20, -10);
279
+ expect(value).toBeGreaterThanOrEqual(-20);
280
+ expect(value).toBeLessThanOrEqual(-10);
281
+ expect(Number.isInteger(value)).toBe(true);
282
+ }
283
+ });
284
+
285
+ test('handles range crossing zero', () => {
286
+ const gen = createGenerator(42);
287
+ for (let i = 0; i < 500; i++) {
288
+ const value = gen.nextInRange(-5, 5);
289
+ expect(value).toBeGreaterThanOrEqual(-5);
290
+ expect(value).toBeLessThanOrEqual(5);
291
+ expect(Number.isInteger(value)).toBe(true);
292
+ }
293
+ });
294
+ });
295
+
296
+ describe('nextBoolean', () => {
297
+ test('returns boolean values with default probability', () => {
298
+ const gen = createGenerator(42);
299
+ let trueCount = 0;
300
+ const trials = 1000;
301
+
302
+ for (let i = 0; i < trials; i++) {
303
+ if (gen.nextBoolean()) {
304
+ trueCount++;
305
+ }
306
+ }
307
+
308
+ // With 0.5 probability over 1000 trials, should be roughly balanced
309
+ expect(trueCount).toBeGreaterThan(350);
310
+ expect(trueCount).toBeLessThan(650);
311
+ });
312
+
313
+ test('returns mostly true with high probability', () => {
314
+ const gen = createGenerator(42);
315
+ let trueCount = 0;
316
+ const trials = 1000;
317
+
318
+ for (let i = 0; i < trials; i++) {
319
+ if (gen.nextBoolean(0.95)) {
320
+ trueCount++;
321
+ }
322
+ }
323
+
324
+ expect(trueCount).toBeGreaterThan(900);
325
+ });
326
+
327
+ test('returns mostly false with low probability', () => {
328
+ const gen = createGenerator(42);
329
+ let trueCount = 0;
330
+ const trials = 1000;
331
+
332
+ for (let i = 0; i < trials; i++) {
333
+ if (gen.nextBoolean(0.05)) {
334
+ trueCount++;
335
+ }
336
+ }
337
+
338
+ expect(trueCount).toBeLessThan(100);
339
+ });
340
+
341
+ test('always returns false with probability 0', () => {
342
+ const gen = createGenerator(42);
343
+ for (let i = 0; i < 100; i++) {
344
+ expect(gen.nextBoolean(0)).toBe(false);
345
+ }
346
+ });
347
+
348
+ test('always returns true with probability 1', () => {
349
+ const gen = createGenerator(42);
350
+ for (let i = 0; i < 100; i++) {
351
+ expect(gen.nextBoolean(1)).toBe(true);
352
+ }
353
+ });
354
+ });
355
+
356
+ describe('nextString', () => {
357
+ test('returns string of requested length', () => {
358
+ const gen = createGenerator(42);
359
+ expect(gen.nextString(10)).toHaveLength(10);
360
+ expect(gen.nextString(0)).toHaveLength(0);
361
+ expect(gen.nextString(100)).toHaveLength(100);
362
+ });
363
+
364
+ test('uses default alphanumeric character set', () => {
365
+ const gen = createGenerator(42);
366
+ const str = gen.nextString(200);
367
+ expect(str).toMatch(/^[A-Za-z0-9]+$/);
368
+ });
369
+
370
+ test('uses custom character set', () => {
371
+ const gen = createGenerator(42);
372
+ const str = gen.nextString(100, 'abc');
373
+ expect(str).toMatch(/^[abc]+$/);
374
+ });
375
+
376
+ test('single character set produces uniform string', () => {
377
+ const gen = createGenerator(42);
378
+ const str = gen.nextString(20, 'X');
379
+ expect(str).toBe('X'.repeat(20));
380
+ });
381
+
382
+ test('same seed produces same string', () => {
383
+ const gen1 = createGenerator(42);
384
+ const gen2 = createGenerator(42);
385
+ expect(gen1.nextString(50)).toBe(gen2.nextString(50));
386
+ });
387
+
388
+ test('empty string length returns empty string', () => {
389
+ const gen = createGenerator(42);
390
+ expect(gen.nextString(0)).toBe('');
391
+ });
392
+ });
393
+
394
+ describe('pickNext', () => {
395
+ test('returns an item from the array', () => {
396
+ const gen = createGenerator(42);
397
+ const items = ['a', 'b', 'c', 'd', 'e'];
398
+ for (let i = 0; i < 100; i++) {
399
+ expect(items).toContain(gen.pickNext(items));
400
+ }
401
+ });
402
+
403
+ test('returns undefined for empty array', () => {
404
+ const gen = createGenerator(42);
405
+ expect(gen.pickNext([])).toBeUndefined();
406
+ });
407
+
408
+ test('returns undefined for undefined input', () => {
409
+ const gen = createGenerator(42);
410
+ expect(gen.pickNext(undefined)).toBeUndefined();
411
+ });
412
+
413
+ test('returns the only item for single-element array', () => {
414
+ const gen = createGenerator(42);
415
+ for (let i = 0; i < 20; i++) {
416
+ expect(gen.pickNext(['only'])).toBe('only');
417
+ }
418
+ });
419
+
420
+ test('is deterministic with same seed', () => {
421
+ const gen1 = createGenerator(42);
422
+ const gen2 = createGenerator(42);
423
+ const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
424
+ const picks1 = Array.from({ length: 20 }, () => gen1.pickNext(items));
425
+ const picks2 = Array.from({ length: 20 }, () => gen2.pickNext(items));
426
+ expect(picks1).toEqual(picks2);
427
+ });
428
+
429
+ test('works with typed arrays', () => {
430
+ const gen = createGenerator(42);
431
+ const numbers = [10, 20, 30];
432
+ const picked = gen.pickNext(numbers);
433
+ expect(picked).toBeDefined();
434
+ expect(numbers).toContain(picked);
435
+ });
436
+ });
437
+
438
+ describe('pickSequential', () => {
439
+ const dictA = ['a1', 'a2', 'a3'];
440
+ const dictB = ['b1', 'b2', 'b3'];
441
+ const dictC = ['c1', 'c2', 'c3'];
442
+
443
+ test('picks one item from each candidate dictionary in order', () => {
444
+ const gen = createGenerator(42);
445
+ const result = gen.pickSequential({
446
+ how: 'sequence',
447
+ candidates: [dictA, dictB, dictC]
448
+ });
449
+
450
+ expect(result).toHaveLength(3);
451
+ expect(dictA).toContain(result[0]);
452
+ expect(dictB).toContain(result[1]);
453
+ expect(dictC).toContain(result[2]);
454
+ });
455
+
456
+ test('defaults count to number of candidate dictionaries', () => {
457
+ const gen = createGenerator(42);
458
+ const result = gen.pickSequential({
459
+ how: 'sequence',
460
+ candidates: [dictA, dictB]
461
+ });
462
+ expect(result).toHaveLength(2);
463
+ });
464
+
465
+ test('wraps around dictionaries when count exceeds candidates', () => {
466
+ const gen = createGenerator(42);
467
+ const result = gen.pickSequential({
468
+ how: 'sequence',
469
+ count: 7,
470
+ candidates: [dictA, dictB, dictC]
471
+ });
472
+
473
+ expect(result).toHaveLength(7);
474
+ // Items 0, 3, 6 come from dictA; 1, 4 from dictB; 2, 5 from dictC
475
+ expect(dictA).toContain(result[0]);
476
+ expect(dictB).toContain(result[1]);
477
+ expect(dictC).toContain(result[2]);
478
+ expect(dictA).toContain(result[3]);
479
+ expect(dictB).toContain(result[4]);
480
+ expect(dictC).toContain(result[5]);
481
+ expect(dictA).toContain(result[6]);
482
+ });
483
+
484
+ test('count of 0 returns empty array', () => {
485
+ const gen = createGenerator(42);
486
+ const result = gen.pickSequential({
487
+ how: 'sequence',
488
+ count: 0,
489
+ candidates: [dictA, dictB]
490
+ });
491
+ expect(result).toEqual([]);
492
+ });
493
+
494
+ test('skips undefined picks from empty candidate dictionaries', () => {
495
+ const gen = createGenerator(42);
496
+ const result = gen.pickSequential({
497
+ how: 'sequence',
498
+ candidates: [dictA, [], dictC]
499
+ });
500
+
501
+ // The empty dictionary yields undefined which is skipped
502
+ expect(result).toHaveLength(2);
503
+ expect(dictA).toContain(result[0]);
504
+ expect(dictC).toContain(result[1]);
505
+ });
506
+
507
+ test('is deterministic with same seed', () => {
508
+ const gen1 = createGenerator(42);
509
+ const gen2 = createGenerator(42);
510
+ const params: ISequentialPickParams<string> = {
511
+ how: 'sequence',
512
+ count: 10,
513
+ candidates: [dictA, dictB, dictC]
514
+ };
515
+ expect(gen1.pickSequential(params)).toEqual(gen2.pickSequential(params));
516
+ });
517
+ });
518
+
519
+ describe('pickRandom', () => {
520
+ const dictA = ['a1', 'a2', 'a3'];
521
+ const dictB = ['b1', 'b2', 'b3'];
522
+ const dictC = ['c1', 'c2', 'c3'];
523
+
524
+ test('picks count items from randomly selected dictionaries', () => {
525
+ const gen = createGenerator(42);
526
+ const result = gen.pickRandom({
527
+ how: 'random',
528
+ count: 10,
529
+ candidates: [dictA, dictB, dictC]
530
+ });
531
+
532
+ expect(result).toHaveLength(10);
533
+ const allItems = [...dictA, ...dictB, ...dictC];
534
+ for (const item of result) {
535
+ expect(allItems).toContain(item);
536
+ }
537
+ });
538
+
539
+ test('count of 0 returns empty array', () => {
540
+ const gen = createGenerator(42);
541
+ const result = gen.pickRandom({
542
+ how: 'random',
543
+ count: 0,
544
+ candidates: [dictA, dictB]
545
+ });
546
+ expect(result).toEqual([]);
547
+ });
548
+
549
+ test('single candidate dictionary picks only from that dictionary', () => {
550
+ const gen = createGenerator(42);
551
+ const result = gen.pickRandom({
552
+ how: 'random',
553
+ count: 20,
554
+ candidates: [dictA]
555
+ });
556
+
557
+ expect(result).toHaveLength(20);
558
+ for (const item of result) {
559
+ expect(dictA).toContain(item);
560
+ }
561
+ });
562
+
563
+ test('is deterministic with same seed', () => {
564
+ const gen1 = createGenerator(42);
565
+ const gen2 = createGenerator(42);
566
+ const params: IRandomSequencePickParams<string> = {
567
+ how: 'random',
568
+ count: 10,
569
+ candidates: [dictA, dictB, dictC]
570
+ };
571
+ expect(gen1.pickRandom(params)).toEqual(gen2.pickRandom(params));
572
+ });
573
+
574
+ test('skips undefined picks from empty candidate dictionaries', () => {
575
+ const gen = createGenerator(42);
576
+ const result = gen.pickRandom({
577
+ how: 'random',
578
+ count: 20,
579
+ candidates: [dictA, [], dictC]
580
+ });
581
+
582
+ // Some picks land on the empty dictionary and are skipped
583
+ expect(result.length).toBeLessThanOrEqual(20);
584
+ expect(result.length).toBeGreaterThan(0);
585
+ const allItems = [...dictA, ...dictC];
586
+ for (const item of result) {
587
+ expect(allItems).toContain(item);
588
+ }
589
+ });
590
+ });
591
+
592
+ describe('pickSequence', () => {
593
+ const dictA = ['a1', 'a2'];
594
+ const dictB = ['b1', 'b2'];
595
+
596
+ test('dispatches to pickSequential for sequence mode', () => {
597
+ const gen = createGenerator(42);
598
+ const result = gen.pickSequence({
599
+ how: 'sequence',
600
+ candidates: [dictA, dictB]
601
+ });
602
+
603
+ expect(result).toHaveLength(2);
604
+ expect(dictA).toContain(result[0]);
605
+ expect(dictB).toContain(result[1]);
606
+ });
607
+
608
+ test('dispatches to pickRandom for random mode', () => {
609
+ const gen = createGenerator(42);
610
+ const result = gen.pickSequence({
611
+ how: 'random',
612
+ count: 5,
613
+ candidates: [dictA, dictB]
614
+ });
615
+
616
+ expect(result).toHaveLength(5);
617
+ const allItems = [...dictA, ...dictB];
618
+ for (const item of result) {
619
+ expect(allItems).toContain(item);
620
+ }
621
+ });
622
+
623
+ test('produces identical results to direct method calls', () => {
624
+ const gen1 = createGenerator(42);
625
+ const gen2 = createGenerator(42);
626
+
627
+ const seqResult = gen1.pickSequence({
628
+ how: 'sequence',
629
+ count: 4,
630
+ candidates: [dictA, dictB]
631
+ });
632
+ const directResult = gen2.pickSequential({
633
+ how: 'sequence',
634
+ count: 4,
635
+ candidates: [dictA, dictB]
636
+ });
637
+ expect(seqResult).toEqual(directResult);
638
+ });
639
+ });
640
+
641
+ describe('global RNG', () => {
642
+ test('setGlobalRng sets and returns the generator', () => {
643
+ const gen = createGenerator(42);
644
+ const result = PseudoRandomGenerator.setGlobalRng(gen);
645
+ expect(result).toBe(gen);
646
+ expect(PseudoRandomGenerator.getGlobalRng()).toBe(gen);
647
+ });
648
+
649
+ test('setGlobalRng with undefined clears the global', () => {
650
+ const gen = createGenerator(42);
651
+ PseudoRandomGenerator.setGlobalRng(gen);
652
+ expect(PseudoRandomGenerator.getGlobalRng()).toBe(gen);
653
+
654
+ PseudoRandomGenerator.setGlobalRng(undefined);
655
+ expect(PseudoRandomGenerator.getGlobalRng()).toBeUndefined();
656
+ });
657
+
658
+ test('getGlobalRng returns undefined when not set', () => {
659
+ expect(PseudoRandomGenerator.getGlobalRng()).toBeUndefined();
660
+ });
661
+ });
662
+
663
+ describe('ensureRng', () => {
664
+ test('returns the provided rng if given', () => {
665
+ const gen = createGenerator(42);
666
+ expect(PseudoRandomGenerator.ensureRng(gen)).toSucceedAndSatisfy((result) => {
667
+ expect(result).toBe(gen);
668
+ });
669
+ });
670
+
671
+ test('returns global rng when no rng is provided and global is set', () => {
672
+ const global = createGenerator(99);
673
+ PseudoRandomGenerator.setGlobalRng(global);
674
+
675
+ expect(PseudoRandomGenerator.ensureRng()).toSucceedAndSatisfy((result) => {
676
+ expect(result).toBe(global);
677
+ });
678
+ });
679
+
680
+ test('creates a new rng when none provided and no global is set', () => {
681
+ expect(PseudoRandomGenerator.ensureRng()).toSucceedAndSatisfy((result) => {
682
+ expect(result).toBeDefined();
683
+ expect(result.rng.counter).toBe(0);
684
+ });
685
+ });
686
+
687
+ test('prefers provided rng over global', () => {
688
+ const global = createGenerator(99);
689
+ const local = createGenerator(42);
690
+ PseudoRandomGenerator.setGlobalRng(global);
691
+
692
+ expect(PseudoRandomGenerator.ensureRng(local)).toSucceedAndSatisfy((result) => {
693
+ expect(result).toBe(local);
694
+ });
695
+ });
696
+ });
697
+ });