[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft/
0

Configure Feed

Select the types of activity you want to include in your feed.

shapecraft / tests / rng.test.ts
3.7 kB 123 lines
1import { describe, it, expect } from 'vitest' 2import { createRng } from '../src/core/rng' 3 4describe('createRng', () => { 5 it('is callable and returns floats in [0, 1)', () => { 6 const rng = createRng(1) 7 for (let i = 0; i < 100; i++) { 8 const v = rng() 9 expect(v).toBeGreaterThanOrEqual(0) 10 expect(v).toBeLessThan(1) 11 } 12 }) 13 14 it('is deterministic for the same seed', () => { 15 const a = createRng(42) 16 const b = createRng(42) 17 const seqA = Array.from({ length: 10 }, () => a()) 18 const seqB = Array.from({ length: 10 }, () => b()) 19 expect(seqA).toEqual(seqB) 20 }) 21 22 it('differs across seeds', () => { 23 const a = createRng(1) 24 const b = createRng(2) 25 expect(a()).not.toEqual(b()) 26 }) 27 28 it('accepts string seeds', () => { 29 const a = createRng('hello') 30 const b = createRng('hello') 31 expect(a()).toEqual(b()) 32 }) 33}) 34 35describe('Rng helpers', () => { 36 it('int() is inclusive and within bounds', () => { 37 const rng = createRng(7) 38 const seen = new Set<number>() 39 for (let i = 0; i < 1000; i++) { 40 const v = rng.int(1, 6) 41 expect(Number.isInteger(v)).toBe(true) 42 expect(v).toBeGreaterThanOrEqual(1) 43 expect(v).toBeLessThanOrEqual(6) 44 seen.add(v) 45 } 46 // Should eventually hit both ends of the inclusive range 47 expect(seen.has(1)).toBe(true) 48 expect(seen.has(6)).toBe(true) 49 }) 50 51 it('float(min, max) stays within bounds', () => { 52 const rng = createRng(3) 53 for (let i = 0; i < 100; i++) { 54 const v = rng.float(5, 10) 55 expect(v).toBeGreaterThanOrEqual(5) 56 expect(v).toBeLessThan(10) 57 } 58 }) 59 60 it('range() passes through fixed values and resolves tuples', () => { 61 const rng = createRng(9) 62 expect(rng.range(2.5)).toBe(2.5) 63 const v = rng.range([0, 4]) 64 expect(v).toBeGreaterThanOrEqual(0) 65 expect(v).toBeLessThanOrEqual(4) 66 const n = rng.range([0, 4], true) 67 expect(Number.isInteger(n)).toBe(true) 68 }) 69 70 it('pick() returns an element of the array', () => { 71 const rng = createRng(11) 72 const arr = ['a', 'b', 'c'] 73 for (let i = 0; i < 50; i++) { 74 expect(arr).toContain(rng.pick(arr)) 75 } 76 }) 77 78 it('seed() produces deterministic integer seeds', () => { 79 const a = createRng(5) 80 const b = createRng(5) 81 expect(a.seed()).toBe(b.seed()) 82 expect(Number.isInteger(a.seed())).toBe(true) 83 }) 84}) 85 86describe('named streams', () => { 87 it('a stream is deterministic for the same name', () => { 88 const rootA = createRng(1) 89 const rootB = createRng(1) 90 const seqA = Array.from({ length: 5 }, () => rootA.stream('canopy')()) 91 // Re-derive each time — same name + same root => same first value 92 expect(rootB.stream('canopy')()).toEqual(rootA.stream('canopy')()) 93 expect(seqA.every((v) => v >= 0 && v < 1)).toBe(true) 94 }) 95 96 it('different stream names are independent', () => { 97 const root = createRng(1) 98 expect(root.stream('a')()).not.toEqual(root.stream('b')()) 99 }) 100 101 it('consuming one stream does not perturb another (the key property)', () => { 102 // Baseline: read canopy without touching snow at all. 103 const root1 = createRng(99) 104 const canopy1 = root1.stream('canopy') 105 const baseline = [canopy1(), canopy1(), canopy1()] 106 107 // Now heavily consume an unrelated stream first, then read canopy. 108 const root2 = createRng(99) 109 const snow2 = root2.stream('snow') 110 for (let i = 0; i < 50; i++) snow2() 111 const canopy2 = root2.stream('canopy') 112 const after = [canopy2(), canopy2(), canopy2()] 113 114 expect(after).toEqual(baseline) 115 }) 116 117 it('fork() yields independent anonymous streams', () => { 118 const root = createRng(1) 119 const f1 = root.fork() 120 const f2 = root.fork() 121 expect(f1()).not.toEqual(f2()) 122 }) 123})