mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
fix: added fix for cursor jump in QB (#9140)
* fix: added fix for cursor jump in QB * chore: minor cleanup * feat: updating the query when the editor is getting out for focus or running the query * test: added test for QuerySearch * chore: updated variable name for QB interaction * chore: updated PR review changes * chore: removed non required comments
This commit is contained in:
parent
a16ab114f5
commit
710f7740d3
@ -93,7 +93,7 @@ function QuerySearch({
|
|||||||
onRun?: (query: string) => void;
|
onRun?: (query: string) => void;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
const [query, setQuery] = useState<string>(queryData.filter?.expression || '');
|
const [query, setQuery] = useState<string>('');
|
||||||
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
|
const [valueSuggestions, setValueSuggestions] = useState<any[]>([]);
|
||||||
const [activeKey, setActiveKey] = useState<string>('');
|
const [activeKey, setActiveKey] = useState<string>('');
|
||||||
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
|
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
|
||||||
@ -104,6 +104,10 @@ function QuerySearch({
|
|||||||
errors: [],
|
errors: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 });
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
const [hasInteractedWithQB, setHasInteractedWithQB] = useState(false);
|
||||||
|
|
||||||
const handleQueryValidation = (newQuery: string): void => {
|
const handleQueryValidation = (newQuery: string): void => {
|
||||||
try {
|
try {
|
||||||
const validationResponse = validateQuery(newQuery);
|
const validationResponse = validateQuery(newQuery);
|
||||||
@ -123,13 +127,28 @@ function QuerySearch({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newQuery = queryData.filter?.expression || '';
|
const newQuery = queryData.filter?.expression || '';
|
||||||
// Only mark as external change if the query actually changed from external source
|
// Only update query from external source when editor is not focused
|
||||||
|
// When focused, just update the lastExternalQuery to track changes
|
||||||
if (newQuery !== lastExternalQuery) {
|
if (newQuery !== lastExternalQuery) {
|
||||||
setQuery(newQuery);
|
setQuery(newQuery);
|
||||||
setIsExternalQueryChange(true);
|
setIsExternalQueryChange(true);
|
||||||
setLastExternalQuery(newQuery);
|
setLastExternalQuery(newQuery);
|
||||||
}
|
}
|
||||||
}, [queryData.filter?.expression, lastExternalQuery]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [queryData.filter?.expression]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Update the query when the editor is blurred and the query has changed
|
||||||
|
// Only call onChange if the editor has been focused before (not on initial mount)
|
||||||
|
if (
|
||||||
|
!isFocused &&
|
||||||
|
hasInteractedWithQB &&
|
||||||
|
query !== queryData.filter?.expression
|
||||||
|
) {
|
||||||
|
onChange(query);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isFocused]);
|
||||||
|
|
||||||
// Validate query when it changes externally (from queryData)
|
// Validate query when it changes externally (from queryData)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -145,9 +164,6 @@ function QuerySearch({
|
|||||||
|
|
||||||
const [showExamples] = useState(false);
|
const [showExamples] = useState(false);
|
||||||
|
|
||||||
const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 });
|
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
|
||||||
|
|
||||||
const [
|
const [
|
||||||
isFetchingCompleteValuesList,
|
isFetchingCompleteValuesList,
|
||||||
setIsFetchingCompleteValuesList,
|
setIsFetchingCompleteValuesList,
|
||||||
@ -161,6 +177,9 @@ function QuerySearch({
|
|||||||
const lastFetchedKeyRef = useRef<string>('');
|
const lastFetchedKeyRef = useRef<string>('');
|
||||||
const lastValueRef = useRef<string>('');
|
const lastValueRef = useRef<string>('');
|
||||||
const isMountedRef = useRef<boolean>(true);
|
const isMountedRef = useRef<boolean>(true);
|
||||||
|
const [shouldRunQueryPostUpdate, setShouldRunQueryPostUpdate] = useState(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
const { handleRunQuery } = useQueryBuilder();
|
const { handleRunQuery } = useQueryBuilder();
|
||||||
|
|
||||||
@ -206,6 +225,7 @@ function QuerySearch({
|
|||||||
|
|
||||||
return (): void => clearTimeout(timeoutId);
|
return (): void => clearTimeout(timeoutId);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[isFocused],
|
[isFocused],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -545,7 +565,6 @@ function QuerySearch({
|
|||||||
|
|
||||||
const handleChange = (value: string): void => {
|
const handleChange = (value: string): void => {
|
||||||
setQuery(value);
|
setQuery(value);
|
||||||
onChange(value);
|
|
||||||
// Mark as internal change to avoid triggering external validation
|
// Mark as internal change to avoid triggering external validation
|
||||||
setIsExternalQueryChange(false);
|
setIsExternalQueryChange(false);
|
||||||
// Update lastExternalQuery to prevent external validation trigger
|
// Update lastExternalQuery to prevent external validation trigger
|
||||||
@ -1209,6 +1228,25 @@ function QuerySearch({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Effect to handle query run after update
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
// Only run the query post updating the filter expression.
|
||||||
|
// This runs the query in the next update cycle of react, when it's guaranteed that the query is updated.
|
||||||
|
// Because both the things are sequential and react batches the updates so it was still taking the old query.
|
||||||
|
if (shouldRunQueryPostUpdate) {
|
||||||
|
if (onRun && typeof onRun === 'function') {
|
||||||
|
onRun(query);
|
||||||
|
} else {
|
||||||
|
handleRunQuery();
|
||||||
|
}
|
||||||
|
setShouldRunQueryPostUpdate(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[shouldRunQueryPostUpdate, handleRunQuery, onRun],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="code-mirror-where-clause">
|
<div className="code-mirror-where-clause">
|
||||||
{editingMode && (
|
{editingMode && (
|
||||||
@ -1283,6 +1321,7 @@ function QuerySearch({
|
|||||||
theme={isDarkMode ? copilot : githubLight}
|
theme={isDarkMode ? copilot : githubLight}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onUpdate={handleUpdate}
|
onUpdate={handleUpdate}
|
||||||
|
data-testid="query-where-clause-editor"
|
||||||
className={cx('query-where-clause-editor', {
|
className={cx('query-where-clause-editor', {
|
||||||
isValid: validation.isValid === true,
|
isValid: validation.isValid === true,
|
||||||
hasErrors: validation.errors.length > 0,
|
hasErrors: validation.errors.length > 0,
|
||||||
@ -1319,11 +1358,14 @@ function QuerySearch({
|
|||||||
// and instead run a custom action
|
// and instead run a custom action
|
||||||
// Mod-Enter is usually Ctrl-Enter or Cmd-Enter based on OS
|
// Mod-Enter is usually Ctrl-Enter or Cmd-Enter based on OS
|
||||||
run: (): boolean => {
|
run: (): boolean => {
|
||||||
if (onRun && typeof onRun === 'function') {
|
if (
|
||||||
onRun(query);
|
onChange &&
|
||||||
} else {
|
typeof onChange === 'function' &&
|
||||||
handleRunQuery();
|
query !== queryData.filter?.expression
|
||||||
|
) {
|
||||||
|
onChange(query);
|
||||||
}
|
}
|
||||||
|
setShouldRunQueryPostUpdate(true);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1342,8 +1384,13 @@ function QuerySearch({
|
|||||||
}}
|
}}
|
||||||
onFocus={(): void => {
|
onFocus={(): void => {
|
||||||
setIsFocused(true);
|
setIsFocused(true);
|
||||||
|
setHasInteractedWithQB(true);
|
||||||
}}
|
}}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
|
onCreateEditor={(view: EditorView): EditorView => {
|
||||||
|
editorRef.current = view;
|
||||||
|
return view;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{query && validation.isValid === false && !isFocused && (
|
{query && validation.isValid === false && !isFocused && (
|
||||||
|
|||||||
@ -0,0 +1,380 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
|
/* eslint-disable import/named */
|
||||||
|
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
|
||||||
|
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
|
||||||
|
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||||
|
import * as UseQBModule from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import React from 'react';
|
||||||
|
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||||
|
import type { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
|
||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import QuerySearch from '../QuerySearch/QuerySearch';
|
||||||
|
|
||||||
|
jest.mock('hooks/useDarkMode', () => ({
|
||||||
|
useIsDarkMode: (): boolean => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('providers/Dashboard/Dashboard', () => ({
|
||||||
|
useDashboard: (): { selectedDashboard: undefined } => ({
|
||||||
|
selectedDashboard: undefined,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('hooks/queryBuilder/useQueryBuilder', () => {
|
||||||
|
const handleRunQuery = jest.fn();
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
useQueryBuilder: (): { handleRunQuery: () => void } => ({ handleRunQuery }),
|
||||||
|
handleRunQuery,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('@codemirror/autocomplete', () => ({
|
||||||
|
autocompletion: (): Record<string, unknown> => ({}),
|
||||||
|
closeCompletion: (): boolean => true,
|
||||||
|
completionKeymap: [] as unknown[],
|
||||||
|
startCompletion: (): boolean => true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@codemirror/lang-javascript', () => ({
|
||||||
|
javascript: (): Record<string, unknown> => ({}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@uiw/codemirror-theme-copilot', () => ({
|
||||||
|
copilot: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@uiw/codemirror-theme-github', () => ({
|
||||||
|
githubLight: {},
|
||||||
|
}));
|
||||||
|
jest.mock('api/querySuggestions/getKeySuggestions', () => ({
|
||||||
|
getKeySuggestions: jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
data: { keys: {} as Record<string, QueryKeyDataSuggestionsProps[]> },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('api/querySuggestions/getValueSuggestion', () => ({
|
||||||
|
getValueSuggestions: jest.fn().mockResolvedValue({
|
||||||
|
data: { data: { values: { stringValues: [], numberValues: [] } } },
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock CodeMirror to a simple textarea to make it testable and call onUpdate
|
||||||
|
jest.mock(
|
||||||
|
'@uiw/react-codemirror',
|
||||||
|
(): Record<string, unknown> => {
|
||||||
|
// Minimal EditorView shape used by the component
|
||||||
|
class EditorViewMock {}
|
||||||
|
(EditorViewMock as any).domEventHandlers = (): unknown => ({} as unknown);
|
||||||
|
(EditorViewMock as any).lineWrapping = {} as unknown;
|
||||||
|
(EditorViewMock as any).editable = { of: () => ({}) } as unknown;
|
||||||
|
|
||||||
|
const keymap = { of: (arr: unknown) => arr } as unknown;
|
||||||
|
const Prec = { highest: (ext: unknown) => ext } as unknown;
|
||||||
|
|
||||||
|
type CodeMirrorProps = {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (v: string) => void;
|
||||||
|
onFocus?: () => void;
|
||||||
|
onBlur?: () => void;
|
||||||
|
placeholder?: string;
|
||||||
|
onCreateEditor?: (view: unknown) => unknown;
|
||||||
|
onUpdate?: (arg: {
|
||||||
|
view: {
|
||||||
|
state: {
|
||||||
|
selection: { main: { head: number } };
|
||||||
|
doc: {
|
||||||
|
toString: () => string;
|
||||||
|
lineAt: (
|
||||||
|
_pos: number,
|
||||||
|
) => { number: number; from: number; to: number; text: string };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) => void;
|
||||||
|
'data-testid'?: string;
|
||||||
|
extensions?: unknown[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function CodeMirrorMock({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onFocus,
|
||||||
|
onBlur,
|
||||||
|
placeholder,
|
||||||
|
onCreateEditor,
|
||||||
|
onUpdate,
|
||||||
|
'data-testid': dataTestId,
|
||||||
|
extensions,
|
||||||
|
}: CodeMirrorProps): JSX.Element {
|
||||||
|
const [localValue, setLocalValue] = React.useState<string>(value ?? '');
|
||||||
|
|
||||||
|
// Provide a fake editor instance
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (onCreateEditor) {
|
||||||
|
onCreateEditor(new EditorViewMock() as any);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Call onUpdate whenever localValue changes to simulate cursor and doc
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (onUpdate) {
|
||||||
|
const text = String(localValue ?? '');
|
||||||
|
const head = text.length;
|
||||||
|
onUpdate({
|
||||||
|
view: {
|
||||||
|
state: {
|
||||||
|
selection: { main: { head } },
|
||||||
|
doc: {
|
||||||
|
toString: (): string => text,
|
||||||
|
lineAt: () => ({
|
||||||
|
number: 1,
|
||||||
|
from: 0,
|
||||||
|
to: text.length,
|
||||||
|
text,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [localValue]);
|
||||||
|
|
||||||
|
const handleKeyDown = (
|
||||||
|
e: React.KeyboardEvent<HTMLTextAreaElement>,
|
||||||
|
): void => {
|
||||||
|
const isModEnter = e.key === 'Enter' && (e.metaKey || e.ctrlKey);
|
||||||
|
if (!isModEnter) return;
|
||||||
|
const exts: unknown[] = Array.isArray(extensions) ? extensions : [];
|
||||||
|
const flat: unknown[] = exts.flatMap((x: unknown) =>
|
||||||
|
Array.isArray(x) ? x : [x],
|
||||||
|
);
|
||||||
|
const keyBindings = flat.filter(
|
||||||
|
(x) =>
|
||||||
|
Boolean(x) &&
|
||||||
|
typeof x === 'object' &&
|
||||||
|
'key' in (x as Record<string, unknown>),
|
||||||
|
) as Array<{ key?: string; run?: () => boolean | void }>;
|
||||||
|
keyBindings
|
||||||
|
.filter((b) => b.key === 'Mod-Enter' && typeof b.run === 'function')
|
||||||
|
.forEach((b) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
b.run!();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
data-testid={dataTestId || 'query-where-clause-editor'}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={localValue}
|
||||||
|
onChange={(e): void => {
|
||||||
|
setLocalValue(e.target.value);
|
||||||
|
if (onChange) {
|
||||||
|
onChange(e.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
style={{ width: '100%', minHeight: 80 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
__esModule: true,
|
||||||
|
default: CodeMirrorMock,
|
||||||
|
EditorView: EditorViewMock,
|
||||||
|
keymap,
|
||||||
|
Prec,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const handleRunQueryMock = ((UseQBModule as unknown) as {
|
||||||
|
handleRunQuery: jest.MockedFunction<() => void>;
|
||||||
|
}).handleRunQuery;
|
||||||
|
|
||||||
|
const PLACEHOLDER_TEXT =
|
||||||
|
"Enter your filter query (e.g., http.status_code >= 500 AND service.name = 'frontend')";
|
||||||
|
const TESTID_EDITOR = 'query-where-clause-editor';
|
||||||
|
const SAMPLE_KEY_TYPING = 'http.';
|
||||||
|
const SAMPLE_VALUE_TYPING_INCOMPLETE = " service.name = '";
|
||||||
|
const SAMPLE_VALUE_TYPING_COMPLETE = " service.name = 'frontend'";
|
||||||
|
const SAMPLE_STATUS_QUERY = " status_code = '200'";
|
||||||
|
|
||||||
|
describe('QuerySearch', () => {
|
||||||
|
it('renders with placeholder', () => {
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||||
|
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.LOGS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByPlaceholderText(PLACEHOLDER_TEXT)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onChange on blur after user edits', async () => {
|
||||||
|
const handleChange = jest.fn() as jest.MockedFunction<(v: string) => void>;
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={handleChange}
|
||||||
|
queryData={initialQueriesMap.metrics.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.METRICS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const editor = screen.getByTestId(TESTID_EDITOR);
|
||||||
|
await user.click(editor);
|
||||||
|
await user.type(editor, SAMPLE_VALUE_TYPING_COMPLETE);
|
||||||
|
// Blur triggers validation + onChange (only if focused at least once and value changed)
|
||||||
|
editor.blur();
|
||||||
|
|
||||||
|
await waitFor(() => expect(handleChange).toHaveBeenCalledTimes(1));
|
||||||
|
expect(handleChange.mock.calls[0][0]).toContain("service.name = 'frontend'");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches key suggestions when typing a key (debounced)', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const advance = (ms: number): void => {
|
||||||
|
jest.advanceTimersByTime(ms);
|
||||||
|
};
|
||||||
|
const user = userEvent.setup({
|
||||||
|
advanceTimers: advance,
|
||||||
|
pointerEventsCheck: 0,
|
||||||
|
});
|
||||||
|
const mockedGetKeys = getKeySuggestions as jest.MockedFunction<
|
||||||
|
typeof getKeySuggestions
|
||||||
|
>;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||||
|
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.LOGS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const editor = screen.getByTestId(TESTID_EDITOR);
|
||||||
|
await user.type(editor, SAMPLE_KEY_TYPING);
|
||||||
|
advance(1000);
|
||||||
|
|
||||||
|
await waitFor(() => expect(mockedGetKeys).toHaveBeenCalled(), {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches value suggestions when editing value context', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const advance = (ms: number): void => {
|
||||||
|
jest.advanceTimersByTime(ms);
|
||||||
|
};
|
||||||
|
const user = userEvent.setup({
|
||||||
|
advanceTimers: advance,
|
||||||
|
pointerEventsCheck: 0,
|
||||||
|
});
|
||||||
|
const mockedGetValues = getValueSuggestions as jest.MockedFunction<
|
||||||
|
typeof getValueSuggestions
|
||||||
|
>;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||||
|
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.LOGS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const editor = screen.getByTestId(TESTID_EDITOR);
|
||||||
|
await user.type(editor, SAMPLE_VALUE_TYPING_INCOMPLETE);
|
||||||
|
advance(1000);
|
||||||
|
|
||||||
|
await waitFor(() => expect(mockedGetValues).toHaveBeenCalled(), {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fetches key suggestions on mount for LOGS', async () => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
const mockedGetKeysOnMount = getKeySuggestions as jest.MockedFunction<
|
||||||
|
typeof getKeySuggestions
|
||||||
|
>;
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||||
|
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.LOGS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(1000);
|
||||||
|
|
||||||
|
await waitFor(() => expect(mockedGetKeysOnMount).toHaveBeenCalled(), {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lastArgs = mockedGetKeysOnMount.mock.calls[
|
||||||
|
mockedGetKeysOnMount.mock.calls.length - 1
|
||||||
|
]?.[0] as { signal: unknown; searchText: string };
|
||||||
|
expect(lastArgs).toMatchObject({ signal: DataSource.LOGS, searchText: '' });
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls provided onRun on Mod-Enter', async () => {
|
||||||
|
const onRun = jest.fn() as jest.MockedFunction<(q: string) => void>;
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||||
|
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.LOGS}
|
||||||
|
onRun={onRun}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const editor = screen.getByTestId(TESTID_EDITOR);
|
||||||
|
await user.click(editor);
|
||||||
|
await user.type(editor, SAMPLE_STATUS_QUERY);
|
||||||
|
await user.keyboard('{Meta>}{Enter}{/Meta}');
|
||||||
|
|
||||||
|
await waitFor(() => expect(onRun).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleRunQuery when Mod-Enter without onRun', async () => {
|
||||||
|
const mockedHandleRunQuery = handleRunQueryMock as jest.MockedFunction<
|
||||||
|
() => void
|
||||||
|
>;
|
||||||
|
mockedHandleRunQuery.mockClear();
|
||||||
|
|
||||||
|
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
render(
|
||||||
|
<QuerySearch
|
||||||
|
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
|
||||||
|
queryData={initialQueriesMap.logs.builder.queryData[0]}
|
||||||
|
dataSource={DataSource.LOGS}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const editor = screen.getByTestId(TESTID_EDITOR);
|
||||||
|
await user.click(editor);
|
||||||
|
await user.type(editor, SAMPLE_VALUE_TYPING_COMPLETE);
|
||||||
|
await user.keyboard('{Meta>}{Enter}{/Meta}');
|
||||||
|
|
||||||
|
await waitFor(() => expect(mockedHandleRunQuery).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user