Got the eslint configuration inside the pre-commit figured out.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-09-20 17:00:39 -04:00
parent 5664f9d799
commit 72ea9eeab2
17 changed files with 657 additions and 525 deletions

View File

@@ -3,10 +3,8 @@ repos:
hooks:
- id: eslint-frontend
name: ESLint
entry: yarn --cwd frontend eslint .
entry: yarn --cwd frontend node lint-staged.js
language: system
types: [javascript]
files: ^frontend/src/
- id: black
name: black
entry: black

View File

@@ -143,6 +143,9 @@ def precommit(session: Session) -> None:
)
session.run("pre-commit", *args)
# Run frontend lint using yarn lint
import os
os.chdir("../frontend")
session.run("yarn", "lint", external=True)
if args and args[0] == "install":
activate_virtualenv_in_precommit_hooks(session)

1032
backend/poetry.lock generated

File diff suppressed because it is too large Load Diff

8
frontend/lint-staged.js Normal file
View File

@@ -0,0 +1,8 @@
// lint-staged.js
const { execSync } = require("child_process");
const files = process.argv
.slice(2)
.filter((f) => /^src\/.*\.(js|jsx|ts|tsx)$/.test(f));
if (files.length) {
execSync(`yarn eslint ${files.join(" ")}`, { stdio: "inherit" });
}

View File

