From b0bc41291efbab251fae14e3300293e3f66556e8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 5 Apr 2026 13:29:41 +0200 Subject: [PATCH] feat(helpers): add deterministic stringHash for stable daily selection --- frontend/src/helpers/stringHash.test.ts | 21 +++++++++++++++++++++ frontend/src/helpers/stringHash.ts | 11 +++++++++++ 2 files changed, 32 insertions(+) create mode 100644 frontend/src/helpers/stringHash.test.ts create mode 100644 frontend/src/helpers/stringHash.ts diff --git a/frontend/src/helpers/stringHash.test.ts b/frontend/src/helpers/stringHash.test.ts new file mode 100644 index 000000000..3626acf0c --- /dev/null +++ b/frontend/src/helpers/stringHash.test.ts @@ -0,0 +1,21 @@ +import {describe, it, expect} from 'vitest' +import {stringHash} from './stringHash' + +describe('stringHash', () => { + it('returns a non-negative integer', () => { + expect(stringHash('hello')).toBeGreaterThanOrEqual(0) + expect(Number.isInteger(stringHash('hello'))).toBe(true) + }) + + it('is deterministic for the same input', () => { + expect(stringHash('foo')).toBe(stringHash('foo')) + }) + + it('returns different values for different inputs', () => { + expect(stringHash('foo')).not.toBe(stringHash('bar')) + }) + + it('handles the empty string', () => { + expect(stringHash('')).toBeGreaterThanOrEqual(0) + }) +}) diff --git a/frontend/src/helpers/stringHash.ts b/frontend/src/helpers/stringHash.ts new file mode 100644 index 000000000..b843b1fbe --- /dev/null +++ b/frontend/src/helpers/stringHash.ts @@ -0,0 +1,11 @@ +// Deterministic non-cryptographic string hash (djb2 variant). +// Used for stable pseudo-random selection keyed on date + user + bucket. +export function stringHash(input: string): number { + let hash = 5381 + for (let i = 0; i < input.length; i++) { + // hash * 33 + char, kept in 32-bit range via `| 0`. + hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0 + } + // Ensure non-negative. + return hash >>> 0 +}