Skip to content

Commit

Permalink
Test Coverage (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Charlie ⚡️ committed Oct 26, 2022
1 parent 889cec1 commit 862ceb7
Show file tree
Hide file tree
Showing 14 changed files with 384 additions and 208 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ node_modules
.pnp.js

# testing
/coverage
/apps/ui/coverage

# next.js
.next/
Expand Down
1 change: 1 addition & 0 deletions apps/ui/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const customJestConfig = {
// if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
moduleDirectories: ["node_modules", "<rootDir>/"],
testEnvironment: "jest-environment-jsdom",
coveragePathIgnorePatterns: ["/node_modules/", "lib/__generated__.ts", "lib/graphql.ts"],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
Expand Down
201 changes: 26 additions & 175 deletions apps/ui/src/components/auth-forms.tsx
Original file line number Diff line number Diff line change
@@ -1,185 +1,36 @@
import { useRouter } from "next/router";
import { Tab } from "@headlessui/react";
import z from "zod";
import { Label, Input, TextLabel } from "./ui/input";
import { login, register } from "../lib/graphql";
import { LoginForm } from "./login-form";
import { RegisterForm } from "./register-form";
import { classNames } from "./ui/class-names";
import { useAuthAtom } from "../hooks/useAuth";
import { useForm } from "./ui/forms/core";

const validationSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8),
});

export function LoginForm() {
const [, updateAtom] = useAuthAtom();
const router = useRouter();
const { errors, isSubmitting, getFieldProps, getFormProps, getErrorProps } = useForm(
{
initialValues: {
email: "",
password: "",
},
validationSchema,
onSubmit(values) {
login(values.email, values.password).then(({ login: { user, token } }) => {
if (user && token) {
updateAtom({ token });
router.push("/dashboard");
}
});
},
},
{
validateOnEvent: "blur",
}
);

return (
<form {...getFormProps()} className="space-y-6">
<div>
<Label htmlFor="email">
<TextLabel>Email</TextLabel>
<Input
disabled={isSubmitting}
type="email"
id="email"
autoComplete="email"
required
data-testid="login-email-input"
{...getFieldProps("email")}
/>
{errors["email"] && <div {...getErrorProps("email")}>{errors["email"]}</div>}
</Label>
</div>

<div>
<Label htmlFor="password">
<TextLabel>Password</TextLabel>
<Input
disabled={isSubmitting}
id="password"
type="password"
autoComplete="current-password"
required
data-testid="login-password-input"
{...getFieldProps("password")}
/>
{errors["password"] && (
<div {...getErrorProps("password")}>{errors["password"]}</div>
)}
</Label>
</div>

<div>
<button
type="submit"
disabled={isSubmitting || Object.keys(errors).length > 0}
className="flex w-full justify-center rounded-md border border-transparent bg-sky-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2">
Login
</button>
</div>
</form>
);
}

export function RegisterForm() {
const [, updateAtom] = useAuthAtom();
const router = useRouter();
const { errors, isSubmitting, getFieldProps, getFormProps, getErrorProps } = useForm(
{
initialValues: {
email: "",
password: "",
},
validationSchema,
onSubmit(values) {
register(values.email, values.password).then((value) => {
if (value.createUser.user && value.createUser.token) {
updateAtom({
token: value.createUser.token,
});

router.push("/dashboard");
}
});
},
},
{
validateOnEvent: "blur",
}
);

return (
<form {...getFormProps()} className="space-y-6">
<div>
<Label htmlFor="email">
<TextLabel>Email</TextLabel>
<Input
disabled={isSubmitting}
type="email"
id="email"
autoComplete="email"
required
data-testid="login-email-input"
{...getFieldProps("email")}
/>
{errors["email"] && <div {...getErrorProps("email")}>{errors["email"]}</div>}
</Label>
</div>

<div>
<Label htmlFor="password">
<TextLabel>Password</TextLabel>
<Input
disabled={isSubmitting}
id="password"
type="password"
autoComplete="current-password"
required
data-testid="login-email-input"
{...getFieldProps("password")}
/>
{errors["password"] && (
<div {...getErrorProps("password")}>{errors["password"]}</div>
)}
</Label>
</div>

<div>
<button
disabled={isSubmitting}
type="submit"
className="flex w-full justify-center rounded-md border border-transparent bg-sky-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2">
Register
</button>
</div>
</form>
);
}

