mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-09 12:46:10 -05:00
can add suggestion comment + tests
This commit is contained in:
@@ -30,7 +30,7 @@ const PlusHomePage: React.FC<PlusHomePageProps> = () => {
|
||||
<>
|
||||
{plusStatusData && plusStatusData.membershipTier && (
|
||||
<SuggestionVouchModal
|
||||
canSuggest={!ownSuggestion}
|
||||
canSuggest={!suggestionsLoading && !ownSuggestion}
|
||||
canVouch={!!plusStatusData.canVouchFor}
|
||||
userPlusMembershipTier={plusStatusData.membershipTier}
|
||||
/>
|
||||
@@ -63,7 +63,7 @@ const PlusHomePage: React.FC<PlusHomePageProps> = () => {
|
||||
<SubText mr={2}>+2</SubText> ({suggestionCounts.TWO})
|
||||
</Flex>
|
||||
</Radio>
|
||||
<Radio value="THREE">
|
||||
<Radio value="THREE" data-cy="plus-three-radio">
|
||||
<Flex align="center">
|
||||
<SubText mr={2}>+3</SubText> ({suggestionCounts.THREE})
|
||||
</Flex>
|
||||
|
||||
@@ -16,7 +16,7 @@ import useMutation from "hooks/useMutation";
|
||||
import { getFullUsername } from "lib/strings";
|
||||
import { Unpacked } from "lib/types";
|
||||
import {
|
||||
suggestionSchema,
|
||||
resuggestionSchema,
|
||||
SUGGESTION_DESCRIPTION_LIMIT,
|
||||
} from "lib/validators/suggestion";
|
||||
import { useState } from "react";
|
||||
@@ -24,7 +24,7 @@ import { useForm } from "react-hook-form";
|
||||
import { Suggestions } from "services/plus";
|
||||
import * as z from "zod";
|
||||
|
||||
type FormData = z.infer<typeof suggestionSchema>;
|
||||
type FormData = z.infer<typeof resuggestionSchema>;
|
||||
|
||||
const Suggestion = ({
|
||||
suggestion,
|
||||
@@ -34,19 +34,14 @@ const Suggestion = ({
|
||||
canSuggest: boolean;
|
||||
}) => {
|
||||
const [showTextarea, setShowTextarea] = useState(false);
|
||||
const { handleSubmit, errors, register, watch, control } = useForm<FormData>({
|
||||
resolver: zodResolver(suggestionSchema),
|
||||
defaultValues: {
|
||||
// region doesn't matter as it is not updated after the first suggestion
|
||||
region: "NA",
|
||||
tier: suggestion.tier,
|
||||
suggestedId: suggestion.suggestedUser.id,
|
||||
},
|
||||
const { handleSubmit, errors, register, watch } = useForm<FormData>({
|
||||
resolver: zodResolver(resuggestionSchema),
|
||||
});
|
||||
const { onSubmit, sending } = useMutation({
|
||||
route: "plus/suggestions",
|
||||
mutationKey: "plus/suggestions",
|
||||
successText: "Comment added",
|
||||
onSuccess: () => setShowTextarea(false),
|
||||
});
|
||||
|
||||
const watchDescription = watch("description", "");
|
||||
@@ -92,23 +87,44 @@ const Suggestion = ({
|
||||
size="sm"
|
||||
onClick={() => setShowTextarea(!showTextarea)}
|
||||
mt={4}
|
||||
data-cy="comment-button"
|
||||
>
|
||||
Add comment
|
||||
</Button>
|
||||
)}
|
||||
{showTextarea && (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form
|
||||
onSubmit={handleSubmit((values) =>
|
||||
onSubmit({
|
||||
...values,
|
||||
// region doesn't matter as it is not updated after the first suggestion
|
||||
region: "NA",
|
||||
tier: suggestion.tier,
|
||||
suggestedId: suggestion.suggestedUser.id,
|
||||
})
|
||||
)}
|
||||
>
|
||||
<FormControl isInvalid={!!errors.description}>
|
||||
<FormLabel htmlFor="description" mt={4}>
|
||||
Comment to suggestion
|
||||
</FormLabel>
|
||||
<Textarea name="description" ref={register} />
|
||||
<Textarea
|
||||
name="description"
|
||||
ref={register}
|
||||
data-cy="comment-textarea"
|
||||
/>
|
||||
<FormHelperText mb={4}>
|
||||
{(watchDescription ?? "").length}/{SUGGESTION_DESCRIPTION_LIMIT}
|
||||
</FormHelperText>
|
||||
<FormErrorMessage>{errors.description?.message}</FormErrorMessage>
|
||||
</FormControl>
|
||||
<Button size="sm" mr={3} type="submit" isLoading={sending}>
|
||||
<Button
|
||||
size="sm"
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={sending}
|
||||
data-cy="submit-button"
|
||||
>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
suggestionSchema,
|
||||
suggestionFullSchema,
|
||||
SUGGESTION_DESCRIPTION_LIMIT,
|
||||
} from "lib/validators/suggestion";
|
||||
import * as z from "zod";
|
||||
@@ -31,7 +31,7 @@ interface Props {
|
||||
userPlusMembershipTier?: number;
|
||||
}
|
||||
|
||||
type FormData = z.infer<typeof suggestionSchema>;
|
||||
type FormData = z.infer<typeof suggestionFullSchema>;
|
||||
|
||||
const SuggestionVouchModal: React.FC<Props> = ({
|
||||
canVouch,
|
||||
@@ -40,10 +40,10 @@ const SuggestionVouchModal: React.FC<Props> = ({
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { handleSubmit, errors, register, watch, control } = useForm<FormData>({
|
||||
resolver: zodResolver(suggestionSchema),
|
||||
resolver: zodResolver(suggestionFullSchema),
|
||||
});
|
||||
const { onSubmit, sending } = useMutation({
|
||||
onClose: () => setIsOpen(false),
|
||||
onSuccess: () => setIsOpen(false),
|
||||
route: "plus/suggestions",
|
||||
mutationKey: "plus/suggestions",
|
||||
successText: "New suggestion submitted",
|
||||
@@ -64,7 +64,12 @@ const SuggestionVouchModal: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" mb={4} onClick={() => setIsOpen(true)}>
|
||||
<Button
|
||||
size="sm"
|
||||
mb={4}
|
||||
onClick={() => setIsOpen(true)}
|
||||
data-cy="suggestion-button"
|
||||
>
|
||||
{getButtonText()}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
@@ -84,7 +89,7 @@ const SuggestionVouchModal: React.FC<Props> = ({
|
||||
<Controller
|
||||
name="tier"
|
||||
control={control}
|
||||
defaultValue={1}
|
||||
defaultValue={userPlusMembershipTier}
|
||||
render={({ value, onChange }) => (
|
||||
<Select
|
||||
value={value}
|
||||
@@ -122,7 +127,11 @@ const SuggestionVouchModal: React.FC<Props> = ({
|
||||
|
||||
<FormControl>
|
||||
<FormLabel mt={4}>Region</FormLabel>
|
||||
<Select name="region" ref={register}>
|
||||
<Select
|
||||
name="region"
|
||||
ref={register}
|
||||
data-cy="region-select"
|
||||
>
|
||||
<option value="NA">NA</option>
|
||||
<option value="EU">EU</option>
|
||||
</Select>
|
||||
@@ -136,7 +145,11 @@ const SuggestionVouchModal: React.FC<Props> = ({
|
||||
<FormLabel htmlFor="description" mt={4}>
|
||||
Description
|
||||
</FormLabel>
|
||||
<Textarea name="description" ref={register} />
|
||||
<Textarea
|
||||
name="description"
|
||||
ref={register}
|
||||
data-cy="description-textarea"
|
||||
/>
|
||||
<FormHelperText>
|
||||
{(watchDescription ?? "").length}/
|
||||
{SUGGESTION_DESCRIPTION_LIMIT}
|
||||
@@ -147,7 +160,12 @@ const SuggestionVouchModal: React.FC<Props> = ({
|
||||
</FormControl>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button mr={3} type="submit" isLoading={sending}>
|
||||
<Button
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={sending}
|
||||
data-cy="submit-button"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button onClick={() => setIsOpen(false)} variant="outline">
|
||||
|
||||
@@ -14,10 +14,46 @@ context("Plus Voting History", () => {
|
||||
});
|
||||
|
||||
context("Plus Home Page", () => {
|
||||
beforeEach(() => {
|
||||
cy.login("sendou");
|
||||
it("can filter through suggestions not logged in", () => {
|
||||
cy.visit("/plus");
|
||||
cy.contains("yooo so cracked").dataCy("plus-three-radio").click();
|
||||
|
||||
cy.contains("yooo so cracked").should("not.exist");
|
||||
});
|
||||
|
||||
it.only("correctly calculates voting percentage", () => {});
|
||||
it("can submit new suggestion and persists with reload", () => {
|
||||
cy.login("sendou");
|
||||
cy.visit("/plus");
|
||||
cy.dataCy("suggestion-button")
|
||||
.click()
|
||||
.get(".select__value-container")
|
||||
.type("NZAP{enter}")
|
||||
.dataCy("region-select")
|
||||
.select("EU")
|
||||
.dataCy("description-textarea")
|
||||
.type("always trust in nzap")
|
||||
.dataCy("submit-button")
|
||||
.click();
|
||||
|
||||
cy.contains("always trust in nzap")
|
||||
.reload()
|
||||
.contains("always trust in nzap")
|
||||
.dataCy("suggestion-button")
|
||||
.should("not.exist");
|
||||
});
|
||||
|
||||
it("can add comment to suggestion and toast shows", () => {
|
||||
cy.login("sendou");
|
||||
cy.visit("/plus");
|
||||
cy.dataCy("comment-button")
|
||||
.click()
|
||||
.dataCy("comment-textarea")
|
||||
.type("yes agreed")
|
||||
.dataCy("submit-button")
|
||||
.click();
|
||||
|
||||
cy.contains("Comment added");
|
||||
cy.contains('"yes agreed" - Sendou#4059');
|
||||
cy.dataCy("comment-button").should("not.exist");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,3 +11,7 @@ Cypress.Commands.add("login", (user: "sendou" | "nzap") => {
|
||||
cy.intercept("/api/auth/session", u);
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.exec("npm run seed");
|
||||
});
|
||||
|
||||
@@ -8,12 +8,12 @@ import { useUser } from "./common";
|
||||
const useMutation = ({
|
||||
route,
|
||||
mutationKey,
|
||||
onClose,
|
||||
onSuccess,
|
||||
successText,
|
||||
}: {
|
||||
route: string;
|
||||
mutationKey: string;
|
||||
onClose?: () => void;
|
||||
onSuccess?: () => void;
|
||||
successText: string;
|
||||
}) => {
|
||||
const toast = useToast();
|
||||
@@ -42,7 +42,7 @@ const useMutation = ({
|
||||
mutate("/api/" + mutationKey);
|
||||
|
||||
toast(getToastOptions(successText, "success"));
|
||||
onClose?.();
|
||||
onSuccess?.();
|
||||
};
|
||||
|
||||
return { onSubmit, sending };
|
||||
|
||||
@@ -2,9 +2,14 @@ import * as z from "zod";
|
||||
|
||||
export const SUGGESTION_DESCRIPTION_LIMIT = 500;
|
||||
|
||||
export const suggestionSchema = z.object({
|
||||
const suggestionRootSchema = z.object({
|
||||
description: z.string().max(SUGGESTION_DESCRIPTION_LIMIT).min(10),
|
||||
});
|
||||
|
||||
export const suggestionFullSchema = suggestionRootSchema.extend({
|
||||
suggestedId: z.number().int(),
|
||||
tier: z.number().int().min(1).max(3),
|
||||
region: z.enum(["NA", "EU"]),
|
||||
});
|
||||
|
||||
export const resuggestionSchema = suggestionRootSchema;
|
||||
|
||||
@@ -72,7 +72,7 @@ export const getPlusSuggestionsData = (): Prisma.PlusSuggestionCreateManyInput[]
|
||||
description: "yooo so cracked",
|
||||
region: "NA",
|
||||
tier: 2,
|
||||
suggestedId: 11,
|
||||
suggestedId: 10,
|
||||
suggesterId: 1,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { PlusSuggestion, Prisma } from "@prisma/client";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { UserError } from "lib/errors";
|
||||
import { getPercentageFromCounts } from "lib/plus";
|
||||
import { userBasicSelection } from "lib/prisma";
|
||||
import { suggestionSchema } from "lib/validators/suggestion";
|
||||
import { suggestionFullSchema } from "lib/validators/suggestion";
|
||||
import prisma from "prisma/client";
|
||||
|
||||
export type PlusStatus = Prisma.PromiseReturnType<typeof getPlusStatus>;
|
||||
@@ -178,7 +178,10 @@ const addSuggestion = async ({
|
||||
data: unknown;
|
||||
userId: number;
|
||||
}) => {
|
||||
const parsedData = { ...suggestionSchema.parse(data), suggesterId: userId };
|
||||
const parsedData = {
|
||||
...suggestionFullSchema.parse(data),
|
||||
suggesterId: userId,
|
||||
};
|
||||
const [suggestions, plusStatuses] = await Promise.all([
|
||||
prisma.plusSuggestion.findMany({}),
|
||||
prisma.plusStatus.findMany({}),
|
||||
@@ -203,6 +206,8 @@ const addSuggestion = async ({
|
||||
(status) => status.userId === userId
|
||||
);
|
||||
|
||||
console.log({ suggesterPlusStatus, parsedData });
|
||||
|
||||
if (
|
||||
!suggesterPlusStatus ||
|
||||
!suggesterPlusStatus.membershipTier ||
|
||||
|
||||
Reference in New Issue
Block a user