/ src / lib / shared-utils.test.ts
shared-utils.test.ts
  1  import assert from 'node:assert/strict'
  2  import { describe, it } from 'node:test'
  3  
  4  import {
  5    errorMessage,
  6    safeJsonParse,
  7    truncate,
  8    hmrSingleton,
  9    dedup,
 10    dedupBy,
 11    sleep,
 12  } from './shared-utils'
 13  
 14  describe('errorMessage', () => {
 15    it('extracts message from Error', () => {
 16      assert.equal(errorMessage(new Error('boom')), 'boom')
 17    })
 18    it('converts string to string', () => {
 19      assert.equal(errorMessage('fail'), 'fail')
 20    })
 21    it('converts number to string', () => {
 22      assert.equal(errorMessage(42), '42')
 23    })
 24    it('converts null to string', () => {
 25      assert.equal(errorMessage(null), 'null')
 26    })
 27    it('converts undefined to string', () => {
 28      assert.equal(errorMessage(undefined), 'undefined')
 29    })
 30    it('handles TypeError subclass', () => {
 31      assert.equal(errorMessage(new TypeError('bad type')), 'bad type')
 32    })
 33  })
 34  
 35  describe('safeJsonParse', () => {
 36    it('parses valid JSON', () => {
 37      assert.deepEqual(safeJsonParse('{"a":1}', null), { a: 1 })
 38    })
 39    it('returns fallback for invalid JSON', () => {
 40      assert.equal(safeJsonParse('not json', 'default'), 'default')
 41    })
 42    it('returns fallback for empty string', () => {
 43      assert.deepEqual(safeJsonParse('', []), [])
 44    })
 45    it('parses arrays', () => {
 46      assert.deepEqual(safeJsonParse('[1,2,3]', []), [1, 2, 3])
 47    })
 48    it('parses null literal', () => {
 49      assert.equal(safeJsonParse('null', 'fallback'), null)
 50    })
 51  })
 52  
 53  describe('truncate', () => {
 54    it('returns short strings unchanged', () => {
 55      assert.equal(truncate('hello', 10), 'hello')
 56    })
 57    it('truncates long strings', () => {
 58      assert.equal(truncate('hello world', 5), 'hello')
 59    })
 60    it('truncates with suffix', () => {
 61      assert.equal(truncate('hello world', 8, '…'), 'hello w…')
 62    })
 63    it('handles exact limit', () => {
 64      assert.equal(truncate('hello', 5), 'hello')
 65    })
 66    it('handles zero limit', () => {
 67      assert.equal(truncate('hello', 0), '')
 68    })
 69    it('handles suffix longer than limit gracefully', () => {
 70      assert.equal(truncate('hello world', 2, '...'), '...')
 71    })
 72    it('empty string unchanged', () => {
 73      assert.equal(truncate('', 10), '')
 74    })
 75  })
 76  
 77  describe('hmrSingleton', () => {
 78    it('creates and returns a value', () => {
 79      const val = hmrSingleton('__test_hmr_1__', () => ({ count: 0 }))
 80      assert.deepEqual(val, { count: 0 })
 81    })
 82    it('returns same instance on second call', () => {
 83      const a = hmrSingleton('__test_hmr_2__', () => ({ id: Math.random() }))
 84      const b = hmrSingleton('__test_hmr_2__', () => ({ id: Math.random() }))
 85      assert.equal(a, b)
 86      assert.equal(a.id, b.id)
 87    })
 88    it('creates different instances for different keys', () => {
 89      const a = hmrSingleton('__test_hmr_3a__', () => 'a')
 90      const b = hmrSingleton('__test_hmr_3b__', () => 'b')
 91      assert.notEqual(a, b)
 92    })
 93  })
 94  
 95  describe('dedup', () => {
 96    it('removes duplicates', () => {
 97      assert.deepEqual(dedup([1, 2, 2, 3, 1]), [1, 2, 3])
 98    })
 99    it('preserves order', () => {
100      assert.deepEqual(dedup(['b', 'a', 'b', 'c']), ['b', 'a', 'c'])
101    })
102    it('handles empty array', () => {
103      assert.deepEqual(dedup([]), [])
104    })
105    it('handles all unique', () => {
106      assert.deepEqual(dedup([1, 2, 3]), [1, 2, 3])
107    })
108  })
109  
110  describe('dedupBy', () => {
111    it('deduplicates by key function', () => {
112      const items = [
113        { id: '1', name: 'a' },
114        { id: '2', name: 'b' },
115        { id: '1', name: 'c' },
116      ]
117      const result = dedupBy(items, (i) => i.id)
118      assert.equal(result.length, 2)
119      assert.equal(result[0].name, 'a')
120      assert.equal(result[1].name, 'b')
121    })
122    it('keeps first occurrence', () => {
123      const result = dedupBy(['hello', 'HELLO', 'world'], (s) => s.toLowerCase())
124      assert.deepEqual(result, ['hello', 'world'])
125    })
126    it('handles empty array', () => {
127      assert.deepEqual(dedupBy([], (x) => String(x)), [])
128    })
129  })
130  
131  describe('sleep', () => {
132    it('resolves after delay', async () => {
133      const start = Date.now()
134      await sleep(50)
135      const elapsed = Date.now() - start
136      assert.ok(elapsed >= 40, `Expected ≥40ms, got ${elapsed}ms`)
137    })
138    it('resolves with undefined', async () => {
139      const result = await sleep(1)
140      assert.equal(result, undefined)
141    })
142  })