Skip to content

Commit

Permalink
feature: add tests for onboarding modal input form
Browse files Browse the repository at this point in the history
  • Loading branch information
jamiebrynes7 committed Mar 7, 2024
1 parent 371bb3c commit 3e4e0bf
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 15 deletions.
123 changes: 123 additions & 0 deletions plugin/src/ui/onboardingModal/TokenInputForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { describe, expect, it } from "vitest";
import { TokenInputForm } from "./TokenInputForm";
import { fireEvent, getByRole, render, screen, waitFor } from "@testing-library/react";
import React from "react";

describe("TokenInputForm", () => {
it("should initially have the button be disabled", () => {
render(
<TokenInputForm
onTokenSubmit={() => {}}
testToken={async () => {
return true;
}}
/>,
);

const button = screen.getByRole("button");
expect(button).toHaveAttribute("disabled");
});

it("should initially not show an error", () => {
render(
<TokenInputForm
onTokenSubmit={() => {}}
testToken={async () => {
return true;
}}
/>,
);

const textBox = screen.getByRole("textbox");
expect(textBox).not.toHaveAttribute("data-invalid");
});

it("should show an error if input is empty after focus and unfocus", () => {
render(
<TokenInputForm
onTokenSubmit={() => {}}
testToken={async () => {
return true;
}}
/>,
);

const textBox = screen.getByRole("textbox");
fireEvent.focus(textBox);
fireEvent.blur(textBox);

expect(textBox).toHaveAttribute("data-invalid", "true");
});

it("should shown an error if API test rejects token", async () => {
const [getArgs, testToken] = makeFakeTokenTest(false);
render(<TokenInputForm onTokenSubmit={() => {}} testToken={testToken} />);

const textBox = screen.getByRole("textbox");
fireEvent.focus(textBox);
fireEvent.change(textBox, { target: { value: "abcdef" } });
fireEvent.blur(textBox);

await waitFor(() => {
expect(textBox).toHaveAttribute("data-invalid", "true");
});
expect(getArgs()).toBe("abcdef");
});

it("should enable button if API test accepts token", async () => {
const [getArgs, testToken] = makeFakeTokenTest(true);
render(<TokenInputForm onTokenSubmit={() => {}} testToken={testToken} />);

const textBox = screen.getByRole("textbox");
fireEvent.focus(textBox);
fireEvent.change(textBox, { target: { value: "abcdef" } });
fireEvent.blur(textBox);

await waitFor(() => {
const button = screen.getByRole("button");
expect(button).not.toHaveAttribute("disabled");
});
expect(getArgs()).toBe("abcdef");
});

it("should call callback when submit button is pressed", async () => {
const [_, testToken] = makeFakeTokenTest(true);
let submittedToken = "";
render(
<TokenInputForm
onTokenSubmit={(token: string) => {
submittedToken = token;
}}
testToken={testToken}
/>,
);

const textBox = screen.getByRole("textbox");
const button = screen.getByRole("button");

fireEvent.focus(textBox);
fireEvent.change(textBox, { target: { value: "abcdef" } });
fireEvent.blur(textBox);

await waitFor(() => {
expect(button).not.toHaveAttribute("disabled");
});

fireEvent.click(button);
expect(submittedToken).toBe("abcdef");
});
});

const makeFakeTokenTest: (
result: boolean,
) => [() => string | undefined, (token: string) => Promise<boolean>] = (result) => {
let called: string | undefined = undefined;

return [
() => called,
async (token: string) => {
called = token;
return result;
},
];
};
16 changes: 2 additions & 14 deletions plugin/src/ui/onboardingModal/TokenInputForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React, { useState } from "react";
import { Button, FieldError, Group, Input, Label, TextField } from "react-aria-components";
import { TodoistApiClient } from "../../api";
import { ObsidianFetcher } from "../../api/fetcher";
import { ObsidianIcon } from "../components/obsidian-icon";

const TokenInputValidationIcon: React.FC<{ status: TokenValidationStatus }> = ({ status }) => {
Expand All @@ -25,9 +23,10 @@ type TokenValidationStatus =

type Props = {
onTokenSubmit: (token: string) => void;
testToken: (token: string) => Promise<boolean>;
};

export const TokenInputForm: React.FC<Props> = ({ onTokenSubmit }) => {
export const TokenInputForm: React.FC<Props> = ({ onTokenSubmit, testToken }) => {
const [token, setToken] = useState<string>("");
const [validationStatus, setValidationStatus] = useState<TokenValidationStatus>({ kind: "none" });

Expand Down Expand Up @@ -80,14 +79,3 @@ export const TokenInputForm: React.FC<Props> = ({ onTokenSubmit }) => {
</div>
);
};

const testToken = async (token: string) => {
const api = new TodoistApiClient(token, new ObsidianFetcher());

try {
await api.getProjects();
return true;
} catch (e) {
return false;
}
};
15 changes: 14 additions & 1 deletion plugin/src/ui/onboardingModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React from "react";
import { type Root, createRoot } from "react-dom/client";
import { TokenInputForm } from "./TokenInputForm";
import "./styles.scss";
import { TodoistApiClient } from "../../api";
import { ObsidianFetcher } from "../../api/fetcher";

type OnTokenSubmitted = (token: string) => Promise<void>;

Expand Down Expand Up @@ -54,7 +56,18 @@ const ModalRoot: React.FC<Props> = ({ onTokenSubmit }) => {
</a>{" "}
on finding your API token.
</p>
<TokenInputForm onTokenSubmit={onTokenSubmit} />
<TokenInputForm onTokenSubmit={onTokenSubmit} testToken={testToken} />
</div>
);
};

const testToken = async (token: string): Promise<boolean> => {
const api = new TodoistApiClient(token, new ObsidianFetcher());

try {
await api.getProjects();
return true;
} catch (e) {
return false;
}
};

0 comments on commit 3e4e0bf

Please sign in to comment.