Fix Goal/Funnel wildcard UI bug

This commit is contained in:
Francis Cao committed 2026-09-02 14:56:35 -07:00
1 parent 2709d8e8ea
commit 26e236c95c
4 files changed
+82 -5

No files matched your search

@@ -61,6 +61,7 @@ function StepRow({
<LookupField
websiteId={websiteId}
type={type}
allowCustomValue
{...field}
onValueChange={(v: string) => {
setEventName(v);
@@ -72,7 +72,7 @@ export function GoalEditForm({
</FormField>
<FormField name="parameters.value" rules={{ required: t(labels.required) }}>
{({ field }) => {
return <LookupField websiteId={websiteId} type={type} {...field} />;
return <LookupField websiteId={websiteId} type={type} allowCustomValue {...field} />;
}}
</FormField>
</Column>
@@ -86,7 +86,7 @@ export function GoalEditForm({
<Column style={{ minWidth: 0 }}>
<FormField name="parameters.value" rules={{ required: t(labels.required) }}>
{({ field }) => {
return <LookupField websiteId={websiteId} type={type} {...field} />;
return <LookupField websiteId={websiteId} type={type} allowCustomValue {...field} />;
}}
</FormField>
</Column>
+66
View File
@@ -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 }) => <div>{children}</div>,
Loading: () => <div>Loading</div>,
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[];
}) => (
<div>
<input
aria-label={ariaLabel}
value={inputValue}
onChange={event => onInputValueChange(event.currentTarget.value)}
/>
{items.map(item => (
<div key={item}>{item}</div>
))}
</div>
),
}));
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 (
<LookupField
websiteId="website-1"
type="path"
value={value}
allowCustomValue
onChange={setValue}
/>
);
}
test('offers a manually entered funnel value as a selectable option', () => {
render(<TestLookupField />);
fireEvent.change(screen.getByLabelText('LookupField'), { target: { value: '/blog/*' } });
expect(screen.getByText('/blog/*')).toBeInTheDocument();
});
+13 -3
View File
@@ -9,6 +9,7 @@ export interface LookupFieldProps extends Omit<ComboBoxProps, 'onChange'> {
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<string>) => {
setSearch(value);
};
@@ -47,7 +57,7 @@ export function LookupField({
<ComboBox
aria-label="LookupField"
{...props}
items={items}
items={options}
inputValue={value}
onInputValueChange={value => {
handleSearch(value);
@@ -61,8 +71,8 @@ export function LookupField({
<Empty message={t(messages.noResultsFound)} />
)
}
>
{items.map(item => (
>
{options.map(item => (
<ListItem key={item} id={item}>
{item}
</ListItem>