mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-06 16:53:38 +02:00
Fix clamping on value inputs to allow empty while focused
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CrackerPanel } from '../components/CrackerPanel';
|
||||
|
||||
vi.mock('meshcore-hashtag-cracker', () => ({
|
||||
GroupTextCracker: class {
|
||||
isGpuAvailable() {
|
||||
return false;
|
||||
}
|
||||
destroy() {}
|
||||
setWordlist() {}
|
||||
abort() {}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('nosleep.js', () => ({
|
||||
default: class {
|
||||
enable() {}
|
||||
disable() {}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../api', () => ({
|
||||
api: {
|
||||
getUndecryptedPacketCount: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../components/ui/sonner', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { api } from '../api';
|
||||
|
||||
const mockedApi = vi.mocked(api);
|
||||
|
||||
describe('CrackerPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockedApi.getUndecryptedPacketCount.mockResolvedValue({ count: 0 });
|
||||
});
|
||||
|
||||
it('allows clearing max length while editing', async () => {
|
||||
render(<CrackerPanel packets={[]} channels={[]} onChannelCreate={vi.fn()} visible={false} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedApi.getUndecryptedPacketCount).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const maxLengthInput = screen.getByLabelText('Max Length:') as HTMLInputElement;
|
||||
fireEvent.change(maxLengthInput, { target: { value: '' } });
|
||||
|
||||
expect(maxLengthInput.value).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -704,6 +704,75 @@ describe('SettingsFanoutSection', () => {
|
||||
expect(audienceInput).toHaveValue('');
|
||||
});
|
||||
|
||||
it('existing community MQTT defaults can be cleared while editing and normalize on save', async () => {
|
||||
const communityConfig: FanoutConfig = {
|
||||
id: 'comm-1',
|
||||
type: 'mqtt_community',
|
||||
name: 'Community Feed',
|
||||
enabled: false,
|
||||
config: {
|
||||
broker_host: 'mqtt-us-v1.letsmesh.net',
|
||||
broker_port: 443,
|
||||
transport: 'websockets',
|
||||
use_tls: true,
|
||||
tls_verify: true,
|
||||
auth_mode: 'token',
|
||||
iata: 'LAX',
|
||||
email: '',
|
||||
token_audience: '',
|
||||
topic_template: 'meshcore/{IATA}/{PUBLIC_KEY}/packets',
|
||||
},
|
||||
scope: { messages: 'none', raw_packets: 'all' },
|
||||
sort_order: 0,
|
||||
created_at: 1000,
|
||||
};
|
||||
mockedApi.getFanoutConfigs.mockResolvedValue([communityConfig]);
|
||||
mockedApi.updateFanoutConfig.mockResolvedValue({
|
||||
...communityConfig,
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
renderSection();
|
||||
await waitFor(() => expect(screen.getByText('Community Feed')).toBeInTheDocument());
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
|
||||
await waitFor(() => expect(screen.getByText('← Back to list')).toBeInTheDocument());
|
||||
|
||||
const hostInput = screen.getByLabelText('Broker Host') as HTMLInputElement;
|
||||
const portInput = screen.getByLabelText('Broker Port') as HTMLInputElement;
|
||||
const topicTemplateInput = screen.getByLabelText('Packet Topic Template') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(hostInput, { target: { value: '' } });
|
||||
fireEvent.change(portInput, { target: { value: '' } });
|
||||
fireEvent.change(topicTemplateInput, { target: { value: '' } });
|
||||
|
||||
expect(hostInput.value).toBe('');
|
||||
expect(portInput.value).toBe('');
|
||||
expect(topicTemplateInput.value).toBe('');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save as Enabled' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedApi.updateFanoutConfig).toHaveBeenCalledWith('comm-1', {
|
||||
name: 'Community Feed',
|
||||
config: {
|
||||
broker_host: 'mqtt-us-v1.letsmesh.net',
|
||||
broker_port: 443,
|
||||
transport: 'websockets',
|
||||
use_tls: true,
|
||||
tls_verify: true,
|
||||
auth_mode: 'token',
|
||||
iata: 'LAX',
|
||||
email: '',
|
||||
token_audience: '',
|
||||
topic_template: 'meshcore/{IATA}/{PUBLIC_KEY}/packets',
|
||||
},
|
||||
scope: { messages: 'none', raw_packets: 'all' },
|
||||
enabled: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('community MQTT can be configured for no auth', async () => {
|
||||
const communityConfig: FanoutConfig = {
|
||||
id: 'comm-1',
|
||||
@@ -783,6 +852,65 @@ describe('SettingsFanoutSection', () => {
|
||||
expect(screen.queryByLabelText('Broker Host')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('private MQTT fields can be cleared while editing and normalize defaults on create', async () => {
|
||||
const createdConfig: FanoutConfig = {
|
||||
id: 'mqtt-private-1',
|
||||
type: 'mqtt_private',
|
||||
name: 'Private MQTT 1',
|
||||
enabled: true,
|
||||
config: {
|
||||
broker_host: 'broker.local',
|
||||
broker_port: 1883,
|
||||
username: '',
|
||||
password: '',
|
||||
use_tls: false,
|
||||
tls_insecure: false,
|
||||
topic_prefix: 'meshcore',
|
||||
},
|
||||
scope: { messages: 'all', raw_packets: 'all' },
|
||||
sort_order: 0,
|
||||
created_at: 2000,
|
||||
};
|
||||
mockedApi.createFanoutConfig.mockResolvedValue(createdConfig);
|
||||
mockedApi.getFanoutConfigs.mockResolvedValueOnce([]).mockResolvedValueOnce([createdConfig]);
|
||||
|
||||
renderSection();
|
||||
await openCreateIntegrationDialog();
|
||||
selectCreateIntegration('Private MQTT');
|
||||
confirmCreateIntegration();
|
||||
await waitFor(() => expect(screen.getByText('← Back to list')).toBeInTheDocument());
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Broker Host'), { target: { value: 'broker.local' } });
|
||||
|
||||
const portInput = screen.getByLabelText('Broker Port') as HTMLInputElement;
|
||||
const prefixInput = screen.getByLabelText('Topic Prefix') as HTMLInputElement;
|
||||
fireEvent.change(portInput, { target: { value: '' } });
|
||||
fireEvent.change(prefixInput, { target: { value: '' } });
|
||||
|
||||
expect(portInput.value).toBe('');
|
||||
expect(prefixInput.value).toBe('');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save as Enabled' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedApi.createFanoutConfig).toHaveBeenCalledWith({
|
||||
type: 'mqtt_private',
|
||||
name: 'Private MQTT #1',
|
||||
config: {
|
||||
broker_host: 'broker.local',
|
||||
broker_port: 1883,
|
||||
username: '',
|
||||
password: '',
|
||||
use_tls: false,
|
||||
tls_insecure: false,
|
||||
topic_prefix: 'meshcore',
|
||||
},
|
||||
scope: { messages: 'all', raw_packets: 'all' },
|
||||
enabled: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('creates MeshRank preset as a regular mqtt_community config', async () => {
|
||||
const createdConfig: FanoutConfig = {
|
||||
id: 'comm-meshrank',
|
||||
@@ -912,6 +1040,57 @@ describe('SettingsFanoutSection', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('map upload geofence radius can be cleared while editing and normalizes to zero', async () => {
|
||||
const createdConfig: FanoutConfig = {
|
||||
id: 'map-1',
|
||||
type: 'map_upload',
|
||||
name: 'Map Upload 1',
|
||||
enabled: true,
|
||||
config: {
|
||||
api_url: '',
|
||||
dry_run: true,
|
||||
geofence_enabled: true,
|
||||
geofence_radius_km: 0,
|
||||
},
|
||||
scope: { messages: 'none', raw_packets: 'all' },
|
||||
sort_order: 0,
|
||||
created_at: 2000,
|
||||
};
|
||||
mockedApi.createFanoutConfig.mockResolvedValue(createdConfig);
|
||||
mockedApi.getFanoutConfigs.mockResolvedValueOnce([]).mockResolvedValueOnce([createdConfig]);
|
||||
|
||||
renderSection();
|
||||
await openCreateIntegrationDialog();
|
||||
selectCreateIntegration('Map Upload');
|
||||
confirmCreateIntegration();
|
||||
await waitFor(() => expect(screen.getByText('← Back to list')).toBeInTheDocument());
|
||||
|
||||
fireEvent.click(screen.getByText('Enable Geofence'));
|
||||
const radiusInput = screen.getByLabelText('Radius (km)') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(radiusInput, { target: { value: '100' } });
|
||||
fireEvent.change(radiusInput, { target: { value: '' } });
|
||||
|
||||
expect(radiusInput.value).toBe('');
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save as Enabled' }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockedApi.createFanoutConfig).toHaveBeenCalledWith({
|
||||
type: 'map_upload',
|
||||
name: 'Map Upload #1',
|
||||
config: {
|
||||
api_url: '',
|
||||
dry_run: true,
|
||||
geofence_enabled: true,
|
||||
geofence_radius_km: 0,
|
||||
},
|
||||
scope: { messages: 'none', raw_packets: 'all' },
|
||||
enabled: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('LetsMesh (EU) preset saves the EU broker defaults', async () => {
|
||||
const createdConfig: FanoutConfig = {
|
||||
id: 'comm-letsmesh-eu',
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { VisualizerControls } from '../components/visualizer/VisualizerControls';
|
||||
|
||||
describe('VisualizerControls', () => {
|
||||
it('allows clearing numeric inputs while editing', () => {
|
||||
render(
|
||||
<VisualizerControls
|
||||
showControls
|
||||
setShowControls={vi.fn()}
|
||||
showAmbiguousPaths={false}
|
||||
setShowAmbiguousPaths={vi.fn()}
|
||||
showAmbiguousNodes={false}
|
||||
setShowAmbiguousNodes={vi.fn()}
|
||||
useAdvertPathHints={false}
|
||||
setUseAdvertPathHints={vi.fn()}
|
||||
collapseLikelyKnownSiblingRepeaters={false}
|
||||
setCollapseLikelyKnownSiblingRepeaters={vi.fn()}
|
||||
splitAmbiguousByTraffic={false}
|
||||
setSplitAmbiguousByTraffic={vi.fn()}
|
||||
observationWindowSec={5}
|
||||
setObservationWindowSec={vi.fn()}
|
||||
pruneStaleNodes
|
||||
setPruneStaleNodes={vi.fn()}
|
||||
pruneStaleMinutes={10}
|
||||
setPruneStaleMinutes={vi.fn()}
|
||||
letEmDrift={false}
|
||||
setLetEmDrift={vi.fn()}
|
||||
autoOrbit={false}
|
||||
setAutoOrbit={vi.fn()}
|
||||
chargeStrength={-100}
|
||||
setChargeStrength={vi.fn()}
|
||||
particleSpeedMultiplier={1}
|
||||
setParticleSpeedMultiplier={vi.fn()}
|
||||
nodeCount={0}
|
||||
linkCount={0}
|
||||
onExpandContract={vi.fn()}
|
||||
onClearAndReset={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
const observationInput = screen.getByLabelText('Ack/echo listen window:') as HTMLInputElement;
|
||||
const pruneInput = screen.getByLabelText('Window:') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(observationInput, { target: { value: '' } });
|
||||
fireEvent.change(pruneInput, { target: { value: '' } });
|
||||
|
||||
expect(observationInput.value).toBe('');
|
||||
expect(pruneInput.value).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user