mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 04:07:05 +00:00
🗓️ fix: Hide Unsupported Schedule Variables (#15053)
This commit is contained in:
parent
6baeff801c
commit
276f5f88fe
4 changed files with 133 additions and 49 deletions
|
|
@ -0,0 +1,33 @@
|
|||
import { ToastProvider } from '@librechat/client';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import type { AgentForm } from '~/common';
|
||||
import Instructions from '../Instructions';
|
||||
|
||||
jest.mock('~/hooks', () => ({
|
||||
useLocalize: () => (key: string) => key,
|
||||
}));
|
||||
|
||||
function InstructionsHarness() {
|
||||
const methods = useForm<AgentForm>({ defaultValues: { instructions: '' } });
|
||||
return (
|
||||
<ToastProvider>
|
||||
<FormProvider {...methods}>
|
||||
<Instructions />
|
||||
</FormProvider>
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('Agent Instructions', () => {
|
||||
it('offers special-variable insertion by default', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<InstructionsHarness />);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'com_ui_variables' }));
|
||||
expect(
|
||||
await screen.findByRole('menuitem', { name: 'com_ui_special_var_current_date' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -418,6 +418,7 @@ export default function ScheduleDialog({
|
|||
required={true}
|
||||
invalid={errors.prompt != null}
|
||||
describedBy="schedule-prompt-message"
|
||||
showVariables={false}
|
||||
portal={false}
|
||||
/>
|
||||
<FieldMessage id="schedule-prompt-message" message={errors.prompt?.message} />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createElement } from 'react';
|
||||
import { ToastProvider } from '@librechat/client';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import type { ReactNode } from 'react';
|
||||
import ScheduleDialog from '../ScheduleDialog';
|
||||
|
|
@ -97,13 +97,54 @@ describe('ScheduleDialog', () => {
|
|||
expect(screen.getByPlaceholderText('com_ui_schedule_prompt_placeholder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('offers the special-variable menu on the prompt field', async () => {
|
||||
it('does not offer special-variable insertion in either prompt editor', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDialog();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'com_ui_variables' }));
|
||||
expect(screen.queryByRole('button', { name: 'com_ui_variables' })).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'com_ui_expand_editor' }));
|
||||
const expandedDialog = screen.getAllByRole('dialog').at(-1);
|
||||
expect(expandedDialog).toBeDefined();
|
||||
expect(
|
||||
await screen.findByRole('menuitem', { name: 'com_ui_special_var_current_date' }),
|
||||
).toBeInTheDocument();
|
||||
within(expandedDialog as HTMLElement).queryByRole('button', { name: 'com_ui_variables' }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps the expanded prompt editor available and synchronized', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDialog();
|
||||
|
||||
const prompt = screen.getByPlaceholderText('com_ui_schedule_prompt_placeholder');
|
||||
await user.type(prompt, 'Initial prompt');
|
||||
await user.click(screen.getByRole('button', { name: 'com_ui_expand_editor' }));
|
||||
|
||||
const expandedDialog = screen.getAllByRole('dialog').at(-1);
|
||||
expect(expandedDialog).toBeDefined();
|
||||
const expandedPrompt = within(expandedDialog as HTMLElement).getByRole('textbox', {
|
||||
name: 'com_ui_prompt',
|
||||
});
|
||||
expect(expandedPrompt).toHaveValue('Initial prompt');
|
||||
|
||||
await user.type(expandedPrompt, ' with details');
|
||||
expect(prompt).toHaveValue('Initial prompt with details');
|
||||
});
|
||||
|
||||
it('preserves prompt validation and accessibility relationships', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDialog();
|
||||
|
||||
const prompt = screen.getByPlaceholderText('com_ui_schedule_prompt_placeholder');
|
||||
expect(prompt).toHaveAccessibleName('com_ui_prompt');
|
||||
expect(prompt).toHaveAttribute('aria-required', 'true');
|
||||
expect(prompt).toHaveAttribute('aria-describedby', 'schedule-prompt-message');
|
||||
expect(prompt).toHaveAttribute('aria-invalid', 'false');
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'com_ui_create' }));
|
||||
|
||||
await waitFor(() => expect(prompt).toHaveAttribute('aria-invalid', 'true'));
|
||||
expect(document.getElementById('schedule-prompt-message')).toHaveTextContent(
|
||||
'com_ui_field_required',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ interface VariableEditorProps {
|
|||
className?: string;
|
||||
labelClassName?: string;
|
||||
containerClassName?: string;
|
||||
/** Whether to expose special-variable insertion controls. Defaults to true. */
|
||||
showVariables?: boolean;
|
||||
/**
|
||||
* Ariakit popovers portal to the body by default, which puts them outside a Radix
|
||||
* dialog's focus trap. Pass `false` from inside a dialog, and give that dialog
|
||||
|
|
@ -73,6 +75,7 @@ export default function VariableEditor({
|
|||
className,
|
||||
labelClassName,
|
||||
containerClassName,
|
||||
showVariables = true,
|
||||
portal = true,
|
||||
}: VariableEditorProps) {
|
||||
const menuId = useId();
|
||||
|
|
@ -102,27 +105,29 @@ export default function VariableEditor({
|
|||
{label}
|
||||
</Label>
|
||||
<div className="flex items-center gap-0.5">
|
||||
<DropdownPopup
|
||||
portal={portal}
|
||||
mountByState={true}
|
||||
unmountOnHide={true}
|
||||
preserveTabOrder={true}
|
||||
isOpen={isMenuOpen}
|
||||
setIsOpen={setIsMenuOpen}
|
||||
trigger={
|
||||
<Menu.MenuButton
|
||||
id={`${id}-variables-menu-button`}
|
||||
aria-label={localize('com_ui_variables')}
|
||||
title={localize('com_ui_variables')}
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-surface-secondary hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-primary"
|
||||
>
|
||||
<PlusCircle className="h-4 w-4" strokeWidth={1.75} aria-hidden={true} />
|
||||
</Menu.MenuButton>
|
||||
}
|
||||
items={variableItems}
|
||||
menuId={menuId}
|
||||
className="pointer-events-auto z-30"
|
||||
/>
|
||||
{showVariables && (
|
||||
<DropdownPopup
|
||||
portal={portal}
|
||||
mountByState={true}
|
||||
unmountOnHide={true}
|
||||
preserveTabOrder={true}
|
||||
isOpen={isMenuOpen}
|
||||
setIsOpen={setIsMenuOpen}
|
||||
trigger={
|
||||
<Menu.MenuButton
|
||||
id={`${id}-variables-menu-button`}
|
||||
aria-label={localize('com_ui_variables')}
|
||||
title={localize('com_ui_variables')}
|
||||
className="inline-flex h-7 w-7 items-center justify-center rounded-lg text-text-secondary transition-colors hover:bg-surface-secondary hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring-primary"
|
||||
>
|
||||
<PlusCircle className="h-4 w-4" strokeWidth={1.75} aria-hidden={true} />
|
||||
</Menu.MenuButton>
|
||||
}
|
||||
items={variableItems}
|
||||
menuId={menuId}
|
||||
className="pointer-events-auto z-30"
|
||||
/>
|
||||
)}
|
||||
<TooltipAnchor
|
||||
description={localize('com_ui_expand_editor')}
|
||||
render={
|
||||
|
|
@ -169,29 +174,33 @@ export default function VariableEditor({
|
|||
placeholder={placeholder}
|
||||
aria-label={label}
|
||||
/>
|
||||
<div className="flex items-center justify-between">
|
||||
<DropdownPopup
|
||||
portal={portal}
|
||||
mountByState={true}
|
||||
unmountOnHide={true}
|
||||
preserveTabOrder={true}
|
||||
isOpen={isDialogMenuOpen}
|
||||
setIsOpen={setIsDialogMenuOpen}
|
||||
trigger={
|
||||
<Menu.MenuButton
|
||||
id={`${id}-variables-menu-button-dialog`}
|
||||
render={
|
||||
<Button type="button" variant="outline" className="gap-1.5">
|
||||
<PlusCircle className="h-4 w-4" strokeWidth={1.75} aria-hidden={true} />
|
||||
{localize('com_ui_variables')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
items={variableItems}
|
||||
menuId={dialogMenuId}
|
||||
className="pointer-events-auto z-[200]"
|
||||
/>
|
||||
<div
|
||||
className={cn('flex items-center', showVariables ? 'justify-between' : 'justify-end')}
|
||||
>
|
||||
{showVariables && (
|
||||
<DropdownPopup
|
||||
portal={portal}
|
||||
mountByState={true}
|
||||
unmountOnHide={true}
|
||||
preserveTabOrder={true}
|
||||
isOpen={isDialogMenuOpen}
|
||||
setIsOpen={setIsDialogMenuOpen}
|
||||
trigger={
|
||||
<Menu.MenuButton
|
||||
id={`${id}-variables-menu-button-dialog`}
|
||||
render={
|
||||
<Button type="button" variant="outline" className="gap-1.5">
|
||||
<PlusCircle className="h-4 w-4" strokeWidth={1.75} aria-hidden={true} />
|
||||
{localize('com_ui_variables')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
items={variableItems}
|
||||
menuId={dialogMenuId}
|
||||
className="pointer-events-auto z-[200]"
|
||||
/>
|
||||
)}
|
||||
<OGDialogClose asChild>
|
||||
<Button type="button">{localize('com_ui_done')}</Button>
|
||||
</OGDialogClose>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue