test setup lackin
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { DeleteIcon } from "lucide-react";
|
||||
import type { UseTRPCMutationOptions, UseTRPCMutationResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { useFormMutationContext } from "./MutationProvider";
|
||||
|
||||
export default function DeleteButton<TD, TE, TV, TC>(params: { mutation: UseTRPCMutationResult<TD, TE, TV, TC>, id?: string }) {
|
||||
export default function DeleteButton(params: { id?: string }) {
|
||||
const ctx = useFormMutationContext()
|
||||
if (ctx == undefined) {
|
||||
throw new Error('Dependent form message needs populates form mutations context')
|
||||
}
|
||||
if (params.id) {
|
||||
return (
|
||||
<Button variant='destructive' onClick={() => params.mutation.mutate(({ id: params.id } as TV))}>
|
||||
<Button variant='destructive' onClick={() => ctx.deleteMutation.mutate(({ id: params.id ? params.id : "" }))}>
|
||||
<DeleteIcon />
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -4,22 +4,16 @@ import { DependentFormMessaage, SubmitButton, DeleteButton } from '~/app/_compon
|
||||
import type { FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form';
|
||||
import type { ReactNode } from 'react';
|
||||
import DependentText from '../../DependentText';
|
||||
import type { UseTRPCMutationResult } from 'node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs';
|
||||
interface Error {
|
||||
message: string
|
||||
}
|
||||
export default function FormScaffold<T extends FieldValues, TD, TE extends Error, TV, TC, TTD, TTE extends Error, TTV, TTC, TTTD, TTTE extends Error, TTTV, TTTC>(params: {
|
||||
|
||||
export default function FormScaffold<T extends FieldValues,>(params: {
|
||||
form: UseFormReturn<T>,
|
||||
onSubmit: SubmitHandler<T>,
|
||||
createMutation: UseTRPCMutationResult<TD, TE, TV, TC>,
|
||||
updateMutation: UseTRPCMutationResult<TTD, TTE, TTV, TTC>,
|
||||
deleteMutation: UseTRPCMutationResult<TTTD, TTTE, TTTV, TTTC>,
|
||||
title: string,
|
||||
children: ReactNode,
|
||||
children?: ReactNode,
|
||||
id?: string,
|
||||
className?: string
|
||||
}) {
|
||||
const { form, onSubmit, createMutation, deleteMutation, updateMutation, title, id, className, children } = params
|
||||
const { form, onSubmit, title, id, className, children } = params
|
||||
return (
|
||||
<Card.Card className={className ? className : "w-5/6 lg:w-1/2"}>
|
||||
<Card.CardHeader>
|
||||
@@ -32,12 +26,13 @@ export default function FormScaffold<T extends FieldValues, TD, TE extends Error
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8"
|
||||
data-testid="form"
|
||||
>
|
||||
{children}
|
||||
<SubmitButton id={id} />
|
||||
<SubmitButton id={id} />
|
||||
<div className='flex flex-row justify-between'>
|
||||
<DependentFormMessaage falseStatus={createMutation.status} trueStatus={updateMutation.status} falseError={createMutation.error} trueError={updateMutation.error} bool={id ? true : false} />
|
||||
<DeleteButton mutation={deleteMutation} id={id} />
|
||||
<DependentFormMessaage bool={id ? true : false} />
|
||||
<DeleteButton id={id} />
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { FormMessage } from "~/components/ui/form";
|
||||
import { useFormMutationContext } from "./MutationProvider";
|
||||
|
||||
interface Error {
|
||||
message: string
|
||||
}
|
||||
|
||||
export default function DependentFormMessaage( params: {trueStatus: string, falseStatus: string, falseError?: Error|null, trueError?:Error|null, bool: boolean} ) {
|
||||
export default function DependentFormMessaage( params: {bool: boolean} ) {
|
||||
const ctx = useFormMutationContext()
|
||||
if (ctx == undefined) {
|
||||
throw new Error('Dependent form message needs populates form mutations context')
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{
|
||||
params.bool ?
|
||||
<FormMessage className={params.trueStatus == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{params.trueError ? params.trueError.message : params.trueStatus}
|
||||
<FormMessage className={ctx.updateMutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{ctx.updateMutation.error ? ctx.updateMutation.error.message : ctx.updateMutation.status}
|
||||
</FormMessage> :
|
||||
<FormMessage className={params.falseStatus == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{params.falseError ? params.falseError.message : params.falseStatus}
|
||||
<FormMessage className={ctx.createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{ctx.createMutation.error ? ctx.createMutation.error.message : ctx.createMutation.status}
|
||||
</FormMessage>
|
||||
}
|
||||
</>
|
||||
|
||||
33
src/app/_components/Form/Components/MutationProvider.tsx
Normal file
33
src/app/_components/Form/Components/MutationProvider.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { UseTRPCMutationResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs";
|
||||
import { createContext, useContext, type ReactNode } from "react";
|
||||
|
||||
interface ToString {
|
||||
toString: () => string
|
||||
message: string
|
||||
}
|
||||
|
||||
|
||||
export interface MutationInterface {
|
||||
mutate: (params:{id:string}) => void
|
||||
error: ToString | null
|
||||
status: "error" | "idle" | "pending" | "success"
|
||||
}
|
||||
|
||||
type FormMutationContext = {
|
||||
createMutation: MutationInterface,
|
||||
updateMutation: MutationInterface,
|
||||
deleteMutation: MutationInterface
|
||||
}
|
||||
|
||||
const FormMutationContext = createContext<FormMutationContext|undefined>(undefined)
|
||||
export function useFormMutationContext() {
|
||||
return useContext(FormMutationContext)
|
||||
}
|
||||
|
||||
export function FormMutationContextProvider(params: {children: ReactNode, value: FormMutationContext}) {
|
||||
return (
|
||||
<FormMutationContext.Provider value={params.value}>
|
||||
{params.children}
|
||||
</FormMutationContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import DependentText from "../../DependentText";
|
||||
|
||||
export default function SubmitButton(params: {id?:string}) {
|
||||
return (
|
||||
<Button type="submit">
|
||||
<Button submit={true}>
|
||||
<DependentText bool={params.id?true:false} true='Update' false='Create' />
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -21,10 +21,8 @@ function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0
|
||||
}
|
||||
const onCheckedItemChange = (key: string) => {
|
||||
return function onCheckedChange(checked: CheckedState) {
|
||||
console.log(key,checked)
|
||||
let state = context.checkedValues;
|
||||
if (checked === "indeterminate") {
|
||||
console.log('checked was intermediate')
|
||||
return
|
||||
} else {
|
||||
state[key] = checked
|
||||
@@ -36,7 +34,6 @@ function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0
|
||||
stateArr.push(key)
|
||||
}
|
||||
}
|
||||
console.log('calling field on change with:', stateArr)
|
||||
params.onChange(stateArr)
|
||||
}
|
||||
}
|
||||
@@ -53,7 +50,7 @@ function InnerMultiBooleanFormField(params: { options: string[], onChange: (arg0
|
||||
<FormItem key={opt}>
|
||||
<div className="flex flex-row justify-between py-2 border-b-1">
|
||||
<FormLabel>{opt}</FormLabel>
|
||||
<Checkbox checked={checked(opt)} onCheckedChange={onCheckedItemChange(opt)} />
|
||||
<Checkbox data-testid="multiboolean-checkbox" checked={checked(opt)} onCheckedChange={onCheckedItemChange(opt)} />
|
||||
</div>
|
||||
</FormItem>
|
||||
))
|
||||
@@ -71,14 +68,14 @@ export default function MultiBooleanFormField<T extends FieldValues>(params: { c
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<PopoverTrigger data-testid="multiboolean-popover-trigger" asChild>
|
||||
<Button variant={'ghost'}>
|
||||
<FormLabel>{params.label}</FormLabel>
|
||||
<ChevronDownIcon/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<FormControl>
|
||||
<PopoverContent>
|
||||
<PopoverContent data-testid="multiboolean-content">
|
||||
<MultiBooleanFieldContext.Provider value={{checkedValues: checkedValues, setCheckedValue: setCheckedValues}}>
|
||||
<InnerMultiBooleanFormField onChange={field.onChange} options={params.options} />
|
||||
</MultiBooleanFieldContext.Provider>
|
||||
|
||||
19
src/app/_components/Test/TestingTest.client.test.tsx
Normal file
19
src/app/_components/Test/TestingTest.client.test.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import TestingTest from "./TestingTest";
|
||||
import { test } from 'vitest'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import { userEvent } from '@testing-library/user-event'
|
||||
import { FormMutationContextProvider } from "../Form/Components/MutationProvider";
|
||||
|
||||
test('TestingTest', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
<TestingTest/>
|
||||
)
|
||||
const submitButton = screen.getByTestId('form-submit')
|
||||
screen.debug()
|
||||
user.click(submitButton)
|
||||
await waitFor(() => {
|
||||
screen.getByTestId('submitted')
|
||||
})
|
||||
screen.debug()
|
||||
})
|
||||
33
src/app/_components/Test/TestingTest.tsx
Normal file
33
src/app/_components/Test/TestingTest.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useForm } from "react-hook-form";
|
||||
import { FormScaffold } from "../Form/Components";
|
||||
import z from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useState } from "react";
|
||||
import { FormMutationContextProvider } from "../Form/Components/MutationProvider";
|
||||
|
||||
export default function TestingTest() {
|
||||
const schema = z.object({ text: z.string() })
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const fakeMutation = {
|
||||
error: {message: "test"},
|
||||
status: "idle" as const,
|
||||
mutate: (_:{id:string}) => {}
|
||||
}
|
||||
const form = useForm<z.infer<typeof schema>>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
text: ""
|
||||
}
|
||||
})
|
||||
const onSubmit = (_: z.infer<typeof schema>) => {
|
||||
setSubmitted(true)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div data-testid={submitted ? "submitted" : "unsubmitted"} />
|
||||
<FormMutationContextProvider value={{ createMutation: fakeMutation, updateMutation: fakeMutation, deleteMutation: fakeMutation }}>
|
||||
<FormScaffold form={form} onSubmit={onSubmit} title="Testing Test" />
|
||||
</FormMutationContextProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
13
src/app/_trpc/GetBaseUrl.ts
Normal file
13
src/app/_trpc/GetBaseUrl.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default function getBaseUrl() {
|
||||
if (typeof window !== 'undefined')
|
||||
// browser should use relative path
|
||||
return '';
|
||||
if (process.env.VERCEL_URL)
|
||||
// reference for vercel.com
|
||||
return `https://${process.env.VERCEL_URL}`;
|
||||
if (process.env.RENDER_INTERNAL_HOSTNAME)
|
||||
// reference for render.com
|
||||
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
|
||||
// assume localhost
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
||||
@@ -4,21 +4,7 @@ import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experime
|
||||
import { httpBatchLink } from "@trpc/client";
|
||||
import React, { useState } from "react"
|
||||
import { trpc } from "./Client";
|
||||
|
||||
function getBaseUrl() {
|
||||
if (typeof window !== 'undefined')
|
||||
// browser should use relative path
|
||||
return '';
|
||||
if (process.env.VERCEL_URL)
|
||||
// reference for vercel.com
|
||||
return `https://${process.env.VERCEL_URL}`;
|
||||
if (process.env.RENDER_INTERNAL_HOSTNAME)
|
||||
// reference for render.com
|
||||
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
|
||||
// assume localhost
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
||||
|
||||
import getBaseUrl from "~/app/_trpc/GetBaseUrl";
|
||||
let clientQueryClient: QueryClient | undefined = undefined;
|
||||
const makeQueryClient = () => new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -38,12 +24,15 @@ function getQueryClient() {
|
||||
}
|
||||
}
|
||||
export default function TrpcProvider({ children }: { children: React.ReactNode }) {
|
||||
console.log("provider calls get base url")
|
||||
const baseUrl = getBaseUrl()
|
||||
console.log("provider got baseurl:", baseUrl)
|
||||
const queryClient = getQueryClient();
|
||||
const [trpcClient] = useState(() => {
|
||||
return trpc.createClient({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: `${getBaseUrl()}/api/trpc`,
|
||||
url: `${baseUrl}/api/trpc`,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useState } from 'react';
|
||||
import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fields';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { SelectItem } from '~/components/ui/select';
|
||||
import FormMutationContextProvider from '~/app/_components/Form/Components/MutationProvider';
|
||||
export default function CreateUpdateCvCategoryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['category']['select']> }) {
|
||||
const schemas = entitySchemas('cvCategory')
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
@@ -33,22 +34,25 @@ export default function CreateUpdateCvCategoryForm(params: { className?: string,
|
||||
createMutation.mutate(values)
|
||||
}
|
||||
return (
|
||||
<FormScaffold
|
||||
form={form}
|
||||
createMutation={createMutation}
|
||||
updateMutation={updateMutation}
|
||||
deleteMutation={deleteMutation}
|
||||
onSubmit={onSubmit}
|
||||
title='Category'
|
||||
id={id}
|
||||
className={params.className}
|
||||
>
|
||||
<TextInputFormField control={form.control} name='name' label='Name' />
|
||||
<SelectFormField control={form.control} name='layoutPosition' label='Layout Position' placeholder={form.getValues().layoutPosition == null ? undefined : form.getValues().layoutPosition}>
|
||||
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
||||
<SelectItem key={o} value={o}> {o} </SelectItem>
|
||||
))}
|
||||
</SelectFormField>
|
||||
</FormScaffold>
|
||||
<FormMutationContextProvider value={{
|
||||
createMutation:createMutation,
|
||||
updateMutation:updateMutation,
|
||||
deleteMutation:deleteMutation
|
||||
}}>
|
||||
<FormScaffold
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
title='Category'
|
||||
id={id}
|
||||
className={params.className}
|
||||
>
|
||||
<TextInputFormField control={form.control} name='name' label='Name' />
|
||||
<SelectFormField control={form.control} name='layoutPosition' label='Layout Position' placeholder={form.getValues().layoutPosition == null ? undefined : form.getValues().layoutPosition}>
|
||||
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
||||
<SelectItem key={o} value={o}> {o} </SelectItem>
|
||||
))}
|
||||
</SelectFormField>
|
||||
</FormScaffold>
|
||||
</FormMutationContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FormScaffold } from '~/app/_components/Form/Components';
|
||||
import { useState } from 'react';
|
||||
import { SelectFormField, TextInputFormField, MdeFormField, CalenderFormField, BooleanFormField } from '~/app/_components/Form/Fields'
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import {FormMutationContextProvider, type FormCreateMutationInterface} from '~/app/_components/Form/Components/MutationProvider';
|
||||
export default function CreateUpdateCvEntryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['entry']['select']>, isUpdate?: boolean }) {
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
const { theme } = useTheme()
|
||||
@@ -42,11 +43,16 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
|
||||
createMutation.mutate(values)
|
||||
}
|
||||
return (
|
||||
//todo
|
||||
// solved by: strictFunctionTypes: false
|
||||
// sadge
|
||||
<FormMutationContextProvider value={{
|
||||
createMutation:createMutation,
|
||||
updateMutation:updateMutation,
|
||||
deleteMutation:deleteMutation
|
||||
}}>
|
||||
<FormScaffold
|
||||
form={form}
|
||||
createMutation={createMutation}
|
||||
updateMutation={updateMutation}
|
||||
deleteMutation={deleteMutation}
|
||||
onSubmit={onSubmit}
|
||||
title='Entry'
|
||||
id={id}
|
||||
@@ -56,7 +62,7 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
|
||||
{
|
||||
categoriesSuccess ?
|
||||
<>
|
||||
{categories.map((c) => {
|
||||
{categories?.map((c) => {
|
||||
return (<SelectItem key={c.id} value={c.id}> {c.name} </SelectItem>)
|
||||
})}
|
||||
</> :
|
||||
@@ -69,5 +75,6 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
|
||||
<CalenderFormField control={form.control} name='toTime' label='To Date' />
|
||||
<BooleanFormField control={form.control} name='hideDates' label='Hide Dates' />
|
||||
</FormScaffold>
|
||||
</FormMutationContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use server'
|
||||
import { expect, test } from 'vitest'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import CreateUpdateProjectForm from './CreateUpdateProjectForm'
|
||||
import TrpcProvider from '~/../test/trpc/vitest.trpcProvider.mock'
|
||||
import TrpcProvider from '~/app/_trpc/TrpcProvider'
|
||||
|
||||
test('CreateUpdateProjectForm', () => {
|
||||
render(
|
||||
@@ -10,6 +9,4 @@ test('CreateUpdateProjectForm', () => {
|
||||
<CreateUpdateProjectForm/>
|
||||
</TrpcProvider>
|
||||
)
|
||||
let options = screen.getAllByRole('combobox')
|
||||
console.log(options)
|
||||
})
|
||||
|
||||
@@ -12,12 +12,11 @@ import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fiel
|
||||
import { FormScaffold } from '~/app/_components/Form/Components';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { makeUseRelationShipWithNameIndex } from '~/lib/hooks';
|
||||
import { FormMutationContextProvider } from '~/app/_components/Form/Components/MutationProvider';
|
||||
export default function CreateUpdateProjectForm(params: { className?: string, entity?: IterableElement<RouterOutputs['project']['select']> }) {
|
||||
const [id, setId] = useState<string|undefined>(params.entity ? params.entity.id : undefined)
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
const schemas = entitySchemas('project')
|
||||
console.log('using trpc')
|
||||
const { data:stacks,id:stackId, name:stackName,success:stacksSuccess, error:stackError } = makeUseRelationShipWithNameIndex('stackItems')(trpc.techStack.select.useQuery({}),id,(items) => {return items ? items.join('-') : ""})
|
||||
console.log(stackError)
|
||||
const { data: stacks, id: stackId, name: stackName, success: stacksSuccess, error: stackError } = makeUseRelationShipWithNameIndex('stackItems')(trpc.techStack.select.useQuery({}), id, (items) => { return items ? items.join('-') : "" })
|
||||
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||
resolver: zodResolver(schemas.insert),
|
||||
defaultValues: {
|
||||
@@ -40,38 +39,42 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
|
||||
createMutation.mutate(values)
|
||||
}
|
||||
return (
|
||||
<FormScaffold
|
||||
form={form}
|
||||
createMutation={createMutation}
|
||||
updateMutation={updateMutation}
|
||||
deleteMutation={deleteMutation}
|
||||
onSubmit={onSubmit}
|
||||
title='Project'
|
||||
id={id}
|
||||
className={params.className}
|
||||
>
|
||||
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
|
||||
{
|
||||
stacksSuccess ?
|
||||
<>
|
||||
{stacks?.map((stack) => {
|
||||
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
|
||||
})}
|
||||
</> :
|
||||
<SelectItem key='placeholder' value="placeholder" />
|
||||
}
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} name='title' label='Title' />
|
||||
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
|
||||
<SelectItem value="open"> open </SelectItem>
|
||||
<SelectItem value="closed"> closed </SelectItem>
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} label='Source Link' name='sourceLink' />
|
||||
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
|
||||
<SelectItem value="released"> released </SelectItem>
|
||||
<SelectItem value="unreleased"> unreleased </SelectItem>
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} label='Release Link' name='releaseLink' />
|
||||
</FormScaffold>
|
||||
<FormMutationContextProvider value={
|
||||
{
|
||||
createMutation: createMutation,
|
||||
updateMutation: updateMutation,
|
||||
deleteMutation: deleteMutation
|
||||
}}>
|
||||
<FormScaffold
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
title='Project'
|
||||
id={id}
|
||||
className={params.className}
|
||||
>
|
||||
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
|
||||
{
|
||||
stacksSuccess ?
|
||||
<>
|
||||
{stacks?.map((stack) => {
|
||||
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
|
||||
})}
|
||||
</> :
|
||||
<SelectItem key='placeholder' value="placeholder" />
|
||||
}
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} name='title' label='Title' />
|
||||
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
|
||||
<SelectItem value="open"> open </SelectItem>
|
||||
<SelectItem value="closed"> closed </SelectItem>
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} label='Source Link' name='sourceLink' />
|
||||
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
|
||||
<SelectItem value="released"> released </SelectItem>
|
||||
<SelectItem value="unreleased"> unreleased </SelectItem>
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} label='Release Link' name='releaseLink' />
|
||||
</FormScaffold>
|
||||
</FormMutationContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { test, assert, expect } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import { userEvent } from '@testing-library/user-event'
|
||||
import CreateUpdateStackForm from './CreateUpdateForm'
|
||||
import TrpcProvider from '~/app/_trpc/TrpcProvider'
|
||||
import { servTrpc } from '~/app/_trpc/ServerClient'
|
||||
import { db } from '~/server/db'
|
||||
import { sql } from 'drizzle-orm'
|
||||
test('CreateUpdateProjectForm',async () => {
|
||||
const user = userEvent.setup()
|
||||
// console.log("db direct:" ,db.query.techStack.findMany());
|
||||
render(
|
||||
<TrpcProvider>
|
||||
<CreateUpdateStackForm/>
|
||||
</TrpcProvider>
|
||||
)
|
||||
const trigger = screen.getByTestId("multiboolean-popover-trigger")
|
||||
fireEvent.click(trigger)
|
||||
const content = screen.getAllByTestId("multiboolean-checkbox")
|
||||
const checkfirst = content.at(0)
|
||||
const checkSecond = content.at(1)
|
||||
assert.notEqual(checkfirst,undefined)
|
||||
assert.notEqual(checkSecond,undefined)
|
||||
await user.click(checkfirst as HTMLElement)
|
||||
await user.click(checkSecond as HTMLElement)
|
||||
// TODO use findby: https://testing-library.com/docs/dom-testing-library/api-async because component has async effects and conditional rendering
|
||||
// this shit just doesnt work
|
||||
// https://github.com/jsdom/jsdom/issues/1937 why ? who knows
|
||||
const submitButton = screen.getByTestId('form-submit')
|
||||
screen.debug(submitButton)
|
||||
// waitFor(() => {
|
||||
// if ((submitButton as HTMLButtonElement).type != 'submit') {
|
||||
// console.log("button isn submit")
|
||||
// throw new Error('submit button props pending');
|
||||
// }
|
||||
// })
|
||||
await user.click(submitButton)
|
||||
screen.debug()
|
||||
//TODO find out why the fuck this is timing out
|
||||
await waitFor(() => {
|
||||
screen.getByTestId('submitted')
|
||||
},{timeout:3000})
|
||||
let res
|
||||
await waitFor(async () => {
|
||||
return new Promise(async (resolve,reject) => {
|
||||
res = await db.execute(sql`SELECT * from "gregorlohaus.com_tech_stack";`)
|
||||
if (res.rows.length < 1) {
|
||||
reject(false)
|
||||
} else {
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
},{timeout:3000})
|
||||
console.log(res)
|
||||
})
|
||||
@@ -2,19 +2,16 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from "zod";
|
||||
import { Form } from "~/components/ui/form";
|
||||
import { trpc } from "~/app/_trpc/Client";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import * as Card from '~/components/ui/card'
|
||||
import type { IterableElement } from 'type-fest'
|
||||
import { stackItemEnum } from "~/server/db/schema";
|
||||
import { stackItemEnum } from "~/server/dbschema/schema";
|
||||
import type { RouterOutputs } from '~/server/routers/_app';
|
||||
import { entitySchemas, makeOnSuccess } from '~/lib/utils';
|
||||
import { useState } from 'react';
|
||||
import { MultiBooleanFormField } from "~/app/_components/Form/Fields"
|
||||
import { DependentFormMessaage, FormScaffold } from '~/app/_components/Form/Components';
|
||||
import DependentText from '~/app/_components/DependentText';
|
||||
import { FormScaffold } from '~/app/_components/Form/Components';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { FormMutationContextProvider } from '~/app/_components/Form/Components/MutationProvider';
|
||||
|
||||
export default function CreateUpdateStackForm(params: { className?: string, entity?: IterableElement<RouterOutputs['techStack']['select']> }) {
|
||||
const schemas = entitySchemas('techStack')
|
||||
@@ -25,6 +22,7 @@ export default function CreateUpdateStackForm(params: { className?: string, enti
|
||||
stackItems: params.entity ? params.entity.stackItems : [],
|
||||
}
|
||||
})
|
||||
const [submitted, setSubmitted] = useState(false)
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
let path = usePathname()
|
||||
let router = useRouter()
|
||||
@@ -32,22 +30,25 @@ export default function CreateUpdateStackForm(params: { className?: string, enti
|
||||
const updateMutation = trpc.techStack.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
|
||||
const deleteMutation = trpc.techStack.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
|
||||
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
||||
setSubmitted(true)
|
||||
params.entity ?
|
||||
updateMutation.mutate(values) :
|
||||
createMutation.mutate(values)
|
||||
createMutation.mutate(values);
|
||||
}
|
||||
return (
|
||||
<FormScaffold
|
||||
form={form}
|
||||
createMutation={createMutation}
|
||||
updateMutation={updateMutation}
|
||||
deleteMutation={deleteMutation}
|
||||
onSubmit={onSubmit}
|
||||
title='Entry'
|
||||
id={id}
|
||||
className={params.className}
|
||||
>
|
||||
<MultiBooleanFormField control={form.control} name='stackItems' label='Stack Items' options={stackItemEnum.enumValues} defaultValues={params.entity?.stackItems} />
|
||||
</FormScaffold>
|
||||
<>
|
||||
<div data-testid={submitted ? "submitted" : ""}/>
|
||||
<FormMutationContextProvider value={{ createMutation: createMutation, updateMutation: updateMutation, deleteMutation: deleteMutation }}>
|
||||
<FormScaffold
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
title='Entry'
|
||||
id={id}
|
||||
className={params.className}
|
||||
>
|
||||
<MultiBooleanFormField control={form.control} name='stackItems' label='Stack Items' options={stackItemEnum.enumValues} defaultValues={params.entity?.stackItems ?? [""]} />
|
||||
</FormScaffold>
|
||||
</FormMutationContextProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,16 +40,20 @@ function Button({
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
submit = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
submit?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
type={submit ? "submit" : props.type}
|
||||
data-testid={submit ? "form-submit" : undefined}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { createSchemaFactory, createSelectSchema, type BuildSchema } from 'drizzle-zod'
|
||||
import * as schema from "~/server/db/schema";
|
||||
import * as schema from '~/server/dbschema/schema'
|
||||
import { PgTable } from "drizzle-orm/pg-core";
|
||||
import type { FieldValues, Path, UseFormReturn } from "react-hook-form";
|
||||
import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// import postgres from "postgres";
|
||||
|
||||
import { env } from "~/env";
|
||||
import * as schema from "./schema";
|
||||
import * as schema from "~/server/dbschema/schema";
|
||||
|
||||
// /**
|
||||
// * Cache the database connection in development. This avoids creating a new connection on every HMR
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Entries } from "type-fest";
|
||||
import { publicProcedure, router } from "~/server/trpc";
|
||||
import { db } from "~/server/db";
|
||||
import * as schema from "~/server/db/schema";
|
||||
import * as schema from '~/server/dbschema/schema';
|
||||
import { isAdmin } from "~/app/actions";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { entitySchemas, type Schema, type SchemaKeys } from "~/lib/utils";
|
||||
@@ -20,6 +20,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
|
||||
}
|
||||
let r = router({
|
||||
select: publicProcedure.input(schemas.update).query(async (opts) => {
|
||||
console.log("select with:", opts.input)
|
||||
const { input } = opts;
|
||||
if (input === undefined) {
|
||||
return await db.select().from(schema[table]).execute();
|
||||
@@ -36,6 +37,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
|
||||
return await db.select().from(schema[table]).execute();
|
||||
}),
|
||||
update: publicProcedure.input(schemas.update).mutation(async (opts) => {
|
||||
console.log("update with:",opts.input)
|
||||
let admin = await isAdmin();
|
||||
if (!admin) {
|
||||
throw new TRPCError(
|
||||
@@ -52,6 +54,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
|
||||
return await db.update(schema[table]).set(input).where(eq(schema[table]['id'], input['id'])).returning().execute();
|
||||
}),
|
||||
insert: publicProcedure.input(schemas.insert).mutation(async (opts) => {
|
||||
console.log("insert with:",opts.input)
|
||||
let admin = await isAdmin();
|
||||
if (!admin) {
|
||||
throw new TRPCError(
|
||||
@@ -62,6 +65,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
|
||||
return await db.insert(schema[table]).values(input).returning().execute();
|
||||
}),
|
||||
delete: publicProcedure.input(schemas.update).mutation(async (opts) => {
|
||||
console.log("delete with:",opts.input)
|
||||
let admin = await isAdmin();
|
||||
if (!admin) {
|
||||
throw new TRPCError(
|
||||
|
||||
Reference in New Issue
Block a user