Skip to content

Commit 86613e3

Browse files
Miodecfehmer
andauthored
refactor: login page (@Miodec) (#7595)
Co-authored-by: Christian Fehmer <fehmer@users.noreply.github.com> Co-authored-by: Christian Fehmer <cfe@sexy-developer.com>
1 parent 6fbc780 commit 86613e3

46 files changed

Lines changed: 1884 additions & 911 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/.oxlintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"rules": {
2222
"explicit-function-return-type": "off",
2323
"no-explicit-any": "off",
24-
"no-unsafe-assignment": "off"
24+
"no-unsafe-assignment": "off",
25+
"no-empty-function": "off"
2526
}
2627
},
2728
{

frontend/__tests__/components/ui/ValidatedInput.spec.tsx

Lines changed: 0 additions & 82 deletions
This file was deleted.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { render, screen, fireEvent } from "@solidjs/testing-library";
2+
import { describe, it, expect, vi } from "vitest";
3+
4+
import { Checkbox } from "../../../../src/ts/components/ui/form/Checkbox";
5+
6+
function makeField(name: string, checked = false) {
7+
return {
8+
name,
9+
state: { value: checked },
10+
handleBlur: vi.fn(),
11+
handleChange: vi.fn(),
12+
} as any;
13+
}
14+
15+
describe("Checkbox", () => {
16+
it("renders with label text", () => {
17+
const field = makeField("agree");
18+
render(() => <Checkbox field={() => field} label="I agree" />);
19+
20+
expect(screen.getByText("I agree")).toBeInTheDocument();
21+
});
22+
23+
it("renders checkbox with field name", () => {
24+
const field = makeField("terms");
25+
render(() => <Checkbox field={() => field} />);
26+
27+
const input = screen.getByRole("checkbox", { hidden: true });
28+
expect(input).toHaveAttribute("id", "terms");
29+
expect(input).toHaveAttribute("name", "terms");
30+
});
31+
32+
it("reflects checked state", () => {
33+
const field = makeField("opt", true);
34+
render(() => <Checkbox field={() => field} />);
35+
36+
const input = screen.getByRole("checkbox", { hidden: true });
37+
expect(input).toBeChecked();
38+
});
39+
40+
it("reflects unchecked state", () => {
41+
const field = makeField("opt", false);
42+
render(() => <Checkbox field={() => field} />);
43+
44+
const input = screen.getByRole("checkbox", { hidden: true });
45+
expect(input).not.toBeChecked();
46+
});
47+
48+
it("calls handleChange on change", async () => {
49+
const field = makeField("opt");
50+
render(() => <Checkbox field={() => field} />);
51+
52+
const input = screen.getByRole("checkbox", { hidden: true });
53+
await fireEvent.change(input, { target: { checked: true } });
54+
expect(field.handleChange).toHaveBeenCalledWith(true);
55+
});
56+
57+
it("calls handleBlur on blur", async () => {
58+
const field = makeField("opt");
59+
render(() => <Checkbox field={() => field} />);
60+
61+
const input = screen.getByRole("checkbox", { hidden: true });
62+
await fireEvent.blur(input);
63+
expect(field.handleBlur).toHaveBeenCalled();
64+
});
65+
66+
it("renders disabled checkbox", () => {
67+
const field = makeField("opt");
68+
render(() => <Checkbox field={() => field} disabled />);
69+
70+
const input = screen.getByRole("checkbox", { hidden: true });
71+
expect(input).toBeDisabled();
72+
});
73+
74+
it("shows check icon styling when checked", () => {
75+
const field = makeField("opt", true);
76+
const { container } = render(() => <Checkbox field={() => field} />);
77+
78+
const icon = container.querySelector(".fa-check");
79+
expect(icon).toBeInTheDocument();
80+
expect(icon).toHaveClass("text-main");
81+
});
82+
83+
it("shows transparent icon styling when unchecked", () => {
84+
const field = makeField("opt", false);
85+
const { container } = render(() => <Checkbox field={() => field} />);
86+
87+
const icon = container.querySelector(".fa-check");
88+
expect(icon).toBeInTheDocument();
89+
expect(icon).toHaveClass("text-transparent");
90+
});
91+
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { render } from "@solidjs/testing-library";
2+
import { describe, it, expect } from "vitest";
3+
4+
import { FieldIndicator } from "../../../../src/ts/components/ui/form/FieldIndicator";
5+
6+
function makeField(overrides: {
7+
isValidating?: boolean;
8+
isTouched?: boolean;
9+
isValid?: boolean;
10+
isDefaultValue?: boolean;
11+
errors?: string[];
12+
hasWarning?: boolean;
13+
warnings?: string[];
14+
}) {
15+
return {
16+
state: {
17+
meta: {
18+
isValidating: overrides.isValidating ?? false,
19+
isTouched: overrides.isTouched ?? false,
20+
isValid: overrides.isValid ?? true,
21+
isDefaultValue: overrides.isDefaultValue ?? true,
22+
errors: overrides.errors ?? [],
23+
},
24+
},
25+
getMeta: () => ({
26+
hasWarning: overrides.hasWarning ?? false,
27+
warnings: overrides.warnings ?? [],
28+
}),
29+
} as any;
30+
}
31+
32+
describe("FieldIndicator", () => {
33+
it("shows loading spinner when validating", () => {
34+
const { container } = render(() => (
35+
<FieldIndicator field={makeField({ isValidating: true })} />
36+
));
37+
expect(container.querySelector(".fa-circle-notch")).toBeInTheDocument();
38+
});
39+
40+
it("shows error icon when touched and invalid", () => {
41+
const { container } = render(() => (
42+
<FieldIndicator
43+
field={makeField({
44+
isTouched: true,
45+
isValid: false,
46+
errors: ["bad value"],
47+
})}
48+
/>
49+
));
50+
expect(container.querySelector(".fa-times")).toBeInTheDocument();
51+
});
52+
53+
it("shows warning icon when has warning", () => {
54+
const { container } = render(() => (
55+
<FieldIndicator
56+
field={makeField({
57+
hasWarning: true,
58+
warnings: ["weak"],
59+
})}
60+
/>
61+
));
62+
expect(
63+
container.querySelector(".fa-exclamation-triangle"),
64+
).toBeInTheDocument();
65+
});
66+
67+
it("shows success check when touched, valid, and not default", () => {
68+
const { container } = render(() => (
69+
<FieldIndicator
70+
field={makeField({
71+
isTouched: true,
72+
isValid: true,
73+
isDefaultValue: false,
74+
})}
75+
/>
76+
));
77+
expect(container.querySelector(".fa-check")).toBeInTheDocument();
78+
});
79+
80+
it("shows nothing when untouched and not validating", () => {
81+
const { container } = render(() => (
82+
<FieldIndicator field={makeField({})} />
83+
));
84+
expect(container.querySelector(".fa-times")).not.toBeInTheDocument();
85+
expect(container.querySelector(".fa-check")).not.toBeInTheDocument();
86+
expect(container.querySelector(".fa-circle-notch")).not.toBeInTheDocument();
87+
});
88+
});
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { render, screen, fireEvent } from "@solidjs/testing-library";
2+
import { describe, it, expect, vi } from "vitest";
3+
4+
import { InputField } from "../../../../src/ts/components/ui/form/InputField";
5+
6+
function makeField(name: string, value = "") {
7+
return {
8+
name,
9+
state: {
10+
value,
11+
meta: {
12+
isValidating: false,
13+
isTouched: false,
14+
isValid: true,
15+
isDefaultValue: true,
16+
errors: [],
17+
},
18+
},
19+
handleBlur: vi.fn(),
20+
handleChange: vi.fn(),
21+
getMeta: () => ({ hasWarning: false, warnings: [] }),
22+
} as any;
23+
}
24+
25+
describe("InputField", () => {
26+
it("renders input with field name as id", () => {
27+
const field = makeField("email");
28+
render(() => <InputField field={() => field} />);
29+
30+
const input = screen.getByRole("textbox");
31+
expect(input).toHaveAttribute("id", "email");
32+
expect(input).toHaveAttribute("name", "email");
33+
});
34+
35+
it("uses field name as default placeholder", () => {
36+
const field = makeField("username");
37+
render(() => <InputField field={() => field} />);
38+
39+
expect(screen.getByPlaceholderText("username")).toBeInTheDocument();
40+
});
41+
42+
it("uses custom placeholder when provided", () => {
43+
const field = makeField("email");
44+
render(() => <InputField field={() => field} placeholder="Enter email" />);
45+
46+
expect(screen.getByPlaceholderText("Enter email")).toBeInTheDocument();
47+
});
48+
49+
it("defaults to text type", () => {
50+
const field = makeField("name");
51+
render(() => <InputField field={() => field} />);
52+
53+
expect(screen.getByRole("textbox")).toHaveAttribute("type", "text");
54+
});
55+
56+
it("uses custom type", () => {
57+
const field = makeField("password");
58+
const { container } = render(() => (
59+
<InputField field={() => field} type="password" />
60+
));
61+
62+
expect(container.querySelector("input")).toHaveAttribute(
63+
"type",
64+
"password",
65+
);
66+
});
67+
68+
it("calls handleChange on input", async () => {
69+
const field = makeField("name");
70+
render(() => <InputField field={() => field} />);
71+
72+
await fireEvent.input(screen.getByRole("textbox"), {
73+
target: { value: "test" },
74+
});
75+
expect(field.handleChange).toHaveBeenCalledWith("test");
76+
});
77+
78+
it("calls handleBlur on blur", async () => {
79+
const field = makeField("name");
80+
render(() => <InputField field={() => field} />);
81+
82+
await fireEvent.blur(screen.getByRole("textbox"));
83+
expect(field.handleBlur).toHaveBeenCalled();
84+
});
85+
86+
it("calls onFocus callback", async () => {
87+
const field = makeField("name");
88+
const onFocus = vi.fn();
89+
render(() => <InputField field={() => field} onFocus={onFocus} />);
90+
91+
await fireEvent.focus(screen.getByRole("textbox"));
92+
expect(onFocus).toHaveBeenCalled();
93+
});
94+
95+
it("renders disabled input", () => {
96+
const field = makeField("name");
97+
render(() => <InputField field={() => field} disabled />);
98+
99+
expect(screen.getByRole("textbox")).toBeDisabled();
100+
});
101+
102+
it("shows FieldIndicator when showIndicator is true", () => {
103+
const field = makeField("name");
104+
field.state.meta.isValidating = true;
105+
const { container } = render(() => (
106+
<InputField field={() => field} showIndicator />
107+
));
108+
109+
expect(container.querySelector(".fa-circle-notch")).toBeInTheDocument();
110+
});
111+
112+
it("hides FieldIndicator by default", () => {
113+
const field = makeField("name");
114+
const { container } = render(() => <InputField field={() => field} />);
115+
116+
expect(container.querySelector(".fa-circle-notch")).not.toBeInTheDocument();
117+
});
118+
});

0 commit comments

Comments
 (0)