Files
Remote-Terminal-for-MeshCore/frontend/src/test/urlHash.test.ts

283 lines
7.3 KiB
TypeScript

/**
* Tests for URL hash utilities.
*
* These tests verify the URL hash parsing and generation
* for deep linking to conversations.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
parseHashConversation,
getMapFocusHash,
resolveChannelFromHashToken,
resolveContactFromHashToken,
} from '../utils/urlHash';
import type { Channel, Contact } from '../types';
describe('parseHashConversation', () => {
let originalHash: string;
beforeEach(() => {
originalHash = window.location.hash;
});
afterEach(() => {
window.location.hash = originalHash;
});
it('returns null for empty hash', () => {
window.location.hash = '';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('parses #raw as raw type', () => {
window.location.hash = '#raw';
const result = parseHashConversation();
expect(result).toEqual({ type: 'raw', name: 'raw' });
});
it('parses #map as map type', () => {
window.location.hash = '#map';
const result = parseHashConversation();
expect(result).toEqual({ type: 'map', name: 'map' });
});
it('parses #map/focus/PUBKEY with focus key', () => {
window.location.hash = '#map/focus/ABCD1234';
const result = parseHashConversation();
expect(result).toEqual({ type: 'map', name: 'map', mapFocusKey: 'ABCD1234' });
});
it('parses #map/focus/ with empty focus as plain map', () => {
window.location.hash = '#map/focus/';
const result = parseHashConversation();
expect(result).toEqual({ type: 'map', name: 'map' });
});
it('decodes URL-encoded map focus key', () => {
window.location.hash = '#map/focus/AB%20CD';
const result = parseHashConversation();
expect(result).toEqual({ type: 'map', name: 'map', mapFocusKey: 'AB CD' });
});
it('parses channel hash', () => {
window.location.hash = '#channel/ABCDEF0123456789ABCDEF0123456789';
const result = parseHashConversation();
expect(result).toEqual({ type: 'channel', name: 'ABCDEF0123456789ABCDEF0123456789' });
});
it('parses contact hash', () => {
window.location.hash =
'#contact/abc123def4567890abc123def4567890abc123def4567890abc123def4567890';
const result = parseHashConversation();
expect(result).toEqual({
type: 'contact',
name: 'abc123def4567890abc123def4567890abc123def4567890abc123def4567890',
});
});
it('parses id plus readable label and preserves id token', () => {
window.location.hash = '#channel/ABCDEF0123456789ABCDEF0123456789/Public%20Room';
const result = parseHashConversation();
expect(result).toEqual({
type: 'channel',
name: 'ABCDEF0123456789ABCDEF0123456789',
label: 'Public Room',
});
});
it('decodes URL-encoded names', () => {
window.location.hash = '#contact/John%20Doe';
const result = parseHashConversation();
expect(result).toEqual({ type: 'contact', name: 'John Doe' });
});
it('returns null for invalid type', () => {
window.location.hash = '#invalid/Test';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('returns null for hash without slash', () => {
window.location.hash = '#channelPublic';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('returns null for hash with empty name', () => {
window.location.hash = '#channel/';
const result = parseHashConversation();
expect(result).toBeNull();
});
it('handles channel names with special characters', () => {
window.location.hash = '#channel/Test%20Channel%21';
const result = parseHashConversation();
expect(result).toEqual({ type: 'channel', name: 'Test Channel!' });
});
});
describe('resolveChannelFromHashToken', () => {
const channels: Channel[] = [
{
key: 'ABCDEF0123456789ABCDEF0123456789',
name: 'Public',
is_hashtag: false,
on_radio: true,
last_read_at: null,
},
{
key: '11111111111111111111111111111111',
name: '#mesh',
is_hashtag: true,
on_radio: false,
last_read_at: null,
},
{
key: '22222222222222222222222222222222',
name: 'Public',
is_hashtag: false,
on_radio: false,
last_read_at: null,
},
];
it('prefers stable key lookup (case-insensitive)', () => {
const result = resolveChannelFromHashToken('abcdef0123456789abcdef0123456789', channels);
expect(result?.key).toBe('ABCDEF0123456789ABCDEF0123456789');
});
it('supports legacy name-based hash lookup', () => {
const result = resolveChannelFromHashToken('Public', channels);
expect(result?.key).toBe('ABCDEF0123456789ABCDEF0123456789');
});
it('supports legacy hashtag hash without leading #', () => {
const result = resolveChannelFromHashToken('mesh', channels);
expect(result?.key).toBe('11111111111111111111111111111111');
});
});
describe('resolveContactFromHashToken', () => {
const contacts: Contact[] = [
{
public_key: 'abc123def4567890abc123def4567890abc123def4567890abc123def4567890',
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
last_seen: null,
on_radio: false,
last_contacted: null,
last_read_at: null,
first_seen: null,
},
{
public_key: 'def456abc1237890def456abc1237890def456abc1237890def456abc1237890',
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
last_seen: null,
on_radio: false,
last_contacted: null,
last_read_at: null,
first_seen: null,
},
{
public_key: 'eeeeee111111222222333333444444555555666666777777888888999999aaaa',
name: null,
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
last_seen: null,
on_radio: false,
last_contacted: null,
last_read_at: null,
first_seen: null,
},
];
it('prefers stable public-key lookup (case-insensitive)', () => {
const result = resolveContactFromHashToken(
'ABC123DEF4567890ABC123DEF4567890ABC123DEF4567890ABC123DEF4567890',
contacts
);
expect(result?.public_key).toBe(
'abc123def4567890abc123def4567890abc123def4567890abc123def4567890'
);
});
it('supports legacy display-name hash lookup', () => {
const result = resolveContactFromHashToken('Alice', contacts);
expect(result?.public_key).toBe(
'abc123def4567890abc123def4567890abc123def4567890abc123def4567890'
);
});
it('supports legacy unnamed-contact prefix hash lookup', () => {
const result = resolveContactFromHashToken('eeeeee111111', contacts);
expect(result?.public_key).toBe(
'eeeeee111111222222333333444444555555666666777777888888999999aaaa'
);
});
});
describe('getMapFocusHash', () => {
it('generates hash with focus key', () => {
const result = getMapFocusHash('ABCD1234');
expect(result).toBe('#map/focus/ABCD1234');
});
it('encodes special characters in key', () => {
const result = getMapFocusHash('AB CD/12');
expect(result).toBe('#map/focus/AB%20CD%2F12');
});
});