Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
cb13f0d
SSES Playground
joshlarson Feb 25, 2026
503745b
Move `PredictionsConsumerStage` to a separate file
joshlarson Apr 21, 2026
b643015
Add UpcomingDeparturesPubSub GenServer
joshlarson Apr 22, 2026
0f83595
Add live view to inspect the UpcomingDeparturesPubSub
joshlarson Apr 22, 2026
b71eb7a
Fix alias order
joshlarson Apr 22, 2026
1e0aa39
Add a subscribeable supervisor (and shut it down when it's not needed!)
joshlarson Apr 23, 2026
e343164
Add a broadcaster (that doesn't broadcast)
joshlarson Apr 25, 2026
2490fd4
Add `PredictionsWorker` and `PredictionsManager`
joshlarson Apr 27, 2026
0c9832f
Add `PredictionsSupervisor` and `PredictionsBroadcasterStage`
joshlarson Apr 27, 2026
b91729e
Refine `PredictionBroadcasterStage`
joshlarson Apr 28, 2026
0ee56f1
Remove unnecessary params from `PredictionsWorker`
joshlarson Apr 28, 2026
9dea473
Cleanup PredictionsStreamLive
joshlarson Apr 28, 2026
0d341af
Refactor PredictionsStreamLive
joshlarson Apr 28, 2026
14f4ff7
Restructure how predictions and events are shown in `PredictionsStrea…
joshlarson Apr 29, 2026
46e8638
fix: Include timezone info in `Prediction.StreamParser` datetimes
joshlarson Apr 29, 2026
09eba16
Show departure time on PredictionsStreamLive
joshlarson Apr 29, 2026
7fb18e1
Send a fake `"reset"` event on subscribe
joshlarson Apr 29, 2026
4f45c96
Show predictions snapshot and derived predictions
joshlarson Apr 30, 2026
b5c2755
Cleanup old modules that we're not using anymore
joshlarson Apr 30, 2026
db908c3
Extract shareable components from PredictionsStreamLive
joshlarson Apr 30, 2026
f1faeaa
Remove defunct `UpcomingDeparturesPubSubStateLive`
joshlarson Apr 30, 2026
9fc7e03
Add new UpcomingDeparturesStreamLive
joshlarson Apr 30, 2026
fc30698
Add UpcomingDeparturesManager and UpcomingDeparturesWorker
joshlarson Apr 30, 2026
5b8fb43
Merge remote-tracking branch 'origin/main' into jdl/sses-playground
joshlarson May 1, 2026
c227163
Don't load schedules for subway
joshlarson May 4, 2026
d07379a
Convert predicted_schedules to upcoming_departures
joshlarson May 4, 2026
98cf84a
Strange formatting
joshlarson May 4, 2026
14fcd63
Use `:hidden` for all upcoming_departures filtering
joshlarson May 4, 2026
296d0ff
Only message upcoming departures consumers when upcoming departures h…
joshlarson May 5, 2026
f3284ed
Use Playground Upcoming Departures in Schedule Finder
joshlarson May 5, 2026
b07a5d5
Add a link to the departures page on the upcoming departures stream page
joshlarson May 5, 2026
e1f06e7
[TMP] feat: Release SF2.0
joshlarson Apr 17, 2026
38d6888
refactor(UpcomingDepartures): pass in predicted schedules
thecristen May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions assets/ts/schedule/components/ScheduleFinder.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { ReactElement } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useSelector } from "react-redux";
import { Dispatch } from "redux";
import { Route, DirectionId } from "../../__v3api";
import {
Expand Down Expand Up @@ -44,43 +44,18 @@ const ScheduleFinder = ({
scheduleNote,
hasServiceToday
}: Props): ReactElement<HTMLElement> => {
const dispatch = useDispatch();
const { modalOpen, selectedOrigin } = useSelector(
(state: StoreProps) => state
);

const currentDirection = useDirectionChangeEvent(directionId);
const openOriginModal = (): void => {
if (!modalOpen) {
dispatch({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "origin"
}
});
}
};

const openScheduleModal = (): void => {
if (selectedOrigin !== undefined && !modalOpen) {
dispatch({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "schedule"
}
});
document.location.href = `/departures/?route_id=${route.id}&direction_id=${directionId}&stop_id=${selectedOrigin}`;
}
};

const handleOriginSelectClick = (): void => {
dispatch({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "origin"
}
});
};

const isFerryRoute = routeToModeName(route) === "ferry";

return (
Expand All @@ -92,7 +67,6 @@ const ScheduleFinder = ({
<ScheduleFinderForm
onDirectionChange={changeDirection}
onOriginChange={changeOrigin}
onOriginSelectClick={openOriginModal}
onSubmit={openScheduleModal}
route={route}
selectedDirection={currentDirection}
Expand All @@ -104,7 +78,6 @@ const ScheduleFinder = ({
closeModal={closeModal}
directionChanged={changeDirection}
initialDirection={currentDirection}
handleOriginSelectClick={handleOriginSelectClick}
originChanged={changeOrigin}
route={route}
routePatternsByDirection={routePatternsByDirection}
Expand Down
17 changes: 0 additions & 17 deletions assets/ts/schedule/components/SchedulePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,6 @@ export const changeOrigin = (
selectedOrigin: origin
}
});
// reopen modal depending on choice:
dispatch({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: origin ? "schedule" : "origin"
}
});
};

export const changeDirection = (
Expand All @@ -88,15 +81,6 @@ const closeModal = (dispatch: Dispatch): void => {
updateURL("");
};

export const handleOriginSelectClick = (dispatch: Dispatch): void => {
dispatch({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "origin"
}
});
};

const getDirectionAndMap = (
schedulePageData: SchedulePageData,
mapData: MapData,
Expand Down Expand Up @@ -239,7 +223,6 @@ const ScheduleNote = ({
closeModal={closeModal}
directionChanged={changeDirection}
initialDirection={currentDirection}
handleOriginSelectClick={handleOriginSelectClick}
originChanged={changeOrigin}
route={route}
routePatternsByDirection={routePatternsByDirection}
Expand Down
44 changes: 1 addition & 43 deletions assets/ts/schedule/components/__tests__/ScheduleFinderTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,47 +256,5 @@ describe("ScheduleFinder", () => {
expect(lastSelectedOriginElement.selected).toBeFalse();
});

it("Opens the origin modal when clicking on the origin drop-down in the schedule modal", async () => {
const user = userEvent.setup();
const dispatchSpy = jest.fn();
jest.spyOn(reactRedux, "useDispatch").mockImplementation(() => dispatchSpy);

renderWithProviders(
<ScheduleFinder
route={route}
stops={stops}
directionId={0}
services={services}
routePatternsByDirection={routePatternsByDirection}
today={today}
updateURL={() => {}}
changeDirection={() => {}}
changeOrigin={() => {}}
closeModal={() => {}}
scheduleNote={null}
hasServiceToday={true}
/>,
{
preloadedState: {
modalOpen: true,
selectedOrigin: "123",
modalMode: "schedule"
}
}
);

// select the last node (i.e. origin drop-down) and choose an option
const scheduleFinderModal = screen.getByLabelText(/Schedules on the.*/);
const originSelectElement = within(scheduleFinderModal).getByTestId(
"schedule-finder-origin-select"
);
await user.click(originSelectElement);

expect(dispatchSpy).toHaveBeenCalledWith({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "origin"
}
});
});

});
171 changes: 3 additions & 168 deletions assets/ts/schedule/components/__tests__/SchedulePageTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import { MapData, StaticMapData } from "../../../leaflet/components/__mapdata";
import {
SchedulePage,
changeOrigin,
changeDirection,
handleOriginSelectClick
changeDirection
} from "../SchedulePage";
import * as schedulePage from "../SchedulePage";
import * as routePatternsByDirectionData from "./test-data/routePatternsByDirectionData.json";
Expand Down Expand Up @@ -575,65 +574,9 @@ describe("SchedulePage", () => {

await user.click(originSelect);

expect(dispatchSpy).toHaveBeenCalledTimes(2);
expect(dispatchSpy).toHaveBeenCalledTimes(1);
});

it("Opens the origin modal", async () => {
const user = userEvent.setup();
const dispatchSpy = jest.fn();
jest.spyOn(reactRedux, "useDispatch").mockImplementation(() => {
return dispatchSpy;
});
renderWithProviders(
<SchedulePage
mapData={mapData}
noBranches={false}
schedulePageData={{
schedule_note: null,
connections: [],
fares,
fare_link: fareLink, // eslint-disable-line camelcase
hours,
holidays,
pdfs,
teasers,
route,
services,
stops,
direction_id: 0,
route_patterns: routePatternsByDirection,
today: "2019-12-05",
stop_tree: stopTreeData,
route_stop_lists: [testRouteStopList],
alerts: [],
"service_today?": true,
variant: null
}}
/>
);

jest.spyOn(reactRedux, "useSelector").mockImplementation(() => {
return {
selectedDirection: 0,
selectedOrigin: "place-welln",
modalMode: "origin",
modalOpen: false
};
});

const buttons = screen.getAllByRole("button");
expect(buttons.length).toBeGreaterThan(1);

await user.click(buttons[1]);

// first call is with INITIALIZE
expect(dispatchSpy).toHaveBeenNthCalledWith(2, {
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "origin"
}
});
});

it("Closes the schedule modal", async () => {
const user = userEvent.setup();
Expand Down Expand Up @@ -732,96 +675,6 @@ describe("SchedulePage", () => {
);
});

it("Opens the origin modal when clicking on the origin drop-down in the schedule modal", async () => {
const user = userEvent.setup();
const changeOriginSpy = jest.spyOn(schedulePage, "handleOriginSelectClick");

renderWithProviders(
<SchedulePage
mapData={mapData}
noBranches={false}
schedulePageData={{
schedule_note: scheduleNoteData,
connections: [],
fares,
fare_link: fareLink, // eslint-disable-line camelcase
hours,
holidays,
pdfs,
teasers,
route,
services,
stops,
direction_id: 0,
route_patterns: routePatternsByDirection,
today: "2019-12-05",
stop_tree: stopTreeData,
route_stop_lists: [testRouteStopList],
alerts: [],
"service_today?": true,
variant: null
}}
/>,
{
preloadedState: {
selectedDirection: 0,
selectedOrigin: "place-welln",
modalMode: "schedule",
modalOpen: true
}
}
);
const originSelect = await waitFor(() =>
screen.getByTestId("schedule-finder-origin-select")
);

await user.click(originSelect);

await waitFor(() =>
expect(changeOriginSpy).toHaveBeenCalledWith(expect.any(Function))
);
});

it("Changes the origin", async () => {
const user = userEvent.setup();
renderWithProviders(
<SchedulePage
mapData={mapData}
noBranches={false}
schedulePageData={{
schedule_note: null,
connections: [],
fares,
fare_link: fareLink, // eslint-disable-line camelcase
hours,
holidays,
pdfs,
teasers,
route,
services,
stops,
direction_id: 0,
route_patterns: routePatternsByDirection,
today: "2019-12-05",
stop_tree: stopTreeData,
route_stop_lists: [testRouteStopList],
alerts: [],
"service_today?": true,
variant: null
}}
/>
);

const dispatchSpy = jest.fn();
jest.spyOn(reactRedux, "useDispatch").mockImplementation(() => {
return dispatchSpy;
});
const originSelect = screen.getByTestId("schedule-finder-origin-select");
await user.selectOptions(originSelect, "123");

expect(dispatchSpy).toHaveBeenCalledTimes(2);
});

it("Checks if it is a unidirectional route", () => {
const dispatchSpy = jest.fn();
jest.spyOn(reactRedux, "useDispatch").mockImplementation(() => {
Expand Down Expand Up @@ -959,7 +812,7 @@ describe("SchedulePage", () => {
});

describe("changeOrigin", () => {
it("should call the dispatch function twice", () => {
it("should call the dispatch function once", () => {
const dispatchSpy = jest.fn();
const testOrigin = "test-origin";
changeOrigin(testOrigin, dispatchSpy);
Expand All @@ -969,12 +822,6 @@ describe("SchedulePage", () => {
selectedOrigin: testOrigin
}
});
expect(dispatchSpy).toHaveBeenCalledWith({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "schedule"
}
});
});
});

Expand All @@ -993,16 +840,4 @@ describe("SchedulePage", () => {
});
});

describe("handleOriginSelectClick", () => {
it("should call the dispatch function setting the new direction in the state", () => {
const dispatchSpy = jest.fn();
handleOriginSelectClick(dispatchSpy);
expect(dispatchSpy).toHaveBeenCalledWith({
type: "OPEN_MODAL",
newStoreValues: {
modalMode: "origin"
}
});
});
});
});
Loading