Extract the leaderboard into a generic, backend-agnostic package and add rolling Today/Week/Month/All-Time windows, a web component, and local-first development against the Firestore emulator. Package (leaderboard/): - LeaderBoardService: backend-agnostic core via a storage-adapter seam, with Firebase and Supabase adapters (Supabase client injected, no added dep) - Rolling time windows (last 24h / 7d / 30d) with hover tooltips; top-N by score - <cozy-leaderboard> web component built on web-component-base: compose the UI in HTML, configure the backend once in JS - Every user-facing string is configurable (labels, tooltips, empty/loading/ error messages, anonymous name) so i18n lives in the app; README + CONFIGURATION App (mnswpr.com): - Compose the board declaratively in index.html via <cozy-leaderboard> - Nickname + randomized greeting bar; score submission through the element - Legends: the current all-time leaders frozen into a static /legends page - Firebase config and leaderboard namespace via Vite env vars; emulator-first local dev (VITE_FIRESTORE_EMULATOR) Firebase schema-as-code: - firebase.json, .firebaserc, firestore.rules (public reads, create-only scores, no client updates/deletes, namespace-generalized), empty indexes (rolling windows need none) - prod (mw-*) vs dev/test (mw-test-*) separation by collection namespace - emulator config, seed script, and docs (firebase-leaderboards.md, leaderboard-env-migration.md, AYO.md) Utils/tests: UTC date-bucket helper (retained as metadata) with Vitest coverage.
41 lines
1.5 KiB
JavaScript
41 lines
1.5 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import { dayKey, weekKey, monthKey, buckets } from '../utils/date-bucket/date-bucket.js'
|
|
|
|
describe('date-bucket', () => {
|
|
it('builds a zero-padded UTC day key', () => {
|
|
expect(dayKey(new Date('2026-07-03T12:00:00Z'))).toBe('2026-07-03')
|
|
expect(dayKey(new Date('2026-01-09T00:00:00Z'))).toBe('2026-01-09')
|
|
})
|
|
|
|
it('builds a UTC month key', () => {
|
|
expect(monthKey(new Date('2026-07-03T12:00:00Z'))).toBe('2026-07')
|
|
expect(monthKey(new Date('2026-12-31T23:59:59Z'))).toBe('2026-12')
|
|
})
|
|
|
|
it('builds an ISO week key', () => {
|
|
// 2026-07-03 falls in ISO week 27
|
|
expect(weekKey(new Date('2026-07-03T12:00:00Z'))).toBe('2026-W27')
|
|
// 2026-01-01 is a Thursday -> ISO week 1 of 2026
|
|
expect(weekKey(new Date('2026-01-01T00:00:00Z'))).toBe('2026-W01')
|
|
})
|
|
|
|
it('rolls the ISO week-year back at the January boundary', () => {
|
|
// 2027-01-01 is a Friday -> still ISO week 53 of 2026
|
|
expect(weekKey(new Date('2027-01-01T00:00:00Z'))).toBe('2026-W53')
|
|
})
|
|
|
|
it('uses UTC, not local time', () => {
|
|
// Just before UTC midnight is still the 3rd in UTC
|
|
expect(dayKey(new Date('2026-07-03T23:59:59Z'))).toBe('2026-07-03')
|
|
// Just after is the 4th
|
|
expect(dayKey(new Date('2026-07-04T00:00:01Z'))).toBe('2026-07-04')
|
|
})
|
|
|
|
it('returns all three keys together', () => {
|
|
expect(buckets(new Date('2026-07-03T12:00:00Z'))).toEqual({
|
|
day: '2026-07-03',
|
|
week: '2026-W27',
|
|
month: '2026-07'
|
|
})
|
|
})
|
|
})
|