Add draft reactions + gifs; region resolution

This commit is contained in:
Jack Kingsman
2026-06-20 21:12:47 -07:00
parent cb4d4ca584
commit 06556a853d
35 changed files with 1111 additions and 44 deletions
+1
View File
@@ -222,6 +222,7 @@ const baseSettings = {
advert_interval: 0,
last_advert_time: 0,
flood_scope: '',
known_regions: [],
blocked_keys: [],
blocked_names: [],
};
+2
View File
@@ -105,6 +105,7 @@ beforeEach(() => {
advert_interval: 0,
last_advert_time: 0,
flood_scope: '',
known_regions: [],
blocked_keys: [],
blocked_names: [],
discovery_blocked_types: [],
@@ -1147,6 +1148,7 @@ describe('SettingsFanoutSection', () => {
advert_interval: 0,
last_advert_time: 0,
flood_scope: '',
known_regions: [],
blocked_keys: [],
blocked_names: [],
discovery_blocked_types: [],
@@ -0,0 +1,81 @@
/**
* Tests for MeshCore Open rich-chat payload parsing (GIFs and reactions).
*
* Formats are ported from meshcore-open; see meshcoreOpenPayloads.ts.
*/
import { describe, it, expect } from 'vitest';
import {
REACTION_EMOJIS,
giphyUrlForId,
parseGif,
parseReaction,
} from '../utils/meshcoreOpenPayloads';
describe('parseGif', () => {
it('parses a g:<id> payload', () => {
expect(parseGif('g:abc123')).toBe('abc123');
});
it('accepts ids with underscores and dashes', () => {
expect(parseGif('g:aB3_-xY')).toBe('aB3_-xY');
});
it('trims surrounding whitespace', () => {
expect(parseGif(' g:abc123 ')).toBe('abc123');
});
it('returns null for non-gif text', () => {
expect(parseGif('hello world')).toBeNull();
expect(parseGif('g:')).toBeNull();
expect(parseGif('g:abc 123')).toBeNull();
expect(parseGif('prefix g:abc')).toBeNull();
expect(parseGif('g:abc!')).toBeNull();
});
it('builds the Giphy media URL', () => {
expect(giphyUrlForId('abc123')).toBe('https://media.giphy.com/media/abc123/giphy.gif');
});
});
describe('parseReaction', () => {
it('decodes the first emoji (index 00)', () => {
const result = parseReaction('r:1a2b:00');
expect(result).toEqual({ emoji: REACTION_EMOJIS[0], targetHash: '1a2b' });
expect(result?.emoji).toBe('👍');
});
it('decodes a non-zero index', () => {
// index 0x06 -> first smiley (after the 6 quick emojis)
const result = parseReaction('r:ffff:06');
expect(result?.emoji).toBe(REACTION_EMOJIS[6]);
expect(result?.targetHash).toBe('ffff');
});
it('trims surrounding whitespace', () => {
expect(parseReaction(' r:1a2b:00 ')?.emoji).toBe('👍');
});
it('returns null for an out-of-range index', () => {
// 0xff (255) is beyond the emoji list length
expect(parseReaction('r:1a2b:ff')).toBeNull();
});
it('returns null for malformed reactions', () => {
expect(parseReaction('r:1a2b')).toBeNull();
expect(parseReaction('r:1a2:00')).toBeNull(); // hash too short
expect(parseReaction('r:1A2B:00')).toBeNull(); // uppercase hex not accepted
expect(parseReaction('r:1a2b:0')).toBeNull(); // index too short
expect(parseReaction('hello')).toBeNull();
});
it('exposes a stable, deduplication-free emoji index range', () => {
// 6 quick + 64 smileys + 33 gestures + 32 hearts + 49 objects
expect(REACTION_EMOJIS.length).toBe(184);
// every defined index decodes to a string
for (let i = 0; i < REACTION_EMOJIS.length; i++) {
const hex = i.toString(16).padStart(2, '0');
expect(parseReaction(`r:0000:${hex}`)?.emoji).toBe(REACTION_EMOJIS[i]);
}
});
});
+25
View File
@@ -62,6 +62,31 @@ describe('MessageList channel sender rendering', () => {
expect(screen.getByTestId('corrupt-avatar')).toBeInTheDocument();
});
it('renders a region badge for region-scoped channel messages', () => {
render(
<MessageList
messages={[createMessage({ sender_name: 'Alice', region: 'nl-gr' })]}
contacts={[]}
loading={false}
/>
);
expect(screen.getByText('nl-gr')).toBeInTheDocument();
expect(screen.getByTitle('Regional scope: nl-gr')).toBeInTheDocument();
});
it('does not render a region badge for unscoped messages', () => {
render(
<MessageList
messages={[createMessage({ sender_name: 'Alice', region: null })]}
contacts={[]}
loading={false}
/>
);
expect(screen.queryByText('nl-gr')).not.toBeInTheDocument();
});
it('prefers stored sender_name for channel messages even when text is not sender-prefixed', () => {
render(
<MessageList
@@ -91,11 +91,26 @@ describe('RawPacketDetailModal', () => {
expect(pathRun.className).toBe(idleClassName);
});
it('shows scope card with transport codes for scoped packets', () => {
it('shows scope card with transport codes for scoped packets without a resolved region', () => {
render(<RawPacketDetailModal packet={SCOPED_PACKET} channels={[]} onClose={vi.fn()} />);
expect(screen.getByText('Scope')).toBeInTheDocument();
expect(screen.getByText('Regional')).toBeInTheDocument();
expect(screen.getByText('0x1234, 0x5678 · unknown region')).toBeInTheDocument();
});
it('shows the resolved region name in the scope card when the backend matched one', () => {
render(
<RawPacketDetailModal
packet={{ ...SCOPED_PACKET, region: 'nl-gr', transport_code: 0x1234 }}
channels={[]}
onClose={vi.fn()}
/>
);
expect(screen.getByText('Scope')).toBeInTheDocument();
expect(screen.getByText('nl-gr')).toBeInTheDocument();
// Raw codes remain visible as the secondary detail.
expect(screen.getByText('0x1234, 0x5678')).toBeInTheDocument();
});
@@ -0,0 +1,35 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
RENDER_RICH_PAYLOADS_KEY,
getSavedRenderRichPayloads,
setSavedRenderRichPayloads,
} from '../utils/richPayloadPreference';
describe('richPayloadPreference utilities', () => {
beforeEach(() => {
localStorage.clear();
});
it('defaults to off when unset', () => {
expect(getSavedRenderRichPayloads()).toBe(false);
});
it('returns true when enabled', () => {
localStorage.setItem(RENDER_RICH_PAYLOADS_KEY, 'true');
expect(getSavedRenderRichPayloads()).toBe(true);
});
it('treats any non-"true" value as off', () => {
localStorage.setItem(RENDER_RICH_PAYLOADS_KEY, 'yes');
expect(getSavedRenderRichPayloads()).toBe(false);
});
it('persists when enabled and clears the key when disabled', () => {
setSavedRenderRichPayloads(true);
expect(localStorage.getItem(RENDER_RICH_PAYLOADS_KEY)).toBe('true');
setSavedRenderRichPayloads(false);
expect(localStorage.getItem(RENDER_RICH_PAYLOADS_KEY)).toBeNull();
});
});
+1
View File
@@ -66,6 +66,7 @@ const baseSettings: AppSettings = {
advert_interval: 0,
last_advert_time: 0,
flood_scope: '',
known_regions: [],
blocked_keys: [],
blocked_names: [],
discovery_blocked_types: [],