From 26e236c95c9c70820c89ceb310b7d00cb618d1c1 Mon Sep 17 00:00:00 2001 From: Francis Cao Date: Wed, 2 Sep 2026 14:56:35 -0700 Subject: [PATCH] Fix Goal/Funnel wildcard UI bug --- .../(reports)/funnels/FunnelEditForm.tsx | 1 + .../(reports)/goals/GoalEditForm.tsx | 4 +- src/components/input/LookupField.test.tsx | 66 +++++++++++++++++++ src/components/input/LookupField.tsx | 16 ++++- 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 src/components/input/LookupField.test.tsx diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx index d9fa644bf..3ab6dac7d 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx @@ -61,6 +61,7 @@ function StepRow({ { setEventName(v); diff --git a/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx b/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx index 3c3b22e52..78877f59e 100644 --- a/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx @@ -72,7 +72,7 @@ export function GoalEditForm({ {({ field }) => { - return ; + return ; }} @@ -86,7 +86,7 @@ export function GoalEditForm({ {({ field }) => { - return ; + return ; }} diff --git a/src/components/input/LookupField.test.tsx b/src/components/input/LookupField.test.tsx new file mode 100644 index 000000000..8fbe917ea --- /dev/null +++ b/src/components/input/LookupField.test.tsx @@ -0,0 +1,66 @@ +import { fireEvent, render, screen } from '@testing-library/react'; +import type { ReactNode } from 'react'; +import { useState } from 'react'; +import { expect, test, vi } from 'vitest'; +import { LookupField } from './LookupField'; + +vi.mock('@umami/react-zen', () => ({ + ListItem: ({ children }: { children: ReactNode }) =>
{children}
, + Loading: () =>
Loading
, + useDebounce: (value: string) => value, +})); + +vi.mock('@/components/common/ComboBox', () => ({ + ComboBox: ({ + 'aria-label': ariaLabel, + inputValue, + onInputValueChange, + items, + }: { + 'aria-label'?: string; + inputValue: string; + onInputValueChange: (value: string) => void; + items: string[]; + }) => ( +
+ onInputValueChange(event.currentTarget.value)} + /> + {items.map(item => ( +
{item}
+ ))} +
+ ), +})); + +vi.mock('@/components/hooks', () => ({ + useMessages: () => ({ + t: (value: string) => value, + messages: { noResultsFound: 'No results found' }, + }), + useWebsiteValuesQuery: () => ({ data: [], isLoading: false }), +})); + +function TestLookupField() { + const [value, setValue] = useState(''); + + return ( + + ); +} + +test('offers a manually entered funnel value as a selectable option', () => { + render(); + + fireEvent.change(screen.getByLabelText('LookupField'), { target: { value: '/blog/*' } }); + + expect(screen.getByText('/blog/*')).toBeInTheDocument(); +}); diff --git a/src/components/input/LookupField.tsx b/src/components/input/LookupField.tsx index 4515f734f..943b959ab 100644 --- a/src/components/input/LookupField.tsx +++ b/src/components/input/LookupField.tsx @@ -9,6 +9,7 @@ export interface LookupFieldProps extends Omit { websiteId: string; type: string; value: string; + allowCustomValue?: boolean; onChange: (value: string) => void; onValueChange?: (value: string) => void; } @@ -17,6 +18,7 @@ export function LookupField({ websiteId, type, value, + allowCustomValue, onChange, onValueChange, ...props @@ -39,6 +41,14 @@ export function LookupField({ return data?.map(({ value }) => value) || []; }, [data]); + const options = useMemo(() => { + if (allowCustomValue && value && !items.includes(value)) { + return [value, ...items]; + } + + return items; + }, [allowCustomValue, items, value]); + const handleSearch = (value: SetStateAction) => { setSearch(value); }; @@ -47,7 +57,7 @@ export function LookupField({ { handleSearch(value); @@ -61,8 +71,8 @@ export function LookupField({ ) } - > - {items.map(item => ( + > + {options.map(item => ( {item}