Something went wrong. Try again.
Vibed Google Timeline alternative (VIBED)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134import { describe, expect, it } from 'vitest'import { FIX_LAYERS, dayLayers, emphasisRange, fixRadius, hiddenByFix, rangeSlice, selectionStyle, trackFeatures,} from './mapStyle'import { MINUTE } from './time'
const pts = [10, 20, 30, 40].map((t) => ({ t }))
describe('emphasisRange', () => { it('keeps a range that holds a fix', () => { expect(emphasisRange(pts, { start: 15, end: 25 })).toEqual({ start: 15, end: 25 }) expect(emphasisRange(pts, { start: 20, end: 20 })).toEqual({ start: 20, end: 20 }) expect(emphasisRange(pts, { start: 0, end: 10 })).toEqual({ start: 0, end: 10 }) })
it('widens a range without fixes to the fixes around it', () => { expect(emphasisRange(pts, { start: 22, end: 28 })).toEqual({ start: 20, end: 30 }) })
it('widens only towards the side that has a fix', () => { expect(emphasisRange(pts, { start: 0, end: 5 })).toEqual({ start: 0, end: 10 }) expect(emphasisRange(pts, { start: 45, end: 50 })).toEqual({ start: 40, end: 50 }) })
it('keeps the range when there are no fixes', () => { expect(emphasisRange([], { start: 1, end: 2 })).toEqual({ start: 1, end: 2 }) })})
describe('rangeSlice', () => { const dup = [10, 20, 20, 30].map((t) => ({ t }))
it('bounds the points inside the range, ends included', () => { expect(rangeSlice(pts, { start: 20, end: 30 })).toEqual([1, 3]) expect(rangeSlice(pts, { start: 15, end: 35 })).toEqual([1, 3]) expect(rangeSlice(dup, { start: 20, end: 20 })).toEqual([1, 3]) expect(rangeSlice(pts, { start: 0, end: 100 })).toEqual([0, 4]) })
it('is empty for a range without points', () => { const [i, j] = rangeSlice(pts, { start: 22, end: 28 }) expect(j - i).toBe(0) expect(rangeSlice(pts, { start: 50, end: 60 })).toEqual([4, 4]) expect(rangeSlice([], { start: 1, end: 2 })).toEqual([0, 0]) })
it('takes the fixes around an empty range once widened', () => { expect(rangeSlice(pts, emphasisRange(pts, { start: 22, end: 28 }))).toEqual([1, 3]) })})
describe('selectionStyle', () => { it('sets the same constant properties with and without a range', () => { const on = selectionStyle(true) const off = selectionStyle(false) expect(on.map((o) => [o.layer, o.property])).toEqual(off.map((o) => [o.layer, o.property])) for (const o of [...on, ...off]) expect(typeof o.value).toBe('number') })
it('dims the base layers while a range is emphasized', () => { const off = selectionStyle(false) selectionStyle(true).forEach((o, i) => expect(o.value).toBeLessThan(off[i].value)) })})
describe('dayLayers', () => { it('draws each emphasized layer above its base layer, with the base paint undimmed', () => { const layers = dayLayers('light') const ids = layers.map((l) => l.id) for (const base of ['track-gap', 'track', 'mock', 'fixes']) { const at = ids.indexOf(base) const emphasis = layers[ids.indexOf(`${base}-emphasis`)] expect(ids.indexOf(emphasis.id)).toBeGreaterThan(at) expect('paint' in emphasis && emphasis.paint).toEqual('paint' in layers[at] && layers[at].paint) } for (const o of selectionStyle(false)) { const layer = layers[ids.indexOf(o.layer)] expect('paint' in layer && (layer.paint as Record<string, unknown>)[o.property]).toBe(o.value) } })
it('has no data-driven layout', () => { for (const l of dayLayers('dark')) for (const v of Object.values(l.layout ?? {})) expect(Array.isArray(v)).toBe(false) })
it('lists the fix layers topmost first', () => { const ids = dayLayers('light').map((l) => l.id) expect(ids.indexOf(FIX_LAYERS[0])).toBeGreaterThan(ids.indexOf(FIX_LAYERS[1])) })})
describe('fixRadius', () => { it('interpolates between zoom 8 and 16 and clamps outside', () => { expect(fixRadius(4)).toBe(2.5) expect(fixRadius(8)).toBe(2.5) expect(fixRadius(12)).toBe(3.75) expect(fixRadius(16)).toBe(5) expect(fixRadius(20)).toBe(5) })
it('hides circles that stay inside the fill with their outline', () => { expect(hiddenByFix(12, 2.75)).toBe(true) expect(hiddenByFix(12, 2.8)).toBe(false) expect(hiddenByFix(16, 4)).toBe(true) })})
describe('trackFeatures', () => { const at = (t: number, i: number) => ({ t: t * MINUTE, lat: i, lon: i })
it('joins consecutive fixes into one line per run and gives each gap its own line', () => { const fc = trackFeatures([at(0, 0), at(1, 1), at(2, 2), at(30, 3), at(31, 4), at(60, 5), at(90, 6)]) expect(fc.features.map((f) => [f.properties.gap, f.geometry.coordinates.map(([x]) => x)])).toEqual([ [false, [0, 1, 2]], [true, [2, 3]], [false, [3, 4]], [true, [4, 5]], [true, [5, 6]], ]) })
it('draws nothing for fewer than two fixes', () => { expect(trackFeatures([]).features).toEqual([]) expect(trackFeatures([at(0, 0)]).features).toEqual([]) })})