Skip to content

Commit a8b0515

Browse files
authored
Merge pull request meshtastic#480 from danditomaso/issue-479-node-connecting-on-https
fix: restored https toggle functionality. added tests
2 parents b1cf4ef + bd9d599 commit a8b0515

File tree

8 files changed

+423
-2269
lines changed

8 files changed

+423
-2269
lines changed

bun.lock

-2,058
This file was deleted.

deno.lock

+280-169
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
},
8181
"devDependencies": {
8282
"@tailwindcss/postcss": "^4.0.9",
83+
"@testing-library/jest-dom": "^6.6.3",
8384
"@testing-library/react": "^16.2.0",
8485
"@types/chrome": "^0.0.307",
8586
"@types/js-cookie": "^3.0.6",
@@ -92,8 +93,8 @@
9293
"@vitejs/plugin-react": "^4.3.4",
9394
"autoprefixer": "^10.4.20",
9495
"gzipper": "^8.2.0",
95-
"happy-dom": "^17.1.8",
9696
"postcss": "^8.5.3",
97+
"jsdom": "^26.0.0",
9798
"simple-git-hooks": "^2.11.1",
9899
"tailwind-merge": "^3.0.2",
99100
"tailwindcss": "^4.0.9",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { describe, it, vi, expect } from "vitest";
2+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
3+
import { HTTP } from "@components/PageComponents/Connect/HTTP.tsx";
4+
import { TransportHTTP } from "@meshtastic/transport-http";
5+
import { MeshDevice } from "@meshtastic/core";
6+
7+
vi.mock("@core/stores/appStore.ts", () => ({
8+
useAppStore: vi.fn(() => ({ setSelectedDevice: vi.fn() })),
9+
}));
10+
11+
vi.mock("@core/stores/deviceStore.ts", () => ({
12+
useDeviceStore: vi.fn(() => ({ addDevice: vi.fn(() => ({ addConnection: vi.fn() })) })),
13+
}));
14+
15+
vi.mock("@core/utils/randId.ts", () => ({
16+
randId: vi.fn(() => "mock-id"),
17+
}));
18+
19+
vi.mock("@meshtastic/transport-http", () => ({
20+
TransportHTTP: {
21+
create: vi.fn(() => Promise.resolve({})),
22+
},
23+
}));
24+
25+
vi.mock("@meshtastic/core", () => ({
26+
MeshDevice: vi.fn(() => ({
27+
configure: vi.fn(),
28+
})),
29+
}));
30+
31+
32+
describe("HTTP Component", () => {
33+
it("renders correctly", () => {
34+
render(<HTTP closeDialog={vi.fn()} />);
35+
expect(screen.getByText("IP Address/Hostname")).toBeInTheDocument();
36+
expect(screen.getByRole("textbox")).toBeInTheDocument();
37+
expect(screen.getByPlaceholderText("000.000.000.000 / meshtastic.local")).toBeInTheDocument();
38+
expect(screen.getByText("Use HTTPS")).toBeInTheDocument();
39+
expect(screen.getByRole("button", { name: "Connect" })).toBeInTheDocument();
40+
});
41+
42+
it("allows input field to be updated", () => {
43+
render(<HTTP closeDialog={vi.fn()} />);
44+
const input = screen.getByRole("textbox");
45+
fireEvent.change(input, { target: { value: "meshtastic.local" } });
46+
expect(input).toHaveValue("meshtastic.local");
47+
});
48+
49+
it("toggles HTTPS switch and updates prefix", () => {
50+
render(<HTTP closeDialog={vi.fn()} />);
51+
const switchInput = screen.getByRole("switch");
52+
expect(screen.getByText("http://")).toBeInTheDocument();
53+
fireEvent.click(switchInput);
54+
expect(screen.getByText("https://")).toBeInTheDocument();
55+
56+
fireEvent.click(switchInput);
57+
expect(switchInput).not.toBeChecked();
58+
expect(screen.getByText("http://")).toBeInTheDocument();
59+
});
60+
61+
it("enables HTTPS toggle when location protocol is https", () => {
62+
Object.defineProperty(globalThis, "location", {
63+
value: { protocol: "https:" },
64+
writable: true,
65+
});
66+
67+
render(<HTTP closeDialog={vi.fn()} />);
68+
69+
const switchInput = screen.getByRole("switch");
70+
expect(switchInput).toBeChecked();
71+
});
72+
73+
it.skip("submits form and triggers connection process", () => {
74+
// This will need further work to test, as it involves a lot of other plumbing mocking
75+
const closeDialog = vi.fn();
76+
render(<HTTP closeDialog={closeDialog} />);
77+
78+
const button = screen.getByRole("button", { name: "Connect" });
79+
expect(button).not.toBeDisabled();
80+
81+
fireEvent.click(button);
82+
83+
waitFor(() => {
84+
expect(button).toBeDisabled();
85+
expect(closeDialog).toHaveBeenCalled();
86+
expect(TransportHTTP.create).toHaveBeenCalled();
87+
expect(MeshDevice).toHaveBeenCalled();
88+
});
89+
});
90+
});
91+

src/components/PageComponents/Connect/HTTP.tsx

+36-36
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,43 @@ import { randId } from "@core/utils/randId.ts";
1010
import { MeshDevice } from "@meshtastic/core";
1111
import { TransportHTTP } from "@meshtastic/transport-http";
1212
import { useState } from "react";
13-
import { Controller, useForm } from "react-hook-form";
13+
import { useForm, useController } from "react-hook-form";
14+
import { FieldWrapper } from "@components/Form/FormWrapper.tsx";
15+
16+
interface FormData {
17+
ip: string;
18+
tls: boolean;
19+
}
1420

1521
export const HTTP = ({ closeDialog }: TabElementProps) => {
16-
const [https, setHTTPS] = useState(false);
1722
const { addDevice } = useDeviceStore();
1823
const { setSelectedDevice } = useAppStore();
19-
const { register, handleSubmit, control } = useForm<{
20-
ip: string;
21-
tls: boolean;
22-
}>({
24+
const { control, handleSubmit, register } = useForm<FormData>({
2325
defaultValues: {
2426
ip: ["client.meshtastic.org", "localhost"].includes(
2527
globalThis.location.hostname,
2628
)
2729
? "meshtastic.local"
2830
: globalThis.location.host,
29-
tls: location.protocol === "https:",
31+
tls: false,
3032
},
3133
});
3234

35+
const {
36+
field: { value: tlsValue, onChange: setTLS },
37+
} = useController({ name: "tls", control });
38+
3339
const [connectionInProgress, setConnectionInProgress] = useState(false);
3440

3541
const onSubmit = handleSubmit(async (data) => {
36-
setConnectionInProgress(true);
42+
console.log(data);
3743

44+
setConnectionInProgress(true);
3845
const id = randId();
3946
const device = addDevice(id);
4047
const transport = await TransportHTTP.create(data.ip, data.tls);
4148
const connection = new MeshDevice(transport, id);
4249
connection.configure();
43-
4450
setSelectedDevice(id);
4551
device.addConnection(connection);
4652
subscribeAll(device, connection);
@@ -50,32 +56,26 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
5056
return (
5157
<form className="flex w-full flex-col gap-2 p-4" onSubmit={onSubmit}>
5258
<div className="flex h-48 flex-col gap-2">
53-
<Label>IP Address/Hostname</Label>
54-
<Input
55-
prefix={https ? "https://" : "http://"}
56-
placeholder="000.000.000.000 / meshtastic.local"
57-
className="text-slate-900 dark:text-slate-900"
58-
disabled={connectionInProgress}
59-
{...register("ip")}
60-
/>
61-
<Controller
62-
name="tls"
63-
control={control}
64-
render={({ field: { ...rest } }) => (
65-
<>
66-
<Label>Use HTTPS</Label>
67-
<Switch
68-
onCheckedChange={(checked: boolean) => {
69-
checked ? setHTTPS(true) : setHTTPS(false);
70-
}}
71-
disabled={location.protocol === "https:" ||
72-
connectionInProgress}
73-
checked={https}
74-
{...rest}
75-
/>
76-
</>
77-
)}
78-
/>
59+
<div>
60+
<Label>IP Address/Hostname</Label>
61+
<Input
62+
prefix={tlsValue ? "https://" : "http://"}
63+
placeholder="000.000.000.000 / meshtastic.local"
64+
className="text-slate-900 dark:text-slate-900"
65+
disabled={connectionInProgress}
66+
{...register("ip")}
67+
/>
68+
</div>
69+
<div className="flex items-center gap-2 mt-2">
70+
<Switch
71+
onCheckedChange={setTLS}
72+
disabled={location.protocol === "https:" || connectionInProgress}
73+
checked={location.protocol === 'https:' || tlsValue}
74+
{...register("tls")}
75+
/>
76+
<Label>Use HTTPS</Label>
77+
78+
</div>
7979
</div>
8080
<Button
8181
type="submit"
@@ -86,4 +86,4 @@ export const HTTP = ({ closeDialog }: TabElementProps) => {
8686
</Button>
8787
</form>
8888
);
89-
};
89+
};

src/components/UI/Input.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ const inputVariants = cva(
2121

2222
export interface InputProps
2323
extends
24-
React.InputHTMLAttributes<HTMLInputElement>,
25-
VariantProps<typeof inputVariants> {
24+
React.InputHTMLAttributes<HTMLInputElement>,
25+
VariantProps<typeof inputVariants> {
2626
prefix?: string;
2727
suffix?: string;
2828
action?: {
@@ -36,9 +36,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
3636
return (
3737
<div className="relative w-full">
3838
{prefix && (
39-
<span className="inline-flex items-center rounded-l-md bg-slate-100/80 px-3 font-mono text-sm text-slate-600">
39+
<label className="inline-flex items-center rounded-l-md bg-slate-100/80 px-3 font-mono text-sm text-slate-600">
4040
{prefix}
41-
</span>
41+
</label>
4242
)}
4343
<input
4444
className={cn(

src/tests/setupTests.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import "@testing-library/jest-dom";
2+
3+
globalThis.ResizeObserver = class {
4+
observe() { }
5+
unobserve() { }
6+
disconnect() { }
7+
};

vite.config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ export default defineConfig({
4747
exclude: ['react-scan']
4848
},
4949
test: {
50-
environment: 'happy-dom',
50+
environment: 'jsdom',
5151
globals: true,
5252
include: ['**/*.{test,spec}.{ts,tsx}'],
53+
setupFiles: ["./src/tests/setupTests.ts"],
54+
5355
}
5456
});

0 commit comments

Comments
 (0)