@@ -37,6 +37,7 @@
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"lint": "eslint 'src/**/*.{ts,tsx}'",
"eject": "react-scripts eject"
},
"eslintConfig": {

View File

@@ -25,16 +25,24 @@ describe("BookingForm", () => {
readyState = 0;
url = "";
withCredentials = false;
close() {}
addEventListener() {}
removeEventListener() {}
close() {
/* mock implementation */
}
addEventListener() {
/* mock implementation */
}
removeEventListener() {
/* mock implementation */
}
dispatchEvent() {
return true;
}
onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null;
onerror: ((this: EventSource, ev: Event) => any) | null = null;
onopen: ((this: EventSource, ev: Event) => any) | null = null;
onmessage: ((this: EventSource, ev: MessageEvent) => unknown) | null =
null;
onerror: ((this: EventSource, ev: Event) => unknown) | null = null;
onopen: ((this: EventSource, ev: Event) => unknown) | null = null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.EventSource = EventSourceMock as any;
});
it("renders the booking form dialog when open", () => {
@@ -42,7 +50,12 @@ describe("BookingForm", () => {
<RoomProvider>
<BookingProvider>
<UserProvider>
<BookingForm open={true} onClose={() => {}} />
<BookingForm
open={true}
onClose={() => {
/* mock implementation */
}}
/>
</UserProvider>
</BookingProvider>
</RoomProvider>

View File

@@ -11,7 +11,15 @@ describe("RoomDetailsModal", () => {
equipment: "TV",
capacity: 10,
};
render(<RoomDetailsModal open={true} onClose={() => {}} room={room} />);
render(
<RoomDetailsModal
open={true}
onClose={() => {
/* mock implementation */
}}
room={room}
/>
);
expect(screen.getByRole("dialog")).toBeInTheDocument();
expect(screen.getByText(/Alpha Room/i)).toBeInTheDocument();
});

View File

@@ -14,7 +14,13 @@ describe("RoomList", () => {
},
];
render(
<RoomList rooms={rooms} selectedRoomId={1} onSelectRoom={() => {}} />
<RoomList
rooms={rooms}
selectedRoomId={1}
onSelectRoom={() => {
/* mock implementation */
}}
/>
);
expect(screen.getByText(/Alpha Room/i)).toBeInTheDocument();
});

View File

@@ -15,7 +15,12 @@ describe("RoomSelect", () => {
];
render(
<RoomProvider value={{ rooms, fetchRooms: jest.fn() }}>
<RoomSelect selectedRoomId={"1"} onChange={() => {}} />
<RoomSelect
selectedRoomId={"1"}
onChange={() => {
/* mock implementation */
}}
/>
</RoomProvider>
);
expect(screen.getByLabelText(/room/i)).toBeInTheDocument();

View File

@@ -26,16 +26,24 @@ describe("BookingPage", () => {
readyState = 0;
url = "";
withCredentials = false;
close() {}
addEventListener() {}
removeEventListener() {}
close() {
/* mock implementation */
}
addEventListener() {
/* mock implementation */
}
removeEventListener() {
/* mock implementation */
}
dispatchEvent() {
return true;
}
onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null;
onerror: ((this: EventSource, ev: Event) => any) | null = null;
onopen: ((this: EventSource, ev: Event) => any) | null = null;
onmessage: ((this: EventSource, ev: MessageEvent) => unknown) | null =
null;
onerror: ((this: EventSource, ev: Event) => unknown) | null = null;
onopen: ((this: EventSource, ev: Event) => unknown) | null = null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.EventSource = EventSourceMock as any;
});
it("renders booking page heading", () => {

View File

@@ -25,16 +25,24 @@ describe("ConfirmationPage", () => {
readyState = 0;
url = "";
withCredentials = false;
close() {}
addEventListener() {}
removeEventListener() {}
close() {
/* mock implementation */
}
addEventListener() {
/* mock implementation */
}
removeEventListener() {
/* mock implementation */
}
dispatchEvent() {
return true;
}
onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null;
onerror: ((this: EventSource, ev: Event) => any) | null = null;
onopen: ((this: EventSource, ev: Event) => any) | null = null;
onmessage: ((this: EventSource, ev: MessageEvent) => unknown) | null =
null;
onerror: ((this: EventSource, ev: Event) => unknown) | null = null;
onopen: ((this: EventSource, ev: Event) => unknown) | null = null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.EventSource = EventSourceMock as any;
});
it("renders confirmation page heading", async () => {

View File

@@ -27,16 +27,24 @@ describe("LandingPage", () => {
readyState = 0;
url = "";
withCredentials = false;
close() {}
addEventListener() {}
removeEventListener() {}
close() {
/* mock implementation */
}
addEventListener() {
/* mock implementation */
}
removeEventListener() {
/* mock implementation */
}
dispatchEvent() {
return true;
}
onmessage: ((this: EventSource, ev: MessageEvent) => any) | null = null;
onerror: ((this: EventSource, ev: Event) => any) | null = null;
onopen: ((this: EventSource, ev: Event) => any) | null = null;
onmessage: ((this: EventSource, ev: MessageEvent) => unknown) | null =
null;
onerror: ((this: EventSource, ev: Event) => unknown) | null = null;
onopen: ((this: EventSource, ev: Event) => unknown) | null = null;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
window.EventSource = EventSourceMock as any;
});
it("renders landing page heading", () => {

View File

@@ -215,7 +215,7 @@ const BookingForm: React.FC<BookingFormProps> = ({
// Handlers
function handleStartChange(value: string) {
setStart(value);
let interval = customInterval ?? ENV.DEFAULT_BOOKING_INTERVAL_MINUTES;
const interval = customInterval ?? ENV.DEFAULT_BOOKING_INTERVAL_MINUTES;
const newEnd = new Date(new Date(value).getTime() + interval * 60000);
setEnd(newEnd.toISOString());
validateFields();

View File

@@ -51,9 +51,7 @@ const CalendarView: React.FC<CalendarViewProps> = ({
};
}, []);
useEffect(() => {
// [CalendarView] events updated log removed
}, [events]);
// [CalendarView] events updated log removed
// Custom event content with tooltip
const renderEventContent = (arg: { event: BookingEvent }) => {
const { event } = arg;
@@ -124,8 +122,8 @@ const CalendarView: React.FC<CalendarViewProps> = ({
// Always show full 24h grid in week/day view
const calendarRef = React.useRef<FullCalendar | null>(null);
let slotMinTime = "00:00:00";
let slotMaxTime = "24:00:00";
const slotMinTime = "00:00:00";
const slotMaxTime = "24:00:00";
const businessHours = [
{
daysOfWeek: [1, 2, 3, 4, 5, 6, 0], // all days
@@ -179,8 +177,6 @@ const CalendarView: React.FC<CalendarViewProps> = ({
});
}
}}
// @ts-ignore: dateClick is a valid prop for FullCalendar, but types may be missing
// @ts-ignore: dateClick is a valid prop for FullCalendar, but types may be missing
dateClick={(info: { date: Date }) => {
if (onSlotSelect) {
// Simulate a slot selection for the whole day in month view

View File

@@ -46,6 +46,6 @@ export function getEventDisplayText(
* @returns CSS class string
*/
export function getRoomClass(roomId: number): string {
let idx = roomId ? roomId % 20 : 0;
const idx = roomId ? roomId % 20 : 0;
return `room-color-${idx}`;
}

View File

@@ -183,9 +183,9 @@ const BookingPage: React.FC = () => {
// 1. Use selected day, but round to nearest quarter-hour of current time (all in user's local timezone)
const now = new Date();
// Always construct selectedDay in local time using year/month/day from the selected date
let baseDate = slotInfo.start ? new Date(slotInfo.start) : new Date();
const baseDate = slotInfo.start ? new Date(slotInfo.start) : new Date();
// Round current time to nearest quarter hour
let minutes = now.getMinutes();
const minutes = now.getMinutes();
let roundedMinutes = Math.round(minutes / 15) * 15;
let hour = now.getHours();
if (roundedMinutes === 60) {
@@ -193,7 +193,7 @@ const BookingPage: React.FC = () => {
roundedMinutes = 0;
}
// Construct selectedDay as local time: new Date(year, month, day, hour, minute, 0, 0)
let selectedDay = new Date(
const selectedDay = new Date(
baseDate.getFullYear(),
baseDate.getMonth(),
baseDate.getDate(),

View File

@@ -66,7 +66,7 @@ const ConfirmationPage: React.FC = () => {
* Subscribe to SSE for live booking status updates.
*/
React.useEffect(() => {
let bookingId = location.state?.booking?.id || booking?.id;
const bookingId = location.state?.booking?.id || booking?.id;
if (!bookingId) return;
// Always get latest booking from context
const latestBooking = bookings.find((b: Booking) => b.id === bookingId);