core-components: tests for LogViewer search state + refactor

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-12-05 23:14:50 +01:00
committed by blam
parent a6622d9704
commit beed531a9d
3 changed files with 248 additions and 26 deletions
@@ -26,31 +26,15 @@ import { LogViewerSearch } from './useLogViewerSearch';
export interface LogViewerControlsProps extends LogViewerSearch {}
export function LogViewerControls(props: LogViewerControlsProps) {
const { resultCount, setResultIndex, toggleShouldFilter } = props;
const { resultCount, resultIndexStep, toggleShouldFilter } = props;
const resultIndex = props.resultIndex ?? 0;
const increment = () => {
if (resultCount !== undefined) {
const next = resultIndex + 1;
setResultIndex(next >= resultCount ? 0 : next);
}
};
const decrement = () => {
if (resultCount !== undefined) {
const next = resultIndex - 1;
setResultIndex(next < 0 ? resultCount - 1 : next);
}
};
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
if (event.metaKey || event.ctrlKey || event.altKey) {
toggleShouldFilter();
} else if (event.shiftKey) {
decrement();
} else {
increment();
resultIndexStep(event.shiftKey);
}
}
};
@@ -59,13 +43,13 @@ export function LogViewerControls(props: LogViewerControlsProps) {
<>
{resultCount !== undefined && (
<>
<IconButton size="small" onClick={decrement}>
<IconButton size="small" onClick={() => resultIndexStep(true)}>
<ChevronLeftIcon />
</IconButton>
<Typography>
{Math.min(resultIndex + 1, resultCount)}/{resultCount}
</Typography>
<IconButton size="small" onClick={increment}>
<IconButton size="small" onClick={() => resultIndexStep()}>
<ChevronRightIcon />
</IconButton>
</>
@@ -0,0 +1,221 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { act, renderHook } from '@testing-library/react-hooks';
import { applySearchFilter, useLogViewerSearch } from './useLogViewerSearch';
import { AnsiLine } from './AnsiProcessor';
const lines = [
new AnsiLine(1, [{ text: 'FooBar', modifiers: {} }]),
new AnsiLine(2, [{ text: 'Baz', modifiers: {} }]),
new AnsiLine(3, [{ text: 'FooBarFoo', modifiers: {} }]),
new AnsiLine(4, [{ text: 'Baz', modifiers: {} }]),
new AnsiLine(5, [{ text: 'BazFoo', modifiers: {} }]),
new AnsiLine(6, [{ text: 'FooFooFoo', modifiers: {} }]),
new AnsiLine(7, [{ text: '', modifiers: {} }]),
new AnsiLine(8, [{ text: 'Bar', modifiers: {} }]),
];
describe('applySearchFilter', () => {
it('should find search results', () => {
expect(applySearchFilter(lines, '')).toEqual({
lines: lines,
results: undefined,
});
expect(applySearchFilter(lines, 'foo')).toEqual({
lines: [lines[0], lines[2], lines[4], lines[5]],
results: [
{ lineNumber: 1, lineIndex: 0 },
{ lineNumber: 3, lineIndex: 0 },
{ lineNumber: 3, lineIndex: 1 },
{ lineNumber: 5, lineIndex: 0 },
{ lineNumber: 6, lineIndex: 0 },
{ lineNumber: 6, lineIndex: 1 },
{ lineNumber: 6, lineIndex: 2 },
],
});
expect(applySearchFilter(lines, 'bar')).toEqual({
lines: [lines[0], lines[2], lines[7]],
results: [
{ lineNumber: 1, lineIndex: 0 },
{ lineNumber: 3, lineIndex: 0 },
{ lineNumber: 8, lineIndex: 0 },
],
});
expect(applySearchFilter(lines, 'baz')).toEqual({
lines: [lines[1], lines[3], lines[4]],
results: [
{ lineNumber: 2, lineIndex: 0 },
{ lineNumber: 4, lineIndex: 0 },
{ lineNumber: 5, lineIndex: 0 },
],
});
});
});
describe('useLogViewerSearch', () => {
it('should provide search state', () => {
const rendered = renderHook(() => useLogViewerSearch(lines));
expect(rendered.result.current).toMatchObject({
lines,
searchText: '',
shouldFilter: false,
resultCount: undefined,
resultIndex: 0,
resultLine: undefined,
resultLineIndex: undefined,
});
rendered.result.current.resultIndexStep();
expect(rendered.result.current).toMatchObject({
lines,
searchText: '',
shouldFilter: false,
resultCount: undefined,
resultIndex: 0,
resultLine: undefined,
resultLineIndex: undefined,
});
act(() => rendered.result.current.toggleShouldFilter());
expect(rendered.result.current).toMatchObject({
lines,
searchText: '',
shouldFilter: true,
resultCount: undefined,
resultIndex: 0,
resultLine: undefined,
resultLineIndex: undefined,
});
act(() => rendered.result.current.setSearchInput('BAR'));
expect(rendered.result.current).toMatchObject({
lines: [lines[0], lines[2], lines[7]],
searchInput: 'BAR',
searchText: 'bar',
shouldFilter: true,
resultCount: 3,
resultIndex: 0,
resultLine: 1,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
lines: [lines[0], lines[2], lines[7]],
resultIndex: 1,
resultLine: 3,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
lines: [lines[0], lines[2], lines[7]],
resultIndex: 2,
resultLine: 8,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
lines: [lines[0], lines[2], lines[7]],
resultIndex: 0,
resultLine: 1,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep(true));
expect(rendered.result.current).toMatchObject({
lines: [lines[0], lines[2], lines[7]],
resultIndex: 2,
resultLine: 8,
resultLineIndex: 0,
});
act(() => rendered.result.current.setSearchInput('FOO'));
expect(rendered.result.current).toMatchObject({
lines: [lines[0], lines[2], lines[4], lines[5]],
searchInput: 'FOO',
searchText: 'foo',
shouldFilter: true,
resultCount: 7,
resultIndex: 2,
resultLine: 3,
resultLineIndex: 1,
});
act(() => rendered.result.current.toggleShouldFilter());
expect(rendered.result.current).toMatchObject({
lines,
shouldFilter: false,
resultCount: 7,
resultIndex: 2,
resultLine: 3,
resultLineIndex: 1,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
lines,
searchInput: 'FOO',
searchText: 'foo',
shouldFilter: false,
resultCount: 7,
resultIndex: 3,
resultLine: 5,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
resultIndex: 4,
resultLine: 6,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
resultIndex: 5,
resultLine: 6,
resultLineIndex: 1,
});
act(() => rendered.result.current.resultIndexStep());
expect(rendered.result.current).toMatchObject({
resultIndex: 6,
resultLine: 6,
resultLineIndex: 2,
});
act(() => rendered.result.current.setSearchInput('BAR'));
expect(rendered.result.current).toMatchObject({
searchText: 'bar',
resultCount: 3,
resultIndex: 6,
resultLine: 8,
resultLineIndex: 0,
});
act(() => rendered.result.current.resultIndexStep(true));
expect(rendered.result.current).toMatchObject({
searchText: 'bar',
resultCount: 3,
resultIndex: 1,
resultLine: 3,
resultLineIndex: 0,
});
});
});
@@ -61,9 +61,9 @@ export interface LogViewerSearch {
shouldFilter: boolean;
toggleShouldFilter: () => void;
resultIndex: number | undefined;
resultCount: number | undefined;
setResultIndex: (number: number) => void;
resultIndex: number | undefined;
resultIndexStep: (decrement?: boolean) => void;
resultLine: number | undefined;
resultLineIndex: number | undefined;
@@ -73,7 +73,7 @@ export function useLogViewerSearch(lines: AnsiLine[]): LogViewerSearch {
const [searchInput, setSearchInput] = useState('');
const searchText = searchInput.toLocaleLowerCase('en-US');
const [resultIndex, setResultIndex] = useState<number | undefined>();
const [resultIndex, setResultIndex] = useState<number>(0);
const [shouldFilter, toggleShouldFilter] = useToggle(false);
@@ -82,7 +82,24 @@ export function useLogViewerSearch(lines: AnsiLine[]): LogViewerSearch {
[lines, searchText],
);
const searchResult = filter.results?.[resultIndex ?? 0];
const searchResult = filter.results
? filter.results[Math.min(resultIndex, filter.results.length - 1)]
: undefined;
const resultCount = filter.results?.length;
const resultIndexStep = (decrement?: boolean) => {
if (decrement) {
if (resultCount !== undefined) {
const next = Math.min(resultIndex - 1, resultCount - 2);
setResultIndex(next < 0 ? resultCount - 1 : next);
}
} else {
if (resultCount !== undefined) {
const next = resultIndex + 1;
setResultIndex(next >= resultCount ? 0 : next);
}
}
};
return {
lines: shouldFilter ? filter.lines : lines,
@@ -91,9 +108,9 @@ export function useLogViewerSearch(lines: AnsiLine[]): LogViewerSearch {
setSearchInput,
shouldFilter,
toggleShouldFilter,
resultCount,
resultIndex,
resultCount: filter.results?.length,
setResultIndex,
resultIndexStep,
resultLine: searchResult?.lineNumber,
resultLineIndex: searchResult?.lineIndex,
};