feat: add tests (#843)

This commit is contained in:
Jeremy 2025-03-04 17:25:46 -08:00 committed by GitHub
parent 95a2af2535
commit 9dd9a9898c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 1063 additions and 191 deletions

View file

@ -44,6 +44,7 @@ function PureArtifactMessages({
key={message.id}
message={message}
isLoading={isLoading && index === messages.length - 1}
index={index}
vote={
votes
? votes.find((vote) => vote.messageId === message.id)

View file

@ -155,7 +155,7 @@ export function Console({ consoleOutputs, setConsoleOutputs }: ConsoleProps) {
<img
src={content.value}
alt="output"
className="rounded-md max-w-[600px] w-full"
className="rounded-md max-w-screen-toast-mobile w-full"
/>
</picture>
) : (

View file

@ -1153,3 +1153,22 @@ export const LineChartIcon = ({ size = 16 }: { size?: number }) => (
></path>
</svg>
);
export const WarningIcon = ({ size = 16 }: { size?: number }) => {
return (
<svg
height={size}
strokeLinejoin="round"
viewBox="0 0 16 16"
width={size}
style={{ color: 'currentcolor' }}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.55846 0.5C9.13413 0.5 9.65902 0.829456 9.90929 1.34788L15.8073 13.5653C16.1279 14.2293 15.6441 15 14.9068 15H1.09316C0.355835 15 -0.127943 14.2293 0.192608 13.5653L6.09065 1.34787C6.34092 0.829454 6.86581 0.5 7.44148 0.5H8.55846ZM8.74997 4.75V5.5V8V8.75H7.24997V8V5.5V4.75H8.74997ZM7.99997 12C8.55226 12 8.99997 11.5523 8.99997 11C8.99997 10.4477 8.55226 10 7.99997 10C7.44769 10 6.99997 10.4477 6.99997 11C6.99997 11.5523 7.44769 12 7.99997 12Z"
fill="currentColor"
/>
</svg>
);
};

View file

@ -50,6 +50,7 @@ export function MessageEditor({
return (
<div className="flex flex-col gap-2 w-full">
<Textarea
data-testid="message-editor"
ref={textareaRef}
className="bg-transparent outline-none overflow-hidden resize-none !text-base rounded-xl w-full"
value={draftContent}
@ -67,6 +68,7 @@ export function MessageEditor({
Cancel
</Button>
<Button
data-testid="message-editor-send-button"
variant="default"
className="h-fit py-2 px-3"
disabled={isSubmitting}

View file

@ -43,14 +43,15 @@ export function MessageReasoning({
) : (
<div className="flex flex-row gap-2 items-center">
<div className="font-medium">Reasoned for a few seconds</div>
<div
<button
type="button"
className="cursor-pointer"
onClick={() => {
setIsExpanded(!isExpanded);
}}
>
<ChevronDownIcon />
</div>
</button>
</div>
)}

View file

@ -3,17 +3,10 @@
import type { ChatRequestOptions, Message } from 'ai';
import cx from 'classnames';
import { AnimatePresence, motion } from 'framer-motion';
import { memo, useMemo, useState } from 'react';
import { memo, useState } from 'react';
import type { Vote } from '@/lib/db/schema';
import { DocumentToolCall, DocumentToolResult } from './document';
import {
ChevronDownIcon,
LoaderIcon,
PencilEditIcon,
SparklesIcon,
} from './icons';
import { PencilEditIcon, SparklesIcon } from './icons';
import { Markdown } from './markdown';
import { MessageActions } from './message-actions';
import { PreviewAttachment } from './preview-attachment';
@ -34,6 +27,7 @@ const PurePreviewMessage = ({
setMessages,
reload,
isReadonly,
index,
}: {
chatId: string;
message: Message;
@ -46,12 +40,14 @@ const PurePreviewMessage = ({
chatRequestOptions?: ChatRequestOptions,
) => Promise<string | null | undefined>;
isReadonly: boolean;
index: number;
}) => {
const [mode, setMode] = useState<'view' | 'edit'>('view');
return (
<AnimatePresence>
<motion.div
data-testid={`message-${message.role}-${index}`}
className="w-full mx-auto max-w-3xl px-4 group/message"
initial={{ y: 5, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
@ -76,7 +72,10 @@ const PurePreviewMessage = ({
<div className="flex flex-col gap-4 w-full">
{message.experimental_attachments && (
<div className="flex flex-row justify-end gap-2">
<div
data-testid={`message-attachments-${index}`}
className="flex flex-row justify-end gap-2"
>
{message.experimental_attachments.map((attachment) => (
<PreviewAttachment
key={attachment.url}
@ -99,6 +98,7 @@ const PurePreviewMessage = ({
<Tooltip>
<TooltipTrigger asChild>
<Button
data-testid={`edit-${message.role}-${index}`}
variant="ghost"
className="px-2 h-fit rounded-full text-muted-foreground opacity-0 group-hover/message:opacity-100"
onClick={() => {

View file

@ -43,6 +43,7 @@ function PureMessages({
{messages.map((message, index) => (
<PreviewMessage
key={message.id}
index={index}
chatId={chatId}
message={message}
isLoading={isLoading && messages.length - 1 === index}

View file

@ -211,7 +211,10 @@ function PureMultimodalInput({
/>
{(attachments.length > 0 || uploadQueue.length > 0) && (
<div className="flex flex-row gap-2 overflow-x-scroll items-end">
<div
data-testid="attachments-preview"
className="flex flex-row gap-2 overflow-x-scroll items-end"
>
{attachments.map((attachment) => (
<PreviewAttachment key={attachment.url} attachment={attachment} />
))}
@ -231,6 +234,7 @@ function PureMultimodalInput({
)}
<Textarea
data-testid="multimodal-input"
ref={textareaRef}
placeholder="Send a message..."
value={input}
@ -293,6 +297,7 @@ function PureAttachmentsButton({
}) {
return (
<Button
data-testid="attachments-button"
className="rounded-md rounded-bl-lg p-[7px] h-fit dark:border-zinc-700 hover:dark:bg-zinc-900 hover:bg-zinc-200"
onClick={(event) => {
event.preventDefault();
@ -317,6 +322,7 @@ function PureStopButton({
}) {
return (
<Button
data-testid="stop-button"
className="rounded-full p-1.5 h-fit border dark:border-zinc-600"
onClick={(event) => {
event.preventDefault();
@ -342,6 +348,7 @@ function PureSendButton({
}) {
return (
<Button
data-testid="send-button"
className="rounded-full p-1.5 h-fit border dark:border-zinc-600"
onClick={(event) => {
event.preventDefault();

View file

@ -12,7 +12,7 @@ export const PreviewAttachment = ({
const { name, url, contentType } = attachment;
return (
<div className="flex flex-col gap-2">
<div data-testid="input-attachment-preview" className="flex flex-col gap-2">
<div className="w-20 h-16 aspect-video bg-muted rounded-md relative flex flex-col items-center justify-center">
{contentType ? (
contentType.startsWith('image') ? (
@ -32,7 +32,10 @@ export const PreviewAttachment = ({
)}
{isUploading && (
<div className="animate-spin absolute text-zinc-500">
<div
data-testid="input-attachment-loader"
className="animate-spin absolute text-zinc-500"
>
<LoaderIcon />
</div>
)}

View file

@ -38,7 +38,10 @@ function PureSuggestedActions({ chatId, append }: SuggestedActionsProps) {
];
return (
<div className="grid sm:grid-cols-2 gap-2 w-full">
<div
data-testid="suggested-actions"
className="grid sm:grid-cols-2 gap-2 w-full"
>
{suggestedActions.map((suggestedAction, index) => (
<motion.div
initial={{ opacity: 0, y: 20 }}

44
components/toast.tsx Normal file
View file

@ -0,0 +1,44 @@
'use client';
import React, { ReactNode } from 'react';
import { toast as sonnerToast } from 'sonner';
import { CheckCircleFillIcon, WarningIcon } from './icons';
const iconsByType: Record<'success' | 'error', ReactNode> = {
success: <CheckCircleFillIcon />,
error: <WarningIcon />,
};
export function toast(props: Omit<ToastProps, 'id'>) {
return sonnerToast.custom((id) => (
<Toast id={id} type={props.type} description={props.description} />
));
}
function Toast(props: ToastProps) {
const { id, type, description } = props;
return (
<div className="flex w-full toast-mobile:w-[356px] justify-center">
<div
data-testid="toast"
key={id}
className="bg-zinc-100 p-3 rounded-lg w-full toast-mobile:w-fit flex flex-row gap-2 items-center"
>
<div
data-type={type}
className="data-[type=error]:text-red-600 data-[type=success]:text-green-600"
>
{iconsByType[type]}
</div>
<div className="text-zinc-950 text-sm">{description}</div>
</div>
</div>
);
}
interface ToastProps {
id: string | number;
type: 'success' | 'error';
description: string;
}