From 563635a0f8decf17427ff8d61b6adf6f6562b420 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 5 Dec 2021 14:54:45 +0100 Subject: [PATCH] core-components: add tests for LogLine highlighting + fix and refactor Signed-off-by: Patrik Oldsberg --- .../src/components/LogViewer/LogLine.test.tsx | 248 +++++++++++++++++- .../src/components/LogViewer/LogLine.tsx | 43 +-- 2 files changed, 272 insertions(+), 19 deletions(-) diff --git a/packages/core-components/src/components/LogViewer/LogLine.test.tsx b/packages/core-components/src/components/LogViewer/LogLine.test.tsx index a4033f9221..ce98746b01 100644 --- a/packages/core-components/src/components/LogViewer/LogLine.test.tsx +++ b/packages/core-components/src/components/LogViewer/LogLine.test.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { ChunkModifiers } from './AnsiProcessor'; -import { findSearchResults, getModifierClasses } from './LogLine'; +import { AnsiLine, ChunkModifiers } from './AnsiProcessor'; +import { + calculateHighlightedChunks, + findSearchResults, + getModifierClasses, +} from './LogLine'; describe('getModifierClasses', () => { const classes = { @@ -110,3 +114,243 @@ describe('findSearchResults', () => { ]); }); }); + +describe('calculateHighlightedChunks', () => { + it('should pass through chunks if there are no results', () => { + const chunks = [{ text: 'Foo', modifiers: {} }]; + const line = new AnsiLine(0, chunks); + expect(calculateHighlightedChunks(line, 'bar')).toBe(chunks); + }); + + it('should highlight one result from plain text', () => { + const line = new AnsiLine(0, [{ text: 'FooBarBaz', modifiers: {} }]); + expect(calculateHighlightedChunks(line, 'foo')).toEqual([ + { + text: 'Foo', + modifiers: {}, + highlight: true, + }, + { + text: 'BarBaz', + modifiers: {}, + }, + ]); + expect(calculateHighlightedChunks(line, 'bar')).toEqual([ + { + text: 'Foo', + modifiers: {}, + }, + { + text: 'Bar', + modifiers: {}, + highlight: true, + }, + { + text: 'Baz', + modifiers: {}, + }, + ]); + expect(calculateHighlightedChunks(line, 'baz')).toEqual([ + { + text: 'FooBar', + modifiers: {}, + }, + { + text: 'Baz', + modifiers: {}, + highlight: true, + }, + ]); + }); + + it('should highlight multiple results from plain text', () => { + const line = new AnsiLine(0, [ + { text: 'FooBarBazBazBarFoo', modifiers: {} }, + ]); + expect(calculateHighlightedChunks(line, 'foo')).toEqual([ + { + text: 'Foo', + modifiers: {}, + highlight: true, + }, + { + text: 'BarBazBazBar', + modifiers: {}, + }, + { + text: 'Foo', + modifiers: {}, + highlight: true, + }, + ]); + expect(calculateHighlightedChunks(line, 'bar')).toEqual([ + { + text: 'Foo', + modifiers: {}, + }, + { + text: 'Bar', + modifiers: {}, + highlight: true, + }, + { + text: 'BazBaz', + modifiers: {}, + }, + { + text: 'Bar', + modifiers: {}, + highlight: true, + }, + { + text: 'Foo', + modifiers: {}, + }, + ]); + expect(calculateHighlightedChunks(line, 'baz')).toEqual([ + { + text: 'FooBar', + modifiers: {}, + }, + { + text: 'Baz', + modifiers: {}, + highlight: true, + }, + { + text: 'Baz', + modifiers: {}, + highlight: true, + }, + { + text: 'BarFoo', + modifiers: {}, + }, + ]); + }); + + it('should forward modifiers to result', () => { + const line = new AnsiLine(0, [ + { text: 'FooBarBazBazBarFoo', modifiers: { bold: true } }, + ]); + expect(calculateHighlightedChunks(line, 'foo')).toEqual([ + { + text: 'Foo', + modifiers: { bold: true }, + highlight: true, + }, + { + text: 'BarBazBazBar', + modifiers: { bold: true }, + }, + { + text: 'Foo', + modifiers: { bold: true }, + highlight: true, + }, + ]); + }); + + it('should highlight full chunks', () => { + const line = new AnsiLine(0, [ + { text: 'Foo', modifiers: { bold: true } }, + { text: 'BarBaz', modifiers: { bold: true } }, + { text: 'BazBar', modifiers: { italic: true } }, + { text: 'Foo', modifiers: { italic: true } }, + ]); + expect(calculateHighlightedChunks(line, 'foo')).toEqual([ + { + text: 'Foo', + modifiers: { bold: true }, + highlight: true, + }, + { + text: 'BarBaz', + modifiers: { bold: true }, + }, + { + text: 'BazBar', + modifiers: { italic: true }, + }, + { + text: 'Foo', + modifiers: { italic: true }, + highlight: true, + }, + ]); + }); + + it('should highlight partial chunks', () => { + const line = new AnsiLine(0, [ + { text: 'Fo', modifiers: { bold: true } }, + { text: 'oFooFo', modifiers: {} }, + { text: 'oBarBaz', modifiers: { italic: true } }, + { text: 'Foo', modifiers: { foreground: 'blue' } }, + { text: 'FooFoo', modifiers: { italic: true } }, + { text: 'F', modifiers: { bold: true } }, + { text: 'o', modifiers: {} }, + { text: 'o', modifiers: { bold: true } }, + ]); + expect(calculateHighlightedChunks(line, 'foo')).toEqual([ + { + text: 'Fo', + modifiers: { bold: true }, + highlight: true, + }, + { + text: 'o', + modifiers: {}, + highlight: true, + }, + { + text: 'Foo', + modifiers: {}, + highlight: true, + }, + { + text: 'Fo', + modifiers: {}, + highlight: true, + }, + { + text: 'o', + modifiers: { italic: true }, + highlight: true, + }, + { + text: 'BarBaz', + modifiers: { italic: true }, + }, + { + text: 'Foo', + modifiers: { foreground: 'blue' }, + highlight: true, + }, + { + text: 'Foo', + modifiers: { italic: true }, + highlight: true, + }, + { + text: 'Foo', + modifiers: { italic: true }, + highlight: true, + }, + { + text: 'F', + modifiers: { bold: true }, + highlight: true, + }, + { + text: 'o', + modifiers: {}, + highlight: true, + }, + { + text: 'o', + modifiers: { bold: true }, + highlight: true, + }, + ]); + }); +}); diff --git a/packages/core-components/src/components/LogViewer/LogLine.tsx b/packages/core-components/src/components/LogViewer/LogLine.tsx index 66197e9865..315ecb0222 100644 --- a/packages/core-components/src/components/LogViewer/LogLine.tsx +++ b/packages/core-components/src/components/LogViewer/LogLine.tsx @@ -82,45 +82,54 @@ export function calculateHighlightedChunks( const chunks = new Array(); - let chunkOffset = 0; + let lineOffset = 0; let nextResult = results.shift(); for (const chunk of line.chunks) { const { text, modifiers } = chunk; - if (!nextResult || chunkOffset + text.length < nextResult.start) { + if (!nextResult || lineOffset + text.length < nextResult.start) { chunks.push(chunk); - chunkOffset += text.length; + lineOffset += text.length; continue; } let localOffset = 0; while (nextResult) { - let localStart = nextResult.start - chunkOffset; - if (localStart < 0) { - localStart = 0; + const localStart = Math.max(nextResult.start - lineOffset, 0); + if (localStart > text.length) { + break; // The next result is not in this chunk } - const localEnd = nextResult.end - chunkOffset; - const beforeMatch = text.slice(localOffset, localStart); - const match = text.slice(localStart, localEnd); - if (beforeMatch) { - chunks.push({ text: beforeMatch, modifiers }); + const localEnd = Math.min(nextResult.end - lineOffset, text.length); + + const hasTextBeforeResult = localStart > localOffset; + if (hasTextBeforeResult) { + chunks.push({ text: text.slice(localOffset, localStart), modifiers }); + } + const hasResultText = localEnd > localStart; + if (hasResultText) { + chunks.push({ + modifiers, + highlight: true, + text: text.slice(localStart, localEnd), + }); } - chunks.push({ text: match, modifiers, highlight: true }); - localOffset = localStart + match.length; + localOffset = localEnd; - if (match.length === searchText.length) { + const foundCompleteResult = nextResult.end - lineOffset === localEnd; + if (foundCompleteResult) { nextResult = results.shift(); } else { - break; + break; // The rest of the result is in the following chunks } } - if (localOffset < text.length) { + const hasTextAfterResult = localOffset < text.length; + if (hasTextAfterResult) { chunks.push({ text: text.slice(localOffset), modifiers }); } - chunkOffset += text.length; + lineOffset += text.length; } return chunks;