feat(devices): pure icon resolver + relativeTime helpers
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
24
public/views/icon_util.js
Normal file
24
public/views/icon_util.js
Normal file
@@ -0,0 +1,24 @@
|
||||
// public/views/icon_util.js — pure helpers (no DOM), unit-tested.
|
||||
const GROUP_DEFAULT = {
|
||||
Network: 'router', Entertainment: 'tv', 'Smart Home': 'plug', Personal: 'phone'
|
||||
};
|
||||
export function autoDefaultIcon(grp) {
|
||||
return `set:devices:${GROUP_DEFAULT[grp] || 'unknown'}`;
|
||||
}
|
||||
// Note: bundled 'devices' icons are .svg; brand icons are served .png by the proxy.
|
||||
export function resolveIcon(ref) {
|
||||
if (typeof ref !== 'string') return null;
|
||||
let m = ref.match(/^set:([a-z0-9-]+):([a-z0-9-]+)$/);
|
||||
if (m) return `/api/icon-sets/${m[1]}/${m[2]}.svg`;
|
||||
m = ref.match(/^brand:([a-z0-9-]+)$/);
|
||||
if (m) return `/api/icons/${m[1]}.png`;
|
||||
return null;
|
||||
}
|
||||
export function relativeTime(iso, now = Date.now()) {
|
||||
const t = typeof iso === 'number' ? iso : Date.parse(iso);
|
||||
const s = Math.max(0, Math.floor((now - t) / 1000));
|
||||
if (s < 60) return 'just now';
|
||||
if (s < 3600) return `${Math.floor(s / 60)}m ago`;
|
||||
if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
|
||||
return `${Math.floor(s / 86400)}d ago`;
|
||||
}
|
||||
29
tests/views/icon_util.test.js
Normal file
29
tests/views/icon_util.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { resolveIcon, relativeTime, autoDefaultIcon } from '../../public/views/icon_util.js';
|
||||
|
||||
describe('autoDefaultIcon', () => {
|
||||
it('maps groups to bundled icons', () => {
|
||||
expect(autoDefaultIcon('Network')).toBe('set:devices:router');
|
||||
expect(autoDefaultIcon('Entertainment')).toBe('set:devices:tv');
|
||||
expect(autoDefaultIcon('Smart Home')).toBe('set:devices:plug');
|
||||
expect(autoDefaultIcon('Personal')).toBe('set:devices:phone');
|
||||
expect(autoDefaultIcon('whatever')).toBe('set:devices:unknown');
|
||||
});
|
||||
});
|
||||
describe('resolveIcon', () => {
|
||||
it('resolves set + brand refs', () => {
|
||||
expect(resolveIcon('set:devices:router')).toBe('/api/icon-sets/devices/router.svg');
|
||||
expect(resolveIcon('set:mine:nas')).toBe('/api/icon-sets/mine/nas.svg');
|
||||
expect(resolveIcon('brand:apple')).toBe('/api/icons/apple.png');
|
||||
});
|
||||
it('returns null for junk', () => { expect(resolveIcon('nope')).toBeNull(); });
|
||||
});
|
||||
describe('relativeTime', () => {
|
||||
const base = Date.parse('2026-06-09T12:00:00Z');
|
||||
it('formats buckets', () => {
|
||||
expect(relativeTime('2026-06-09T11:59:30Z', base)).toBe('just now');
|
||||
expect(relativeTime('2026-06-09T11:40:00Z', base)).toBe('20m ago');
|
||||
expect(relativeTime('2026-06-09T09:00:00Z', base)).toBe('3h ago');
|
||||
expect(relativeTime('2026-06-06T12:00:00Z', base)).toBe('3d ago');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user