mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
283 lines
7.3 KiB
TypeScript
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');
|
|
});
|
|
});
|