function MagicTab({ children }: { children: React.ReactNode }) {
return (
<Tab
className={({ selected }) =>
classNames(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-sky-700",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-400 focus:outline-none focus:ring-2",
selected ? "bg-white shadow" : "text-sky-100 hover:bg-white/[0.12] hover:text-white"
)
}>
{children}
</Tab>
);
}

export function AuthenticationTabs() {
return (
<Tab.Group as="div">
<Tab.List className="mb-4 flex space-x-1 rounded-xl bg-sky-900/20 p-1">
<MagicTab>Login</MagicTab>
<MagicTab>Register</MagicTab>
<Tab
className={({ selected }) =>
classNames(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-sky-700",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-400 focus:outline-none focus:ring-2",
selected
? "bg-white shadow"
: "text-sky-100 hover:bg-white/[0.12] hover:text-white"
)
}>
Login
</Tab>
<Tab
className={({ selected }) =>
classNames(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-sky-700",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-sky-400 focus:outline-none focus:ring-2",
selected
? "bg-white shadow"
: "text-sky-100 hover:bg-white/[0.12] hover:text-white"
)
}>
Register
</Tab>
</Tab.List>
<Tab.Panels className="rounded-md p-4 dark:bg-zinc-800">
<Tab.Panel>
Expand Down
7 changes: 6 additions & 1 deletion apps/ui/src/components/feed-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { removeFeed, updateFeedTitle, getAllTags, getFeed } from "../lib/graphql
import type { FeedDetailsFragment, TagInfoFragment } from "../lib/__generated__";
import { useDashboardContext } from "../hooks/useDashboard";
import { TagSelectionList } from "./tag-lists";
import { useEventCallback } from "../hooks/useEventCallback";

interface FeedSettingsFormProps {
onSubmit(title: string, tagID?: string | null): void | Promise<void>;
Expand Down Expand Up @@ -139,6 +140,10 @@ export function FeedSettings() {
[feed]
);

const handleClose = useEventCallback(() => {
setOpen(false);
});

return (
<>
<Button aria-label="Update feed" onClick={() => setOpen(true)}>
Expand All @@ -147,7 +152,7 @@ export function FeedSettings() {
{data && data.feed && (
<Dialog
isOpen={isOpen}
onClose={() => setOpen(false)}
onClose={handleClose}
title={`Update feed "${data.feed.title}"`}>
<UpdateFeedForm
initialFeed={data.feed}
Expand Down
83 changes: 83 additions & 0 deletions apps/ui/src/components/login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useRouter } from "next/router";
import z from "zod";
import { Label, Input, TextLabel } from "./ui/input";
import { login } from "../lib/graphql";
import { useAuthAtom } from "../hooks/useAuth";
import { useForm } from "./ui/forms/core";

const validationSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8),
});

export function LoginForm() {
const [, , { loginWithToken }] = useAuthAtom();
const router = useRouter();
const { errors, isSubmitting, getFieldProps, getFormProps, getErrorProps } = useForm(
{
initialValues: {
email: "",
password: "",
},
validationSchema,
onSubmit(values) {
login(values.email, values.password).then(({ login: { user, token } }) => {
if (user && token) {
loginWithToken(token);
router.push("/dashboard");
}
});
},
},
{
validateOnEvent: "blur",
}
);

return (
<form {...getFormProps()} className="space-y-6">
<div>
<Label htmlFor="email">
<TextLabel>Email</TextLabel>
<Input
disabled={isSubmitting}
type="email"
id="email"
autoComplete="email"
required
data-testid="login-email-input"
{...getFieldProps("email")}
/>
{errors["email"] && <div {...getErrorProps("email")}>{errors["email"]}</div>}
</Label>
</div>

<div>
<Label htmlFor="password">
<TextLabel>Password</TextLabel>
<Input
disabled={isSubmitting}
id="password"
type="password"
autoComplete="current-password"
required
data-testid="login-password-input"
{...getFieldProps("password")}
/>
{errors["password"] && (
<div {...getErrorProps("password")}>{errors["password"]}</div>
)}
</Label>
</div>

<div>
<button
type="submit"
disabled={isSubmitting || Object.keys(errors).length > 0}
className="flex w-full justify-center rounded-md border border-transparent bg-sky-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2">
Login
</button>
</div>
</form>
);
}
Loading

1 comment on commit 862ceb7

@vercel
Copy link

@vercel vercel bot commented on 862ceb7 Oct 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.