chore: lint/format all files (#604)

* chore: lint/format all files

* Fix config sidebar button state (#602)

* chore: Update deno.lock version and add Radix UI slider component (#601)

* fix: improve how table addresses even/odd rows

---------

Co-authored-by: philon- <philon-@users.noreply.github.com>
Co-authored-by: Kamil Dzieniszewski <kamil.dzieniszewski@gmail.com>
This commit is contained in:
Dan Ditomaso
2025-05-04 15:55:04 -04:00
committed by GitHub
co-authored by philon- Kamil Dzieniszewski
parent b7bdb1a502
commit 480ca46a95
104 changed files with 3104 additions and 2527 deletions
@@ -1,16 +1,16 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
// Ensure the path is correct for import
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
vi.mock('@core/stores/messageStore', () => ({
vi.mock("@core/stores/messageStore", () => ({
useMessageStore: vi.fn(() => ({
deleteAllMessages: vi.fn(),
})),
}));
describe('DeleteMessagesDialog', () => {
describe("DeleteMessagesDialog", () => {
const mockOnOpenChange = vi.fn();
const mockClearAllMessages = vi.fn();
@@ -20,50 +20,61 @@ describe('DeleteMessagesDialog', () => {
const mockedUseMessageStore = vi.mocked(useMessageStore);
mockedUseMessageStore.mockImplementation(() => ({
deleteAllMessages: mockClearAllMessages
deleteAllMessages: mockClearAllMessages,
}));
mockedUseMessageStore.mockClear();
});
it('calls onOpenChange with false when the close button (X) is clicked', () => {
render(<DeleteMessagesDialog open={true} onOpenChange={mockOnOpenChange} />);
const closeButton = screen.queryByTestId('dialog-close-button');
it("calls onOpenChange with false when the close button (X) is clicked", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
const closeButton = screen.queryByTestId("dialog-close-button");
if (!closeButton) {
throw new Error("Dialog close button with data-testid='dialog-close-button' not found. Did you add it to the component?");
throw new Error(
"Dialog close button with data-testid='dialog-close-button' not found. Did you add it to the component?",
);
}
fireEvent.click(closeButton);
expect(mockOnOpenChange).toHaveBeenCalledTimes(1);
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
});
it('renders the dialog when open is true', () => {
render(<DeleteMessagesDialog open={true} onOpenChange={mockOnOpenChange} />);
expect(screen.getByText('Clear All Messages')).toBeInTheDocument();
expect(screen.getByText(/This action will clear all message history./)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Clear Messages' })).toBeInTheDocument();
it("renders the dialog when open is true", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
expect(screen.getByText("Clear All Messages")).toBeInTheDocument();
expect(screen.getByText(/This action will clear all message history./))
.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Dismiss" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Clear Messages" }))
.toBeInTheDocument();
});
it('does not render the dialog when open is false', () => {
render(<DeleteMessagesDialog open={false} onOpenChange={mockOnOpenChange} />);
expect(screen.queryByText('Clear All Messages')).toBeNull();
it("does not render the dialog when open is false", () => {
render(
<DeleteMessagesDialog open={false} onOpenChange={mockOnOpenChange} />,
);
expect(screen.queryByText("Clear All Messages")).toBeNull();
});
it('calls onOpenChange with false when the dismiss button is clicked', () => {
render(<DeleteMessagesDialog open={true} onOpenChange={mockOnOpenChange} />);
fireEvent.click(screen.getByRole('button', { name: 'Dismiss' }));
it("calls onOpenChange with false when the dismiss button is clicked", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
expect(mockOnOpenChange).toHaveBeenCalledTimes(1); // Add count check
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
});
it('calls deleteAllMessages and onOpenChange with false when the clear messages button is clicked', () => {
render(<DeleteMessagesDialog open={true} onOpenChange={mockOnOpenChange} />);
fireEvent.click(screen.getByRole('button', { name: 'Clear Messages' }));
it("calls deleteAllMessages and onOpenChange with false when the clear messages button is clicked", () => {
render(
<DeleteMessagesDialog open onOpenChange={mockOnOpenChange} />,
);
fireEvent.click(screen.getByRole("button", { name: "Clear Messages" }));
expect(mockClearAllMessages).toHaveBeenCalledTimes(1);
expect(mockOnOpenChange).toHaveBeenCalledTimes(1); // Add count check
expect(mockOnOpenChange).toHaveBeenCalledWith(false);
});
});
});
@@ -1,4 +1,3 @@
import { Button } from "@components/UI/Button.tsx";
import {
Dialog,
+13 -5
View File
@@ -44,8 +44,14 @@ export const DeviceNameDialog = ({
values: defaultValues,
});
const { currentLength: currentLongNameLength } = validateMaxByteLength(getValues('longName'), MAX_LONG_NAME_BYTE_LENGTH);
const { currentLength: currentShortNameLength } = validateMaxByteLength(getValues('shortName'), MAX_SHORT_NAME_BYTE_LENGTH);
const { currentLength: currentLongNameLength } = validateMaxByteLength(
getValues("longName"),
MAX_LONG_NAME_BYTE_LENGTH,
);
const { currentLength: currentShortNameLength } = validateMaxByteLength(
getValues("shortName"),
MAX_SHORT_NAME_BYTE_LENGTH,
);
const onSubmit = handleSubmit((data) => {
connection?.setOwner(
@@ -83,7 +89,7 @@ export const DeviceNameDialog = ({
label: "Long Name",
type: "text",
properties: {
className: 'text-slate-900 dark:text-slate-200',
className: "text-slate-900 dark:text-slate-200",
fieldLength: {
currentValueLength: currentLongNameLength ?? 0,
max: MAX_LONG_NAME_BYTE_LENGTH,
@@ -113,11 +119,13 @@ export const DeviceNameDialog = ({
</div>
<DialogFooter>
<Button type="button" variant="destructive" onClick={handleReset}>Reset</Button>
<Button type="button" variant="destructive" onClick={handleReset}>
Reset
</Button>
<Button type="submit">Save</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
};
};
-1
View File
@@ -12,7 +12,6 @@ import { RefreshKeysDialog } from "@components/Dialog/RefreshKeysDialog/RefreshK
import { RebootOTADialog } from "@components/Dialog/RebootOTADialog.tsx";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
export const DialogManager = () => {
const { channels, config, dialog, setDialogOpen } = useDevice();
return (
+1 -1
View File
@@ -51,7 +51,7 @@ export const ImportDialog = ({
const paddedString = encodedChannelConfig
.padEnd(
encodedChannelConfig.length +
((4 - (encodedChannelConfig.length % 4)) % 4),
((4 - (encodedChannelConfig.length % 4)) % 4),
"=",
)
.replace(/-/g, "+")
@@ -43,8 +43,9 @@ export const LocationResponseDialog = ({
Coordinates:{" "}
<a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${location?.data.latitudeI / 1e7
}&mlon=${location?.data.longitudeI / 1e7}&layers=N`}
href={`https://www.openstreetmap.org/?mlat=${
location?.data.latitudeI / 1e7
}&mlon=${location?.data.longitudeI / 1e7}&layers=N`}
target="_blank"
rel="noreferrer"
>
+8 -6
View File
@@ -90,8 +90,8 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
{browserFeatures.length > 0 && (
<>
This connection type requires{" "}
{formatFeatureList(browserFeatures)}. Please use a
supported browser, like Chrome or Edge.
{formatFeatureList(browserFeatures)}. Please use a supported
browser, like Chrome or Edge.
</>
)}
{needsSecureContext && (
@@ -114,11 +114,9 @@ export const NewDeviceDialog = ({
open,
onOpenChange,
}: NewDeviceProps) => {
const [connectionInProgress, setConnectionInProgress] =
useState(false);
const [connectionInProgress, setConnectionInProgress] = useState(false);
const { unsupported } = useBrowserFeatureDetection();
const tabs: TabManifest[] = [
{
label: "HTTP",
@@ -160,7 +158,11 @@ export const NewDeviceDialog = ({
{tab.isDisabled
? <ErrorMessage missingFeatures={unsupported} />
: null}
<tab.element closeDialog={() => onOpenChange(false)} setConnectionInProgress={setConnectionInProgress} connectionInProgress={connectionInProgress} />
<tab.element
closeDialog={() => onOpenChange(false)}
setConnectionInProgress={setConnectionInProgress}
connectionInProgress={connectionInProgress}
/>
</fieldset>
</TabsContent>
))}
@@ -1,4 +1,4 @@
import { describe, it, vi, expect, beforeEach } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { NodeDetailsDialog } from "@components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
@@ -11,7 +11,6 @@ vi.mock("@core/stores/appStore");
const mockUseDevice = vi.mocked(useDevice);
const mockUseAppStore = vi.mocked(useAppStore);
describe("NodeDetailsDialog", () => {
const mockDevice = {
num: 1234,
@@ -54,30 +53,33 @@ describe("NodeDetailsDialog", () => {
});
it("renders node details correctly", () => {
render(<NodeDetailsDialog open={true} onOpenChange={() => { }} />);
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
expect(screen.getByText(/Node Details for Test Node \(TN\)/i)).toBeInTheDocument();
expect(screen.getByText(/Node Details for Test Node \(TN\)/i))
.toBeInTheDocument();
expect(screen.getByText("Node Number: 1234")).toBeInTheDocument();
expect(screen.getByText(/Node Hex: !/i)).toBeInTheDocument();
expect(screen.getByText(/Last Heard:/i)).toBeInTheDocument();
expect(screen.getByText(/Coordinates:/i)).toBeInTheDocument();
const link = screen.getByRole('link', { name: /^45, -75$/ });
const link = screen.getByRole("link", { name: /^45, -75$/ });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', expect.stringContaining('openstreetmap.org'));
expect(link).toHaveAttribute(
"href",
expect.stringContaining("openstreetmap.org"),
);
expect(screen.getByText(/Altitude: 200m/i)).toBeInTheDocument();
expect(screen.getByText(/Air TX utilization: 50.12%/i)).toBeInTheDocument();
expect(screen.getByText(/Channel utilization: 75.46%/i)).toBeInTheDocument();
expect(screen.getByText(/Channel utilization: 75.46%/i))
.toBeInTheDocument();
expect(screen.getByText(/Battery level: 88.79%/i)).toBeInTheDocument();
expect(screen.getByText(/Voltage: 4.20V/i)).toBeInTheDocument();
expect(screen.getByText(/Uptime:/i)).toBeInTheDocument();
expect(screen.getByText(/All Raw Metrics:/i)).toBeInTheDocument();
});
it("renders null if device is not found", () => {
@@ -99,7 +101,9 @@ describe("NodeDetailsDialog", () => {
},
});
const { container } = render(<NodeDetailsDialog open={true} onOpenChange={() => { }} />);
const { container } = render(
<NodeDetailsDialog open onOpenChange={() => {}} />,
);
expect(container.firstChild).toBeNull();
expect(screen.queryByText(/Node Details for/i)).not.toBeInTheDocument();
@@ -110,7 +114,7 @@ describe("NodeDetailsDialog", () => {
mockUseDevice.mockReturnValue({ getNode: () => nodeWithoutPosition });
mockUseAppStore.mockReturnValue({ nodeNumDetails: 1234 });
render(<NodeDetailsDialog open={true} onOpenChange={() => { }} />);
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
expect(screen.queryByText(/Coordinates:/i)).not.toBeInTheDocument();
expect(screen.queryByText(/Altitude:/i)).not.toBeInTheDocument();
@@ -122,7 +126,7 @@ describe("NodeDetailsDialog", () => {
mockUseDevice.mockReturnValue({ getNode: () => nodeWithoutMetrics });
mockUseAppStore.mockReturnValue({ nodeNumDetails: 1234 });
render(<NodeDetailsDialog open={true} onOpenChange={() => { }} />);
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
expect(screen.queryByText(/Device Metrics:/i)).not.toBeInTheDocument();
expect(screen.queryByText(/Air TX utilization:/i)).not.toBeInTheDocument();
@@ -134,9 +138,8 @@ describe("NodeDetailsDialog", () => {
mockUseDevice.mockReturnValue({ getNode: () => nodeNeverHeard });
mockUseAppStore.mockReturnValue({ nodeNumDetails: 1234 });
render(<NodeDetailsDialog open={true} onOpenChange={() => { }} />);
render(<NodeDetailsDialog open onOpenChange={() => {}} />);
expect(screen.getByText(/Last Heard: Never/i)).toBeInTheDocument();
});
});
});
@@ -65,7 +65,7 @@ export const NodeDetailsDialog = ({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent >
<DialogContent>
<DialogClose />
<DialogHeader>
<DialogTitle>
@@ -78,9 +78,8 @@ export const NodeDetailsDialog = ({
<div className="flex flex-col">
<DeviceImage
className="w-32 h-32 mx-auto rounded-lg border-4 border-slate-200 dark:border-slate-800"
deviceType={
Protobuf.Mesh.HardwareModel[device.user?.hwModel ?? 0]
}
deviceType={Protobuf.Mesh
.HardwareModel[device.user?.hwModel ?? 0]}
/>
<div className="bg-slate-100 text-slate-900 dark:text-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
<p className="text-lg font-semibold">Details:</p>
@@ -91,16 +90,14 @@ export const NodeDetailsDialog = ({
<p>Node Number: {device.num}</p>
<p>Node Hex: !{numberToHexUnpadded(device.num)}</p>
<p>
Role:{" "}
{
Protobuf.Config.Config_DeviceConfig_Role[
Role: {Protobuf.Config.Config_DeviceConfig_Role[
device.user?.role ?? 0
]
}
]}
</p>
<p>
Last Heard:{" "}
{device.lastHeard === 0 ? "Never" : <TimeAgo timestamp={device.lastHeard * 1000} />}
Last Heard: {device.lastHeard === 0
? "Never"
: <TimeAgo timestamp={device.lastHeard * 1000} />}
</p>
</div>
@@ -112,9 +109,9 @@ export const NodeDetailsDialog = ({
Coordinates:{" "}
<a
className="text-blue-500 dark:text-blue-400"
href={`https://www.openstreetmap.org/?mlat=${device.position.latitudeI / 1e7
}&mlon=${device.position.longitudeI / 1e7
}&layers=N`}
href={`https://www.openstreetmap.org/?mlat=${
device.position.latitudeI / 1e7
}&mlon=${device.position.longitudeI / 1e7}&layers=N`}
target="_blank"
rel="noreferrer"
>
@@ -140,7 +137,7 @@ export const NodeDetailsDialog = ({
<p key={metric.key}>
{metric.label}: {metric.format(metric.value)}
</p>
)
),
)}
{device.deviceMetrics.uptimeSeconds && (
<p>
@@ -150,7 +147,6 @@ export const NodeDetailsDialog = ({
)}
</div>
)}
</div>
<div className="text-slate-900 dark:text-slate-100 w-full max-w-[464px] bg-slate-100 dark:bg-slate-800 p-3 rounded-lg mt-3">
+4 -1
View File
@@ -13,7 +13,10 @@ import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { TrashIcon } from "lucide-react";
import { Button } from "../UI/Button.tsx";
import { MessageType, useMessageStore } from "../../core/stores/messageStore/index.ts";
import {
MessageType,
useMessageStore,
} from "../../core/stores/messageStore/index.ts";
export interface NodeOptionsDialogProps {
node: Protobuf.Mesh.NodeInfo | undefined;
@@ -14,7 +14,7 @@ export interface PkiRegenerateDialogProps {
title: string;
description: string;
button: string;
}
};
open: boolean;
onOpenChange: () => void;
onSubmit: () => void;
+13 -11
View File
@@ -79,8 +79,8 @@ export const QRDialog = ({
{channel.settings?.name.length
? channel.settings.name
: channel.role === Protobuf.Channel.Channel_Role.PRIMARY
? "Primary"
: `Channel: ${channel.index}`}
? "Primary"
: `Channel: ${channel.index}`}
</Label>
<Checkbox
key={channel.index}
@@ -108,20 +108,22 @@ export const QRDialog = ({
<div className="flex justify-center">
<button
type="button"
className={`border-slate-900 border-t border-l border-b rounded-l h-10 px-7 py-2 text-sm font-medium focus:outline-hidden focus:ring-2 focus:ring-offset-2 ${qrCodeAdd
? "focus:ring-green-800 bg-green-800 text-white"
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
}`}
className={`border-slate-900 border-t border-l border-b rounded-l h-10 px-7 py-2 text-sm font-medium focus:outline-hidden focus:ring-2 focus:ring-offset-2 ${
qrCodeAdd
? "focus:ring-green-800 bg-green-800 text-white"
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
}`}
onClick={() => setQrCodeAdd(true)}
>
Add Channels
</button>
<button
type="button"
className={`border-slate-900 border-t border-r border-b rounded-r h-10 px-4 py-2 text-sm font-medium focus:outline-hidden focus:ring-2 focus:ring-offset-2 ${!qrCodeAdd
? "focus:ring-green-800 bg-green-800 text-white"
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
}`}
className={`border-slate-900 border-t border-r border-b rounded-r h-10 px-4 py-2 text-sm font-medium focus:outline-hidden focus:ring-2 focus:ring-offset-2 ${
!qrCodeAdd
? "focus:ring-green-800 bg-green-800 text-white"
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
}`}
onClick={() => setQrCodeAdd(false)}
>
Replace Channels
@@ -134,7 +136,7 @@ export const QRDialog = ({
value={qrCodeUrl}
disabled
action={{
key: 'copy-value',
key: "copy-value",
icon: ClipboardIcon,
onClick() {
void navigator.clipboard.writeText(qrCodeUrl);
+30 -26
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { RebootOTADialog } from './RebootOTADialog.tsx';
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { RebootOTADialog } from "./RebootOTADialog.tsx";
import { ReactNode } from "react";
const rebootOtaMock = vi.fn();
@@ -8,41 +8,46 @@ let mockConnection: { rebootOta: (delay: number) => void } | undefined = {
rebootOta: rebootOtaMock,
};
vi.mock('@core/stores/deviceStore.ts', () => ({
vi.mock("@core/stores/deviceStore.ts", () => ({
useDevice: () => ({
connection: mockConnection,
}),
}));
vi.mock('@components/UI/Button.tsx', async () => {
const actual = await vi.importActual('@components/UI/Button.tsx');
vi.mock("@components/UI/Button.tsx", async () => {
const actual = await vi.importActual("@components/UI/Button.tsx");
return {
...actual,
Button: (props: any) => <button {...props} />,
Button: (props) => <button {...props} />,
};
});
vi.mock('@components/UI/Input.tsx', async () => {
const actual = await vi.importActual('@components/UI/Input.tsx');
vi.mock("@components/UI/Input.tsx", async () => {
const actual = await vi.importActual("@components/UI/Input.tsx");
return {
...actual,
Input: (props: any) => <input {...props} />,
Input: (props) => <input {...props} />,
};
});
vi.mock('@components/UI/Dialog.tsx', () => {
vi.mock("@components/UI/Dialog.tsx", () => {
return {
Dialog: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogHeader: ({ children }: { children: ReactNode }) => <div>{children}</div>,
DialogContent: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
),
DialogHeader: ({ children }: { children: ReactNode }) => (
<div>{children}</div>
),
DialogTitle: ({ children }: { children: ReactNode }) => <h1>{children}</h1>,
DialogDescription: ({ children }: { children: ReactNode }) => <p>{children}</p>,
DialogDescription: ({ children }: { children: ReactNode }) => (
<p>{children}</p>
),
DialogClose: () => null,
};
});
describe('RebootOTADialog', () => {
describe("RebootOTADialog", () => {
beforeEach(() => {
vi.useFakeTimers();
rebootOtaMock.mockClear();
@@ -52,19 +57,19 @@ describe('RebootOTADialog', () => {
vi.useRealTimers();
});
it('renders dialog with default input value', () => {
render(<RebootOTADialog open={true} onOpenChange={() => { }} />);
it("renders dialog with default input value", () => {
render(<RebootOTADialog open onOpenChange={() => {}} />);
expect(screen.getByPlaceholderText(/enter delay/i)).toHaveValue(5);
expect(screen.getByText(/schedule reboot/i)).toBeInTheDocument();
expect(screen.getByText(/reboot to ota mode now/i)).toBeInTheDocument();
});
it('schedules a reboot with delay and calls rebootOta', async () => {
it("schedules a reboot with delay and calls rebootOta", async () => {
const onOpenChangeMock = vi.fn();
render(<RebootOTADialog open={true} onOpenChange={onOpenChangeMock} />);
render(<RebootOTADialog open onOpenChange={onOpenChangeMock} />);
fireEvent.change(screen.getByPlaceholderText(/enter delay/i), {
target: { value: '3' },
target: { value: "3" },
});
fireEvent.click(screen.getByText(/schedule reboot/i));
@@ -79,9 +84,9 @@ describe('RebootOTADialog', () => {
});
});
it('triggers an instant reboot', async () => {
it("triggers an instant reboot", async () => {
const onOpenChangeMock = vi.fn();
render(<RebootOTADialog open={true} onOpenChange={onOpenChangeMock} />);
render(<RebootOTADialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByText(/reboot to ota mode now/i));
@@ -91,13 +96,13 @@ describe('RebootOTADialog', () => {
});
});
it('does not call reboot if connection is undefined', async () => {
it("does not call reboot if connection is undefined", async () => {
const onOpenChangeMock = vi.fn();
// simulate no connection
mockConnection = undefined;
render(<RebootOTADialog open={true} onOpenChange={onOpenChangeMock} />);
render(<RebootOTADialog open onOpenChange={onOpenChangeMock} />);
fireEvent.click(screen.getByText(/schedule reboot/i));
vi.advanceTimersByTime(5000);
@@ -110,5 +115,4 @@ describe('RebootOTADialog', () => {
// reset connection for other tests
mockConnection = { rebootOta: rebootOtaMock };
});
});
+8 -6
View File
@@ -19,7 +19,9 @@ export interface RebootOTADialogProps {
const DEFAULT_REBOOT_DELAY = 5; // seconds
export const RebootOTADialog = ({ open, onOpenChange }: RebootOTADialogProps) => {
export const RebootOTADialog = (
{ open, onOpenChange }: RebootOTADialogProps,
) => {
const { connection } = useDevice();
const [time, setTime] = useState<number>(DEFAULT_REBOOT_DELAY);
const [isScheduled, setIsScheduled] = useState(false);
@@ -28,8 +30,8 @@ export const RebootOTADialog = ({ open, onOpenChange }: RebootOTADialogProps) =>
const handleSetTime = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.validity.valid) {
e.preventDefault();
return
};
return;
}
const val = e.target.value;
setInputValue(val);
@@ -73,7 +75,8 @@ export const RebootOTADialog = ({ open, onOpenChange }: RebootOTADialogProps) =>
<DialogHeader>
<DialogTitle>Reboot to OTA Mode</DialogTitle>
<DialogDescription>
Reboot the connected node after a delay into OTA (Over-the-Air) mode.
Reboot the connected node after a delay into OTA (Over-the-Air)
mode.
</DialogDescription>
</DialogHeader>
@@ -89,7 +92,7 @@ export const RebootOTADialog = ({ open, onOpenChange }: RebootOTADialogProps) =>
/>
<Button onClick={() => handleRebootWithTimeout()} className="w-9/12">
<ClockIcon className="mr-2" size={18} />
{isScheduled ? 'Reboot has been scheduled' : 'Schedule Reboot'}
{isScheduled ? "Reboot has been scheduled" : "Schedule Reboot"}
</Button>
</div>
@@ -101,4 +104,3 @@ export const RebootOTADialog = ({ open, onOpenChange }: RebootOTADialogProps) =>
</Dialog>
);
};
@@ -3,7 +3,7 @@ import { DeviceContext, useDeviceStore } from "@core/stores/deviceStore.ts";
import { RefreshKeysDialog } from "./RefreshKeysDialog.tsx";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { expect, test, vi, beforeEach, afterEach } from 'vitest';
import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { Protobuf } from "@meshtastic/core";
vi.mock("@core/stores/messageStore");
@@ -12,7 +12,9 @@ vi.mock("./useRefreshKeysDialog");
const mockUseMessageStore = vi.mocked(useMessageStore);
const mockUseRefreshKeysDialog = vi.mocked(useRefreshKeysDialog);
const getInitialState = () => useDeviceStore.getInitialState?.() ?? { devices: new Map(), remoteDevices: new Map() };
const getInitialState = () =>
useDeviceStore.getInitialState?.() ??
{ devices: new Map(), remoteDevices: new Map() };
beforeEach(() => {
useDeviceStore.setState(getInitialState(), true);
@@ -39,17 +41,19 @@ test("renders dialog when there is a node error for the active chat", () => {
longName: "Problem Node Long",
shortName: "ProbNode",
isLicensed: false,
macaddr: new Uint8Array(0)
macaddr: new Uint8Array(0),
},
lastHeard: Date.now() / 1000,
snr: 10
snr: 10,
} as Protobuf.Mesh.NodeInfo);
deviceStore.setNodeError(activeChatNum, "PKI_MISMATCH");
const updatedDeviceState = useDeviceStore.getState().getDevice(deviceId);
if (!updatedDeviceState) {
throw new Error("Failed to get updated device state from store for provider");
throw new Error(
"Failed to get updated device state from store for provider",
);
}
mockUseMessageStore.mockReturnValue({ activeChat: activeChatNum });
@@ -63,12 +67,18 @@ test("renders dialog when there is a node error for the active chat", () => {
render(
<DeviceContext.Provider value={updatedDeviceState}>
<RefreshKeysDialog open onOpenChange={vi.fn()} />
</DeviceContext.Provider>
</DeviceContext.Provider>,
);
expect(screen.getByText(/Keys Mismatch - Problem Node Long/)).toBeInTheDocument();
expect(screen.getByText(/Your node is unable to send a direct message to node: Problem Node Long \(ProbNode\)/)).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Request New Keys" })).toBeInTheDocument();
expect(screen.getByText(/Keys Mismatch - Problem Node Long/))
.toBeInTheDocument();
expect(
screen.getByText(
/Your node is unable to send a direct message to node: Problem Node Long \(ProbNode\)/,
),
).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Request New Keys" }))
.toBeInTheDocument();
expect(screen.getByRole("button", { name: "Dismiss" })).toBeInTheDocument();
});
@@ -90,7 +100,7 @@ test("does not render dialog if no error exists for active chat", () => {
const { container } = render(
<DeviceContext.Provider value={currentDeviceState}>
<RefreshKeysDialog open onOpenChange={vi.fn()} />
</DeviceContext.Provider>
</DeviceContext.Provider>,
);
expect(container.firstChild).toBeNull();
@@ -16,7 +16,9 @@ export interface RefreshKeysDialogProps {
onOpenChange: (open: boolean) => void;
}
export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps) => {
export const RefreshKeysDialog = (
{ open, onOpenChange }: RefreshKeysDialogProps,
) => {
const { activeChat } = useMessageStore();
const { nodeErrors, getNode } = useDevice();
const { handleCloseDialog, handleNodeRemove } = useRefreshKeysDialog();
@@ -31,8 +33,12 @@ export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps
const text = {
title: `Keys Mismatch - ${nodeWithError?.user?.longName ?? ""}`,
description: `Your node is unable to send a direct message to node: ${nodeWithError?.user?.longName ?? ""} (${nodeWithError?.user?.shortName ?? ""}). This is due to the remote node's current public key does not match the previously stored key for this node.`,
}
description: `Your node is unable to send a direct message to node: ${
nodeWithError?.user?.longName ?? ""
} (${
nodeWithError?.user?.shortName ?? ""
}). This is due to the remote node's current public key does not match the previously stored key for this node.`,
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-8 flex flex-col gap-2">
@@ -44,7 +50,10 @@ export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps
<ul className="mt-2">
<li className="flex place-items-center gap-2 items-start">
<div className="p-2 bg-slate-500 rounded-lg mt-1">
<LockKeyholeOpenIcon size={30} className="text-white justify-center" />
<LockKeyholeOpenIcon
size={30}
className="text-white justify-center"
/>
</div>
<div className="flex flex-col gap-2">
<div>
@@ -70,6 +79,6 @@ export const RefreshKeysDialog = ({ open, onOpenChange }: RefreshKeysDialogProps
</ul>
{/* </DialogDescription> */}
</DialogContent>
</Dialog >
</Dialog>
);
};
@@ -1,4 +1,4 @@
import { renderHook, act } from "@testing-library/react";
import { act, renderHook } from "@testing-library/react";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { useDevice } from "@core/stores/deviceStore.ts";
@@ -38,7 +38,7 @@ describe("useRefreshKeysDialog Hook", () => {
});
vi.mocked(useMessageStore).mockReturnValue({
activeChat: "chat-123"
activeChat: "chat-123",
});
});
@@ -46,7 +46,9 @@ describe("useRefreshKeysDialog Hook", () => {
getNodeErrorMock.mockReturnValue({ node: "node-abc" });
const { result } = renderHook(() => useRefreshKeysDialog());
act(() => { result.current.handleNodeRemove(); });
act(() => {
result.current.handleNodeRemove();
});
expect(getNodeErrorMock).toHaveBeenCalledTimes(1);
expect(getNodeErrorMock).toHaveBeenCalledWith("chat-123");
@@ -60,7 +62,9 @@ describe("useRefreshKeysDialog Hook", () => {
it("handleNodeRemove should do nothing if there is no error", () => {
const { result } = renderHook(() => useRefreshKeysDialog());
act(() => { result.current.handleNodeRemove(); });
act(() => {
result.current.handleNodeRemove();
});
expect(getNodeErrorMock).toHaveBeenCalledTimes(1);
expect(getNodeErrorMock).toHaveBeenCalledWith("chat-123");
@@ -79,4 +83,4 @@ describe("useRefreshKeysDialog Hook", () => {
expect(setDialogOpenMock).toHaveBeenCalledTimes(1);
expect(setDialogOpenMock).toHaveBeenCalledWith("refreshKeys", false);
});
});
});
@@ -3,11 +3,12 @@ import { useDevice } from "@core/stores/deviceStore.ts";
import { useMessageStore } from "@core/stores/messageStore/index.ts";
export function useRefreshKeysDialog() {
const { removeNode, setDialogOpen, clearNodeError, getNodeError } = useDevice();
const { removeNode, setDialogOpen, clearNodeError, getNodeError } =
useDevice();
const { activeChat } = useMessageStore();
const handleCloseDialog = useCallback(() => {
setDialogOpen('refreshKeys', false);
setDialogOpen("refreshKeys", false);
}, [setDialogOpen]);
const handleNodeRemove = useCallback(() => {
@@ -22,6 +23,6 @@ export function useRefreshKeysDialog() {
return {
handleCloseDialog,
handleNodeRemove
handleNodeRemove,
};
}
}
@@ -26,8 +26,8 @@ export const TracerouteResponseDialog = ({
const { getNode } = useDevice();
const route: number[] = traceroute?.data.route ?? [];
const routeBack: number[] = traceroute?.data.routeBack ?? [];
const snrTowards = (traceroute?.data.snrTowards ?? []).map(snr => snr / 4);
const snrBack = (traceroute?.data.snrBack ?? []).map(snr => snr / 4);
const snrTowards = (traceroute?.data.snrTowards ?? []).map((snr) => snr / 4);
const snrBack = (traceroute?.data.snrBack ?? []).map((snr) => snr / 4);
const from = getNode(traceroute?.from ?? 0);
const longName = from?.user?.longName ??
(from ? `!${numberToHexUnpadded(from?.num)}` : "Unknown");
@@ -1,6 +1,6 @@
// deno-lint-ignore-file
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { UnsafeRolesDialog } from "@components/Dialog/UnsafeRolesDialog/UnsafeRolesDialog.tsx";
import { eventBus } from "@core/utils/eventBus.ts";
import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
@@ -10,41 +10,58 @@ describe("UnsafeRolesDialog", () => {
setDialogOpen: vi.fn(),
};
const renderWithDeviceContext = (ui: any) => {
const renderWithDeviceContext = (ui: React.ReactNode) => {
return render(
<DeviceWrapper device={mockDevice}>
{ui}
</DeviceWrapper>
</DeviceWrapper>,
);
};
it("renders the dialog when open is true", () => {
renderWithDeviceContext(<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />);
renderWithDeviceContext(
<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />,
);
const dialog = screen.getByRole('dialog');
const dialog = screen.getByRole("dialog");
expect(dialog).toBeInTheDocument();
expect(screen.getByText(/I have read the/i)).toBeInTheDocument();
expect(screen.getByText(/understand the implications/i)).toBeInTheDocument();
expect(screen.getByText(/understand the implications/i))
.toBeInTheDocument();
const links = screen.getAllByRole('link');
const links = screen.getAllByRole("link");
expect(links).toHaveLength(2);
expect(links[0]).toHaveTextContent('Device Role Documentation');
expect(links[1]).toHaveTextContent('Choosing The Right Device Role');
expect(links[0]).toHaveTextContent("Device Role Documentation");
expect(links[1]).toHaveTextContent("Choosing The Right Device Role");
});
it("displays the correct links", () => {
renderWithDeviceContext(<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />);
renderWithDeviceContext(
<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />,
);
const docLink = screen.getByRole("link", { name: /Device Role Documentation/i });
const blogLink = screen.getByRole("link", { name: /Choosing The Right Device Role/i });
const docLink = screen.getByRole("link", {
name: /Device Role Documentation/i,
});
const blogLink = screen.getByRole("link", {
name: /Choosing The Right Device Role/i,
});
expect(docLink).toHaveAttribute("href", "https://meshtastic.org/docs/configuration/radio/device/");
expect(blogLink).toHaveAttribute("href", "https://meshtastic.org/blog/choosing-the-right-device-role/");
expect(docLink).toHaveAttribute(
"href",
"https://meshtastic.org/docs/configuration/radio/device/",
);
expect(blogLink).toHaveAttribute(
"href",
"https://meshtastic.org/blog/choosing-the-right-device-role/",
);
});
it("does not allow confirmation until checkbox is checked", () => {
renderWithDeviceContext(<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />);
renderWithDeviceContext(
<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />,
);
const confirmButton = screen.getByRole("button", { name: /confirm/i });
@@ -58,27 +75,37 @@ describe("UnsafeRolesDialog", () => {
it("emits the correct event when closing via close button", () => {
const eventSpy = vi.spyOn(eventBus, "emit");
renderWithDeviceContext(<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />);
renderWithDeviceContext(
<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />,
);
const dismissButton = screen.getByRole("button", { name: /close/i });
fireEvent.click(dismissButton);
expect(eventSpy).toHaveBeenCalledWith("dialog:unsafeRoles", { action: "dismiss" });
expect(eventSpy).toHaveBeenCalledWith("dialog:unsafeRoles", {
action: "dismiss",
});
});
it("emits the correct event when dismissing", () => {
const eventSpy = vi.spyOn(eventBus, "emit");
renderWithDeviceContext(<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />);
renderWithDeviceContext(
<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />,
);
const dismissButton = screen.getByRole("button", { name: /dismiss/i });
fireEvent.click(dismissButton);
expect(eventSpy).toHaveBeenCalledWith("dialog:unsafeRoles", { action: "dismiss" });
expect(eventSpy).toHaveBeenCalledWith("dialog:unsafeRoles", {
action: "dismiss",
});
});
it("emits the correct event when confirming", () => {
const eventSpy = vi.spyOn(eventBus, "emit");
renderWithDeviceContext(<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />);
renderWithDeviceContext(
<UnsafeRolesDialog open={true} onOpenChange={vi.fn()} />,
);
const checkbox = screen.getByRole("checkbox");
const confirmButton = screen.getByRole("button", { name: /confirm/i });
@@ -86,6 +113,8 @@ describe("UnsafeRolesDialog", () => {
fireEvent.click(checkbox);
fireEvent.click(confirmButton);
expect(eventSpy).toHaveBeenCalledWith("dialog:unsafeRoles", { action: "confirm" });
expect(eventSpy).toHaveBeenCalledWith("dialog:unsafeRoles", {
action: "confirm",
});
});
});
@@ -19,29 +19,40 @@ export interface RouterRoleDialogProps {
onOpenChange: (open: boolean) => void;
}
export const UnsafeRolesDialog = ({ open, onOpenChange }: RouterRoleDialogProps) => {
export const UnsafeRolesDialog = (
{ open, onOpenChange }: RouterRoleDialogProps,
) => {
const [confirmState, setConfirmState] = useState(false);
const { setDialogOpen } = useDevice();
const deviceRoleLink = "https://meshtastic.org/docs/configuration/radio/device/";
const choosingTheRightDeviceRoleLink = "https://meshtastic.org/blog/choosing-the-right-device-role/";
const deviceRoleLink =
"https://meshtastic.org/docs/configuration/radio/device/";
const choosingTheRightDeviceRoleLink =
"https://meshtastic.org/blog/choosing-the-right-device-role/";
const handleCloseDialog = (action: 'confirm' | 'dismiss') => {
setDialogOpen('unsafeRoles', false);
const handleCloseDialog = (action: "confirm" | "dismiss") => {
setDialogOpen("unsafeRoles", false);
setConfirmState(false);
eventBus.emit('dialog:unsafeRoles', { action });
}
eventBus.emit("dialog:unsafeRoles", { action });
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-8 flex flex-col">
<DialogClose onClick={() => handleCloseDialog('dismiss')} />
<DialogClose onClick={() => handleCloseDialog("dismiss")} />
<DialogHeader>
<DialogTitle>Are you sure?</DialogTitle>
</DialogHeader>
<DialogDescription className="text-md">
I have read the <Link href={deviceRoleLink} className="">Device Role Documentation</Link>{" "}
and the blog post about <Link href={choosingTheRightDeviceRoleLink}>Choosing The Right Device Role</Link> and understand the implications of changing the role.
I have read the{" "}
<Link href={deviceRoleLink} className="">
Device Role Documentation
</Link>{" "}
and the blog post about{" "}
<Link href={choosingTheRightDeviceRoleLink}>
Choosing The Right Device Role
</Link>{" "}
and understand the implications of changing the role.
</DialogDescription>
<div className="flex items-center gap-2">
<Checkbox
@@ -56,16 +67,20 @@ export const UnsafeRolesDialog = ({ open, onOpenChange }: RouterRoleDialogProps)
<Button
variant="default"
name="dismiss"
onClick={() => handleCloseDialog('dismiss')}> Dismiss
onClick={() => handleCloseDialog("dismiss")}
>
Dismiss
</Button>
<Button
variant="default"
name="confirm"
disabled={!confirmState}
onClick={() => handleCloseDialog('confirm')}> Confirm
onClick={() => handleCloseDialog("confirm")}
>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog >
</Dialog>
);
};
@@ -1,9 +1,12 @@
import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
import { renderHook } from '@testing-library/react';
import { useUnsafeRolesDialog, UNSAFE_ROLES } from "@components/Dialog/UnsafeRolesDialog/useUnsafeRolesDialog.ts";
import { afterEach, beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { renderHook } from "@testing-library/react";
import {
UNSAFE_ROLES,
useUnsafeRolesDialog,
} from "@components/Dialog/UnsafeRolesDialog/useUnsafeRolesDialog.ts";
import { eventBus } from "@core/utils/eventBus.ts";
vi.mock('@core/utils/eventBus', () => ({
vi.mock("@core/utils/eventBus", () => ({
eventBus: {
on: vi.fn(),
off: vi.fn(),
@@ -15,13 +18,13 @@ const mockDevice = {
setDialogOpen: vi.fn(),
};
vi.mock('@core/stores/deviceStore', () => ({
vi.mock("@core/stores/deviceStore", () => ({
useDevice: () => ({
setDialogOpen: mockDevice.setDialogOpen,
}),
}));
describe('useUnsafeRolesDialog', () => {
describe("useUnsafeRolesDialog", () => {
beforeEach(() => {
vi.resetAllMocks();
});
@@ -34,84 +37,115 @@ describe('useUnsafeRolesDialog', () => {
return renderHook(() => useUnsafeRolesDialog());
};
describe('handleCloseDialog', () => {
it('should call setDialogOpen with correct parameters when dialog is closed', () => {
describe("handleCloseDialog", () => {
it("should call setDialogOpen with correct parameters when dialog is closed", () => {
const { result } = renderUnsafeRolesHook();
result.current.handleCloseDialog();
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith('unsafeRoles', false);
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith(
"unsafeRoles",
false,
);
});
});
describe('validateRoleSelection', () => {
it('should resolve with true for safe roles without opening dialog', async () => {
describe("validateRoleSelection", () => {
it("should resolve with true for safe roles without opening dialog", async () => {
const { result } = renderUnsafeRolesHook();
const safeRole = 'SAFE_ROLE';
const safeRole = "SAFE_ROLE";
const validationResult = await result.current.validateRoleSelection(safeRole);
const validationResult = await result.current.validateRoleSelection(
safeRole,
);
expect(validationResult).toBe(true);
expect(mockDevice.setDialogOpen).not.toHaveBeenCalled();
});
it('should open dialog for unsafe roles and resolve with true when confirmed', async () => {
it("should open dialog for unsafe roles and resolve with true when confirmed", async () => {
const { result } = renderUnsafeRolesHook();
const validationPromise = result.current.validateRoleSelection(UNSAFE_ROLES[0]);
const validationPromise = result.current.validateRoleSelection(
UNSAFE_ROLES[0],
);
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith('unsafeRoles', true);
expect(eventBus.on).toHaveBeenCalledWith('dialog:unsafeRoles', expect.any(Function));
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith(
"unsafeRoles",
true,
);
expect(eventBus.on).toHaveBeenCalledWith(
"dialog:unsafeRoles",
expect.any(Function),
);
const onHandler = (eventBus.on as Mock).mock.calls[0][1];
onHandler({ action: 'confirm' });
onHandler({ action: "confirm" });
const validationResult = await validationPromise;
expect(validationResult).toBe(true);
expect(eventBus.off).toHaveBeenCalledWith('dialog:unsafeRoles', onHandler);
expect(eventBus.off).toHaveBeenCalledWith(
"dialog:unsafeRoles",
onHandler,
);
});
it('should resolve with false when user dismisses the dialog', async () => {
it("should resolve with false when user dismisses the dialog", async () => {
const { result } = renderUnsafeRolesHook();
const validationPromise = result.current.validateRoleSelection(UNSAFE_ROLES[0]);
const validationPromise = result.current.validateRoleSelection(
UNSAFE_ROLES[0],
);
const onHandler = (eventBus.on as Mock).mock.calls[0][1];
onHandler({ action: 'dismiss' });
onHandler({ action: "dismiss" });
const validationResult = await validationPromise;
expect(validationResult).toBe(false);
expect(eventBus.off).toHaveBeenCalledWith('dialog:unsafeRoles', onHandler);
expect(eventBus.off).toHaveBeenCalledWith(
"dialog:unsafeRoles",
onHandler,
);
});
it('should clean up event listener after response', async () => {
it("should clean up event listener after response", async () => {
const { result } = renderUnsafeRolesHook();
const validationPromise = result.current.validateRoleSelection(UNSAFE_ROLES[1]);
const validationPromise = result.current.validateRoleSelection(
UNSAFE_ROLES[1],
);
const onHandler = (eventBus.on as Mock).mock.calls[0][1];
onHandler({ action: 'confirm' });
onHandler({ action: "confirm" });
await validationPromise;
expect(eventBus.off).toHaveBeenCalledWith('dialog:unsafeRoles', onHandler);
expect(eventBus.off).toHaveBeenCalledWith(
"dialog:unsafeRoles",
onHandler,
);
});
});
it('should work with all unsafe roles', async () => {
it("should work with all unsafe roles", async () => {
const { result } = renderUnsafeRolesHook();
for (const unsafeRole of UNSAFE_ROLES) {
mockDevice.setDialogOpen.mockClear();
(eventBus.on as Mock).mockClear();
const validationPromise = result.current.validateRoleSelection(unsafeRole);
const validationPromise = result.current.validateRoleSelection(
unsafeRole,
);
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith('unsafeRoles', true);
expect(mockDevice.setDialogOpen).toHaveBeenCalledWith(
"unsafeRoles",
true,
);
const onHandler = (eventBus.on as Mock).mock.calls[0][1];
onHandler({ action: 'confirm' });
onHandler({ action: "confirm" });
const validationResult = await validationPromise;
expect(validationResult).toBe(true);
}
});
});
});
@@ -21,7 +21,9 @@ export const useUnsafeRolesDialog = () => {
setDialogOpen("unsafeRoles", true);
return new Promise((resolve) => {
const handleResponse = ({ action }: { action: "confirm" | "dismiss" }) => {
const handleResponse = (
{ action }: { action: "confirm" | "dismiss" },
) => {
eventBus.off("dialog:unsafeRoles", handleResponse);
resolve(action === "confirm");
};
@@ -29,7 +31,7 @@ export const useUnsafeRolesDialog = () => {
eventBus.on("dialog:unsafeRoles", handleResponse);
});
},
[setDialogOpen]
[setDialogOpen],
);
return {