mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-05 17:32:10 +02:00
Add custom pathing (closes #45)
This commit is contained in:
@@ -106,4 +106,25 @@ describe('ContactInfoPane', () => {
|
||||
expect(screen.getByText('Flood')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows forced routing override and learned route separately', async () => {
|
||||
const contact = createContact({
|
||||
last_path_len: 1,
|
||||
out_path_hash_mode: 0,
|
||||
route_override_path: 'ae92f13e',
|
||||
route_override_len: 2,
|
||||
route_override_hash_mode: 1,
|
||||
});
|
||||
getContactDetail.mockResolvedValue(createDetail(contact));
|
||||
|
||||
render(<ContactInfoPane {...baseProps} contactKey={contact.public_key} />);
|
||||
|
||||
await screen.findByText('Alice');
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Routing')).toBeInTheDocument();
|
||||
expect(screen.getByText('(forced)')).toBeInTheDocument();
|
||||
expect(screen.getByText('Learned Route')).toBeInTheDocument();
|
||||
expect(screen.getByText('1 hop')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
extractPacketPayloadHex,
|
||||
findContactsByPrefix,
|
||||
calculateDistance,
|
||||
formatRouteLabel,
|
||||
formatRoutingOverrideInput,
|
||||
getEffectiveContactRoute,
|
||||
resolvePath,
|
||||
formatDistance,
|
||||
formatHopCounts,
|
||||
@@ -131,6 +134,42 @@ describe('extractPacketPayloadHex', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('contact routing helpers', () => {
|
||||
it('prefers routing override over learned route', () => {
|
||||
const effective = getEffectiveContactRoute(
|
||||
createContact({
|
||||
last_path: 'AABB',
|
||||
last_path_len: 1,
|
||||
out_path_hash_mode: 0,
|
||||
route_override_path: 'AE92F13E',
|
||||
route_override_len: 2,
|
||||
route_override_hash_mode: 1,
|
||||
})
|
||||
);
|
||||
|
||||
expect(effective.path).toBe('AE92F13E');
|
||||
expect(effective.pathLen).toBe(2);
|
||||
expect(effective.pathHashMode).toBe(1);
|
||||
expect(effective.forced).toBe(true);
|
||||
});
|
||||
|
||||
it('formats route labels and override input', () => {
|
||||
expect(formatRouteLabel(-1)).toBe('flood');
|
||||
expect(formatRouteLabel(0)).toBe('direct');
|
||||
expect(formatRouteLabel(2, true)).toBe('2 hops');
|
||||
|
||||
expect(
|
||||
formatRoutingOverrideInput(
|
||||
createContact({
|
||||
route_override_path: 'AE92F13E',
|
||||
route_override_len: 2,
|
||||
route_override_hash_mode: 1,
|
||||
})
|
||||
)
|
||||
).toBe('ae92,f13e');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findContactsByPrefix', () => {
|
||||
const contacts: Contact[] = [
|
||||
createContact({
|
||||
|
||||
@@ -307,67 +307,82 @@ describe('RepeaterDashboard', () => {
|
||||
expect(screen.getByText('1 hop')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('direct path is clickable with reset title', () => {
|
||||
it('direct path is clickable with routing override title', () => {
|
||||
const directContacts: Contact[] = [
|
||||
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
|
||||
];
|
||||
|
||||
render(<RepeaterDashboard {...defaultProps} contacts={directContacts} />);
|
||||
|
||||
const directEl = screen.getByTitle('Click to reset path to flood');
|
||||
const directEl = screen.getByTitle('Click to edit routing override');
|
||||
expect(directEl).toBeInTheDocument();
|
||||
expect(directEl.textContent).toBe('direct');
|
||||
});
|
||||
|
||||
it('clicking direct path calls resetContactPath on confirm', async () => {
|
||||
const directContacts: Contact[] = [
|
||||
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
|
||||
it('shows forced decorator when a routing override is active', () => {
|
||||
const forcedContacts: Contact[] = [
|
||||
{
|
||||
...contacts[0],
|
||||
last_path_len: 1,
|
||||
last_seen: 1700000000,
|
||||
route_override_path: 'ae92f13e',
|
||||
route_override_len: 2,
|
||||
route_override_hash_mode: 1,
|
||||
},
|
||||
];
|
||||
|
||||
// Mock window.confirm to return true
|
||||
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
|
||||
render(<RepeaterDashboard {...defaultProps} contacts={forcedContacts} />);
|
||||
|
||||
// Mock the api module
|
||||
const { api } = await import('../api');
|
||||
const resetSpy = vi.spyOn(api, 'resetContactPath').mockResolvedValue({
|
||||
status: 'ok',
|
||||
public_key: REPEATER_KEY,
|
||||
});
|
||||
|
||||
render(<RepeaterDashboard {...defaultProps} contacts={directContacts} />);
|
||||
|
||||
fireEvent.click(screen.getByTitle('Click to reset path to flood'));
|
||||
|
||||
expect(confirmSpy).toHaveBeenCalledWith('Reset path to flood?');
|
||||
expect(resetSpy).toHaveBeenCalledWith(REPEATER_KEY);
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
resetSpy.mockRestore();
|
||||
expect(screen.getByText('2 hops')).toBeInTheDocument();
|
||||
expect(screen.getByText('(forced)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking path does not call API when confirm is cancelled', async () => {
|
||||
it('clicking direct path opens prompt and updates routing override', async () => {
|
||||
const directContacts: Contact[] = [
|
||||
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
|
||||
];
|
||||
|
||||
// Mock window.confirm to return false
|
||||
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false);
|
||||
const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue('0');
|
||||
|
||||
const { api } = await import('../api');
|
||||
const resetSpy = vi.spyOn(api, 'resetContactPath').mockResolvedValue({
|
||||
const overrideSpy = vi.spyOn(api, 'setContactRoutingOverride').mockResolvedValue({
|
||||
status: 'ok',
|
||||
public_key: REPEATER_KEY,
|
||||
});
|
||||
|
||||
render(<RepeaterDashboard {...defaultProps} contacts={directContacts} />);
|
||||
|
||||
fireEvent.click(screen.getByTitle('Click to reset path to flood'));
|
||||
fireEvent.click(screen.getByTitle('Click to edit routing override'));
|
||||
|
||||
expect(confirmSpy).toHaveBeenCalledWith('Reset path to flood?');
|
||||
expect(resetSpy).not.toHaveBeenCalled();
|
||||
expect(promptSpy).toHaveBeenCalled();
|
||||
expect(overrideSpy).toHaveBeenCalledWith(REPEATER_KEY, '0');
|
||||
|
||||
confirmSpy.mockRestore();
|
||||
resetSpy.mockRestore();
|
||||
promptSpy.mockRestore();
|
||||
overrideSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('clicking path does not call API when prompt is cancelled', async () => {
|
||||
const directContacts: Contact[] = [
|
||||
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
|
||||
];
|
||||
|
||||
const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue(null);
|
||||
|
||||
const { api } = await import('../api');
|
||||
const overrideSpy = vi.spyOn(api, 'setContactRoutingOverride').mockResolvedValue({
|
||||
status: 'ok',
|
||||
public_key: REPEATER_KEY,
|
||||
});
|
||||
|
||||
render(<RepeaterDashboard {...defaultProps} contacts={directContacts} />);
|
||||
|
||||
fireEvent.click(screen.getByTitle('Click to edit routing override'));
|
||||
|
||||
expect(promptSpy).toHaveBeenCalled();
|
||||
expect(overrideSpy).not.toHaveBeenCalled();
|
||||
|
||||
promptSpy.mockRestore();
|
||||
overrideSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user