diff --git a/frontend/src/ResolveForecastDialog.test.tsx b/frontend/src/ResolveForecastDialog.test.tsx
index 81845c8b..b987e20b 100644
--- a/frontend/src/ResolveForecastDialog.test.tsx
+++ b/frontend/src/ResolveForecastDialog.test.tsx
@@ -3,6 +3,9 @@ import {Forecast, Resolution} from "./__generated__/graphql";
import {client} from "./client";
import {ApolloProvider} from "@apollo/client";
import {ResolveForecastDialog} from "./ResolveForecastDialog";
+import userEvent from "@testing-library/user-event";
+import {server} from "./mocks/server";
+import {graphql} from "msw";
function addDays(date: Date, days: number) {
const result = new Date(date);
@@ -60,3 +63,214 @@ test('the resolve dialog is displayed', () => {
expect(screen.getByText("No")).toBeInTheDocument();
expect(screen.getByText("No correct outcome (resolve forecast as N/A)")).toBeInTheDocument();
});
+
+type ResolveForecastRequest = {
+ variables: {
+ forecastId: string,
+ resolution: string,
+ correctOutcomeId: string,
+ };
+}
+
+test('forecast can be resolved with outcome', async () => {
+ const user = userEvent.setup();
+ let requestBody: ResolveForecastRequest | undefined;
+
+ server.use(
+ graphql.mutation("resolveForecast", async (req, res, ctx) => {
+ requestBody = await req.json();
+ return res(
+ ctx.data({
+ "resolveForecast": {
+ "id": "999",
+ "title": "Mock title",
+ "resolution": "RESOLVED",
+ "resolves": addDays(now, 30),
+ "closes": null,
+ "estimates": [
+ {
+ "id": "e01",
+ probabilities: [
+ {
+ id: "p01",
+ outcome: {
+ id: "o01",
+ text: "Yes",
+ correct: true,
+ },
+ },
+ {
+ id: "p02",
+ outcome: {
+ id: "o02",
+ text: "No",
+ correct: false,
+ },
+ },
+ ],
+ },
+ ],
+ "__typename": "Forecast"
+ }
+ }),
+ )
+ }),
+ );
+
+ const now = new Date();
+ const forecast: Forecast = {
+ id: "f01",
+ resolution: Resolution.Unresolved,
+ resolves: addDays(now, 30),
+ title: "Will it rain tomorrow?",
+ created: now,
+ description: "If it rains continuously for more than 30 minutes, that" +
+ " counts as rain.",
+ estimates: [
+ {
+ id: "e01",
+ reason: "The weather report says so",
+ created: now,
+ probabilities: [
+ {
+ id: "p01",
+ value: 20,
+ outcome: {
+ id: "o01",
+ text: "Yes",
+ correct: false,
+ },
+ },
+ {
+ id: "p02",
+ value: 80,
+ outcome: {
+ id: "o02",
+ text: "No",
+ correct: false,
+ },
+ },
+ ],
+ },
+ ],
+ };
+
+ let handleCloseCalled = false;
+
+ render(
+
+ handleCloseCalled=true} />
+
+ );
+
+ expect(screen.getByRole("heading", {name: "Will it rain tomorrow?"})).toBeInTheDocument();
+ await user.click(screen.getByLabelText('Yes'));
+ await user.click(screen.getByRole("button", {name: "Save"}));
+
+ expect(handleCloseCalled).toBe(true);
+
+ expect(requestBody).toBeTruthy();
+ if (!requestBody) {
+ return
+ }
+ expect(requestBody.variables.forecastId).toBe("f01");
+ expect(requestBody.variables.correctOutcomeId).toBe("o01");
+ // The API assumes RESOLVED when no resolution is passed
+ expect(requestBody.variables.resolution).toBeUndefined();
+});
+
+test('error is displayed', async () => {
+ const user = userEvent.setup();
+ let requestBody: ResolveForecastRequest | undefined;
+
+ server.use(
+ graphql.mutation("resolveForecast", async (req, res, ctx) => {
+ requestBody = await req.json();
+ return res(
+ ctx.errors([
+ {
+ name: "whatever",
+ message: "API test error",
+ }
+ ])
+ )
+ }),
+ );
+
+ const now = new Date();
+ const forecast: Forecast = {
+ id: "f01",
+ resolution: Resolution.Unresolved,
+ resolves: addDays(now, 30),
+ title: "Will it rain tomorrow?",
+ created: now,
+ description: "If it rains continuously for more than 30 minutes, that" +
+ " counts as rain.",
+ estimates: [
+ {
+ id: "e01",
+ reason: "The weather report says so",
+ created: now,
+ probabilities: [
+ {
+ id: "p01",
+ value: 20,
+ outcome: {
+ id: "o01",
+ text: "Yes",
+ correct: false,
+ },
+ },
+ {
+ id: "p02",
+ value: 80,
+ outcome: {
+ id: "o02",
+ text: "No",
+ correct: false,
+ },
+ },
+ ],
+ },
+ ],
+ };
+
+ let handleCloseCalled = false;
+
+ render(
+
+ handleCloseCalled=true} />
+
+ );
+
+ expect(screen.getByRole("heading", {name: "Will it rain tomorrow?"})).toBeInTheDocument();
+ await user.click(screen.getByLabelText('Yes'));
+ await user.click(screen.getByRole("button", {name: "Save"}));
+
+ // on error the dialog should not close
+ expect(handleCloseCalled).toBe(false);
+
+ const errFinder = (content: string, element: Element | null): boolean => {
+ const message = "ApolloError: API test error";
+ if (element &&
+ element.tagName.toLowerCase() === 'p' &&
+ element.textContent === message
+ ) {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(element).toHaveStyle("color: red");
+ return true;
+ }
+ return false;
+ }
+
+ expect(await screen.findByText(errFinder)).toBeTruthy();
+
+ expect(requestBody).toBeTruthy();
+ if (!requestBody) {
+ return
+ }
+ expect(requestBody.variables.forecastId).toBe("f01");
+ expect(requestBody.variables.correctOutcomeId).toBe("o01");
+ // The API assumes RESOLVED when no resolution is passed
+ expect(requestBody.variables.resolution).toBeUndefined();
+});
diff --git a/frontend/src/ResolveForecastDialog.tsx b/frontend/src/ResolveForecastDialog.tsx
index 305b8430..e924b308 100644
--- a/frontend/src/ResolveForecastDialog.tsx
+++ b/frontend/src/ResolveForecastDialog.tsx
@@ -83,19 +83,22 @@ export const ResolveForecastDialog: FC<{
correctOutcomeId: outcome,
}
}
- resolveForecastMutation({variables: queryVars})
- .then(value => {
- if (!value.errors) {
- handleClose();
- }
- })
- .catch(reason => {
- console.log("error resolveForecastMutation catch", reason);
- });
+ void resolveForecastMutation({variables: queryVars});
}
- const [resolveForecastMutation, {error}] = useMutation(RESOLVE_FORECAST);
+ const [resolveForecastMutation, {error}] = useMutation<
+ ResolveForecastMutation,
+ ResolveForecastMutationVariables>(
+ RESOLVE_FORECAST,
+ {
+ onCompleted: _ => handleClose(),
+ // it seems to be necessary to call onError so that the
+ // 'error' variable works correctly.
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
+ onError: _ => {},
+ },
+ );
let messageBox:JSX.Element = <>>;
if (error) {