Chore: Convert `VirtualizedTraceView.test.tsx` to RTL (#61589)

* Chore: Convert VirtualizedTraceView.test.tsx to RTL

* Don't need to change shared component

Co-authored-by: André Pereira <adrapereira@gmail.com>
pull/61655/head
Hamas Shafiq 2 years ago committed by GitHub
parent 5e8866ed5a
commit 7f4b2fcb9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .betterer.results
  2. 2
      packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx
  3. 465
      packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.tsx

@ -7,9 +7,6 @@ exports[`no enzyme tests`] = {
value: `{
"packages/grafana-ui/src/components/QueryField/QueryField.test.tsx:2976628669": [
[0, 26, 13, "RegExp match", "2409514259"]
],
"packages/jaeger-ui-components/src/TraceTimelineViewer/VirtualizedTraceView.test.tsx:3891071965": [
[13, 42, 13, "RegExp match", "2409514259"]
]
}`
};

@ -8,7 +8,7 @@ import { styleMixins, useStyles2 } from '../../themes';
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
import { getPropertiesForVariant } from '../Button';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '../Tooltip/Tooltip';
import { Tooltip } from '../Tooltip';
type CommonProps = {
/** Icon name */

@ -11,86 +11,44 @@
// 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 { shallow, mount, ShallowWrapper } from 'enzyme';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { TNil } from 'src/types';
import { Trace, TraceSpan } from 'src/types/trace';
import { Trace } from 'src/types/trace';
import traceGenerator from '../demo/trace-generators';
import transformTraceData from '../model/transform-trace-data';
import ListView from './ListView';
import SpanBarRow, { SpanBarRowProps } from './SpanBarRow';
import DetailState from './SpanDetail/DetailState';
import SpanDetailRow, { SpanDetailRowProps } from './SpanDetailRow';
import SpanTreeOffset from './SpanTreeOffset';
import VirtualizedTraceView, {
DEFAULT_HEIGHTS,
UnthemedVirtualizedTraceView,
VirtualizedTraceViewProps,
} from './VirtualizedTraceView';
import VirtualizedTraceView, { VirtualizedTraceViewProps } from './VirtualizedTraceView';
jest.mock('./SpanTreeOffset');
describe('<VirtualizedTraceViewImpl>', () => {
let wrapper: ShallowWrapper<VirtualizedTraceViewProps, {}, UnthemedVirtualizedTraceView>;
let instance: UnthemedVirtualizedTraceView;
const trace = transformTraceData(traceGenerator.trace({ numberOfSpans: 10 }))!;
const topOfExploreViewRef = jest.fn();
const props = {
childrenHiddenIDs: new Set(),
childrenToggle: jest.fn(),
clearShouldScrollToFirstUiFindMatch: jest.fn(),
currentViewRangeTime: [0.25, 0.75],
detailLogItemToggle: jest.fn(),
detailLogsToggle: jest.fn(),
detailProcessToggle: jest.fn(),
detailStates: new Map(),
detailTagsToggle: jest.fn(),
detailToggle: jest.fn(),
findMatchesIDs: null,
registerAccessors: jest.fn(),
scrollToFirstVisibleSpan: jest.fn(),
setSpanNameColumnWidth: jest.fn(),
setTrace: jest.fn(),
shouldScrollToFirstUiFindMatch: false,
spanNameColumnWidth: 0.5,
trace,
uiFind: 'uiFind',
topOfExploreViewRef,
} as unknown as VirtualizedTraceViewProps;
function expandRow(rowIndex: number) {
const detailStates = new Map();
const detailState = new DetailState();
detailStates.set(trace.spans[rowIndex].spanID, detailState);
wrapper.setProps({ detailStates });
return detailState;
}
function addSpansAndCollapseTheirParent(newSpanID = 'some-id') {
const childrenHiddenIDs = new Set([newSpanID]);
const spans = [
trace.spans[0],
// this span is condidered to have collapsed children
{ spanID: newSpanID, depth: 1, traceID: trace.traceID },
// these two "spans" are children and should be hidden
{ depth: 2 },
{ depth: 3 },
...trace.spans.slice(1),
] as TraceSpan[];
const _trace = { ...trace, spans };
wrapper.setProps({ childrenHiddenIDs, trace: _trace });
return spans;
}
function updateSpan(srcTrace: Trace, spanIndex: number, update: Partial<TraceSpan>) {
const span = { ...srcTrace.spans[spanIndex], ...update };
const spans = [...srcTrace.spans.slice(0, spanIndex), span, ...srcTrace.spans.slice(spanIndex + 1)];
return { ...srcTrace, spans };
}
const trace = transformTraceData(traceGenerator.trace({ numberOfSpans: 2 }))!;
const topOfExploreViewRef = jest.fn();
let props = {
childrenHiddenIDs: new Set(),
childrenToggle: jest.fn(),
clearShouldScrollToFirstUiFindMatch: jest.fn(),
currentViewRangeTime: [0.25, 0.75],
detailLogItemToggle: jest.fn(),
detailLogsToggle: jest.fn(),
detailProcessToggle: jest.fn(),
detailStates: new Map(),
detailTagsToggle: jest.fn(),
detailToggle: jest.fn(),
findMatchesIDs: null,
registerAccessors: jest.fn(),
scrollToFirstVisibleSpan: jest.fn(),
setSpanNameColumnWidth: jest.fn(),
setTrace: jest.fn(),
shouldScrollToFirstUiFindMatch: false,
spanNameColumnWidth: 0.5,
trace,
uiFind: 'uiFind',
topOfExploreViewRef,
} as unknown as VirtualizedTraceViewProps;
describe('<VirtualizedTraceViewImpl>', () => {
beforeEach(() => {
jest.mocked(SpanTreeOffset).mockReturnValue(<div />);
Object.keys(props).forEach((key) => {
@ -98,344 +56,71 @@ describe('<VirtualizedTraceViewImpl>', () => {
(props[key as keyof VirtualizedTraceViewProps] as jest.Mock).mockReset();
}
});
wrapper = shallow(<VirtualizedTraceView {...(props as unknown as VirtualizedTraceViewProps)} />)
.dive()
.dive();
instance = wrapper.instance();
});
it('renders without exploding', () => {
expect(wrapper).toBeDefined();
});
it('renders when a trace is not set', () => {
wrapper.setProps({ trace: null as unknown as Trace });
expect(wrapper).toBeDefined();
});
it('renders a ListView', () => {
expect(wrapper.find(ListView)).toBeDefined();
});
it('renders scrollToTopButton', () => {
expect(wrapper.find({ title: 'Scroll to top' }).exists()).toBeTruthy();
});
it('sets the trace for global state.traceTimeline', () => {
expect(jest.mocked(props.setTrace).mock.calls).toEqual([[trace, props.uiFind]]);
expect(jest.mocked(props.setTrace).mock.calls).toEqual([[trace, props.uiFind]]);
jest.mocked(props.setTrace).mockReset();
const traceID = 'some-other-id';
const _trace = { ...trace, traceID };
wrapper.setProps({ trace: _trace });
expect(jest.mocked(props.setTrace).mock.calls).toEqual([[_trace, props.uiFind]]);
});
describe('props.registerAccessors', () => {
let lv: ListView;
let expectedArg: {
getBottomRowIndexVisible: () => void;
getTopRowIndexVisible: () => void;
getViewHeight: () => number;
getRowPosition: (index: number) => { height: number; y: number };
getViewRange: () => [number, number];
getSearchedSpanIDs: () => Set<string> | TNil;
getCollapsedChildren: () => Set<string>;
mapRowIndexToSpanIndex: (index: number) => number;
mapSpanIndexToRowIndex: (index: number) => number;
};
beforeEach(() => {
const getBottomRowIndexVisible = () => {};
const getTopRowIndexVisible = () => {};
lv = {
getViewHeight: () => {},
getBottomVisibleIndex: getBottomRowIndexVisible,
getTopVisibleIndex: getTopRowIndexVisible,
getRowPosition: () => {},
} as unknown as ListView;
expectedArg = {
getBottomRowIndexVisible,
getTopRowIndexVisible,
getViewHeight: lv.getViewHeight,
getRowPosition: lv.getRowPosition,
getViewRange: instance.getViewRange,
getSearchedSpanIDs: instance.getSearchedSpanIDs,
getCollapsedChildren: instance.getCollapsedChildren,
mapRowIndexToSpanIndex: instance.mapRowIndexToSpanIndex,
mapSpanIndexToRowIndex: instance.mapSpanIndexToRowIndex,
};
});
it('invokes when the listView is set', () => {
expect(jest.mocked(props.registerAccessors).mock.calls.length).toBe(0);
instance.setListView(lv);
expect(jest.mocked(props.registerAccessors).mock.calls).toEqual([[expectedArg]]);
});
it('invokes when registerAccessors changes', () => {
const registerAccessors = jest.fn();
instance.setListView(lv);
wrapper.setProps({ registerAccessors });
expect(registerAccessors.mock.calls).toEqual([[expectedArg]]);
});
});
it('returns the current view range via getViewRange()', () => {
expect(instance.getViewRange()).toBe(props.currentViewRangeTime);
});
it('returns findMatchesIDs via getSearchedSpanIDs()', () => {
const findMatchesIDs: Set<string> = new Set();
wrapper.setProps({ findMatchesIDs });
expect(instance.getSearchedSpanIDs()).toBe(findMatchesIDs);
});
it('returns childrenHiddenIDs via getCollapsedChildren()', () => {
const childrenHiddenIDs: Set<string> = new Set();
wrapper.setProps({ childrenHiddenIDs });
expect(instance.getCollapsedChildren()).toBe(childrenHiddenIDs);
});
describe('mapRowIndexToSpanIndex() maps row index to span index', () => {
it('works when nothing is collapsed or expanded', () => {
const i = trace.spans.length - 1;
expect(instance.mapRowIndexToSpanIndex(i)).toBe(i);
});
it('works when a span is expanded', () => {
expandRow(1);
expect(instance.mapRowIndexToSpanIndex(0)).toBe(0);
expect(instance.mapRowIndexToSpanIndex(1)).toBe(1);
expect(instance.mapRowIndexToSpanIndex(2)).toBe(1);
expect(instance.mapRowIndexToSpanIndex(3)).toBe(2);
});
it('works when a parent span is collapsed', () => {
addSpansAndCollapseTheirParent();
expect(instance.mapRowIndexToSpanIndex(0)).toBe(0);
expect(instance.mapRowIndexToSpanIndex(1)).toBe(1);
expect(instance.mapRowIndexToSpanIndex(2)).toBe(4);
expect(instance.mapRowIndexToSpanIndex(3)).toBe(5);
});
});
describe('mapSpanIndexToRowIndex() maps span index to row index', () => {
it('works when nothing is collapsed or expanded', () => {
const i = trace.spans.length - 1;
expect(instance.mapSpanIndexToRowIndex(i)).toBe(i);
});
it('works when a span is expanded', () => {
expandRow(1);
expect(instance.mapSpanIndexToRowIndex(0)).toBe(0);
expect(instance.mapSpanIndexToRowIndex(1)).toBe(1);
expect(instance.mapSpanIndexToRowIndex(2)).toBe(3);
expect(instance.mapSpanIndexToRowIndex(3)).toBe(4);
});
it('works when a parent span is collapsed', () => {
addSpansAndCollapseTheirParent();
expect(instance.mapSpanIndexToRowIndex(0)).toBe(0);
expect(instance.mapSpanIndexToRowIndex(1)).toBe(1);
expect(() => instance.mapSpanIndexToRowIndex(2)).toThrow();
expect(() => instance.mapSpanIndexToRowIndex(3)).toThrow();
expect(instance.mapSpanIndexToRowIndex(4)).toBe(2);
});
});
it('renders service name, operation name and duration for each span', () => {
render(<VirtualizedTraceView {...props} />);
expect(screen.getAllByText(trace.services[0].name)).toBeTruthy();
describe('getKeyFromIndex() generates a "key" from a row index', () => {
function verify(input: number, output: string) {
expect(instance.getKeyFromIndex(input)).toBe(output);
if (trace.services.length > 1) {
expect(screen.getAllByText(trace.services[1].name)).toBeTruthy();
}
it('works when nothing is expanded or collapsed', () => {
verify(0, `${trace.spans[0].traceID}--${trace.spans[0].spanID}--bar`);
});
it('works when rows are expanded', () => {
expandRow(1);
verify(1, `${trace.spans[1].traceID}--${trace.spans[1].spanID}--bar`);
verify(2, `${trace.spans[1].traceID}--${trace.spans[1].spanID}--detail`);
verify(3, `${trace.spans[2].traceID}--${trace.spans[2].spanID}--bar`);
});
expect(screen.getAllByText(trace.spans[0].operationName)).toBeTruthy();
expect(screen.getAllByText(trace.spans[1].operationName)).toBeTruthy();
it('works when a parent span is collapsed', () => {
const spans = addSpansAndCollapseTheirParent();
verify(1, `${spans[1].traceID}--${spans[1].spanID}--bar`);
verify(2, `${spans[4].traceID}--${spans[4].spanID}--bar`);
});
});
let durationSpan0 = trace.spans[0].duration;
describe('getIndexFromKey() converts a "key" to the corresponding row index', () => {
function verify(input: string, output: number) {
expect(instance.getIndexFromKey(input)).toBe(output);
if (trace.spans[0].duration >= 1_000_000) {
durationSpan0 = Math.floor(trace.spans[0].duration / 1000000);
} else if (trace.spans[0].duration >= 1000) {
durationSpan0 = Math.floor(trace.spans[0].duration / 1000);
}
it('works when nothing is expanded or collapsed', () => {
verify(`${trace.traceID}--${trace.spans[0].spanID}--bar`, 0);
});
let durationSpan1 = trace.spans[1].duration;
it('works when rows are expanded', () => {
expandRow(1);
verify(`${trace.spans[1].traceID}--${trace.spans[1].spanID}--bar`, 1);
verify(`${trace.spans[1].traceID}--${trace.spans[1].spanID}--detail`, 2);
verify(`${trace.spans[2].traceID}--${trace.spans[2].spanID}--bar`, 3);
});
if (trace.spans[1].duration >= 1_000_000) {
durationSpan1 = Math.floor(trace.spans[1].duration / 1000000);
} else if (trace.spans[1].duration >= 1000) {
durationSpan1 = Math.floor(trace.spans[1].duration / 1000);
}
it('works when a parent span is collapsed', () => {
const spans = addSpansAndCollapseTheirParent();
verify(`${spans[1].traceID}--${spans[1].spanID}--bar`, 1);
verify(`${spans[4].traceID}--${spans[4].spanID}--bar`, 2);
});
expect(screen.getAllByText(durationSpan0, { exact: false })).toBeTruthy();
expect(screen.getAllByText(durationSpan1, { exact: false })).toBeTruthy();
});
describe('getRowHeight()', () => {
it('returns the expected height for non-detail rows', () => {
expect(instance.getRowHeight(0)).toBe(DEFAULT_HEIGHTS.bar);
});
it('returns the expected height for detail rows that do not have logs', () => {
expandRow(0);
expect(instance.getRowHeight(1)).toBe(DEFAULT_HEIGHTS.detail);
});
it('returns the expected height for detail rows that do have logs', () => {
const logs = [
{
timestamp: Date.now(),
fields: traceGenerator.tags(),
},
];
const altTrace = updateSpan(trace, 0, { logs });
expandRow(0);
wrapper.setProps({ trace: altTrace });
expect(instance.getRowHeight(1)).toBe(DEFAULT_HEIGHTS.detailWithLogs);
});
it('renders without exploding', () => {
render(<VirtualizedTraceView {...props} />);
expect(screen.getByTestId('ListView')).toBeInTheDocument();
expect(screen.getByTitle('Scroll to top')).toBeInTheDocument();
});
describe('renderRow()', () => {
it('renders a SpanBarRow when it is not a detail', () => {
const span = trace.spans[1];
const row = instance.renderRow('some-key', {}, 1, {});
const rowWrapper = shallow(row!);
expect(
rowWrapper.containsMatchingElement(
<SpanBarRow
{...({
clippingLeft: instance.getClipping().left,
clippingRight: instance.getClipping().right,
columnDivision: props.spanNameColumnWidth,
isChildrenExpanded: true,
isDetailExpanded: false,
isMatchingFilter: false,
numTicks: 5,
onDetailToggled: props.detailToggle,
onChildrenToggled: props.childrenToggle,
rpc: undefined,
showErrorIcon: false,
span: span,
} as unknown as SpanBarRowProps)}
/>
)
).toBe(true);
});
it('renders a SpanBarRow with a RPC span if the row is collapsed and a client span', () => {
const clientTags = [{ key: 'span.kind', value: 'client' }, ...trace.spans[0].tags];
const serverTags = [{ key: 'span.kind', value: 'server' }, ...trace.spans[1].tags];
let altTrace = updateSpan(trace, 0, { tags: clientTags });
altTrace = updateSpan(altTrace, 1, { tags: serverTags });
const childrenHiddenIDs = new Set([altTrace.spans[0].spanID]);
wrapper.setProps({ childrenHiddenIDs, trace: altTrace });
const rowWrapper = mount(instance.renderRow('some-key', {}, 0, {})!);
const spanBarRow = rowWrapper.find(SpanBarRow);
expect(spanBarRow.length).toBe(1);
expect(spanBarRow.prop('rpc')).toBeDefined();
});
it('renders a SpanDetailRow when it is a detail', () => {
const detailState = expandRow(1);
const span = trace.spans[1];
const row = instance.renderRow('some-key', {}, 2, {});
const rowWrapper = shallow(row!);
expect(
rowWrapper.containsMatchingElement(
<SpanDetailRow
{...({
columnDivision: props.spanNameColumnWidth,
onDetailToggled: props.detailToggle,
detailState: detailState,
logItemToggle: props.detailLogItemToggle,
logsToggle: props.detailLogsToggle,
processToggle: props.detailProcessToggle,
span: span,
tagsToggle: props.detailTagsToggle,
} as unknown as SpanDetailRowProps)}
/>
)
).toBe(true);
});
it('renders a SpanBarRow with a client span and no instrumented server span', () => {
const externServiceName = 'externalServiceTest';
const leafSpan = trace.spans.find((span) => !span.hasChildren);
const leafSpanIndex = trace.spans.indexOf(leafSpan!);
const clientTags = [
{ key: 'span.kind', value: 'client' },
{ key: 'peer.service', value: externServiceName },
...leafSpan!.tags,
];
const altTrace = updateSpan(trace, leafSpanIndex, { tags: clientTags });
wrapper.setProps({ trace: altTrace });
const rowWrapper = mount(instance.renderRow('some-key', {}, leafSpanIndex, {})!);
const spanBarRow = rowWrapper.find(SpanBarRow);
expect(spanBarRow.length).toBe(1);
expect(spanBarRow.prop('noInstrumentedServer')).not.toBeNull();
});
it('renders when a trace is not set', () => {
props = { ...props, trace: null as unknown as Trace };
render(<VirtualizedTraceView {...props} />);
expect(screen.getByTestId('ListView')).toBeInTheDocument();
expect(screen.getByTitle('Scroll to top')).toBeInTheDocument();
});
describe('shouldScrollToFirstUiFindMatch', () => {
const propsWithTrueShouldScrollToFirstUiFindMatch = { ...props, shouldScrollToFirstUiFindMatch: true };
beforeEach(() => {
jest.mocked(props.scrollToFirstVisibleSpan).mockReset();
jest.mocked(props.clearShouldScrollToFirstUiFindMatch).mockReset();
});
it('calls props.scrollToFirstVisibleSpan if shouldScrollToFirstUiFindMatch is true', () => {
expect(props.scrollToFirstVisibleSpan).not.toHaveBeenCalled();
expect(props.clearShouldScrollToFirstUiFindMatch).not.toHaveBeenCalled();
wrapper.setProps(propsWithTrueShouldScrollToFirstUiFindMatch);
expect(props.scrollToFirstVisibleSpan).toHaveBeenCalledTimes(1);
expect(props.clearShouldScrollToFirstUiFindMatch).toHaveBeenCalledTimes(1);
});
describe('shouldComponentUpdate', () => {
it('returns true if props.shouldScrollToFirstUiFindMatch changes to true', () => {
expect(wrapper.instance().shouldComponentUpdate(propsWithTrueShouldScrollToFirstUiFindMatch)).toBe(true);
});
it('returns true if props.shouldScrollToFirstUiFindMatch changes to false and another props change', () => {
const propsWithOtherDifferenceAndTrueshouldScrollToFirstUiFindMatch = {
...propsWithTrueShouldScrollToFirstUiFindMatch,
clearShouldScrollToFirstUiFindMatch: () => {},
};
wrapper.setProps(propsWithOtherDifferenceAndTrueshouldScrollToFirstUiFindMatch);
expect(wrapper.instance().shouldComponentUpdate(props)).toBe(true);
});
it('renders ListView', () => {
render(<VirtualizedTraceView {...props} />);
expect(screen.getByTestId('ListView')).toBeInTheDocument();
});
it('returns false if props.shouldScrollToFirstUiFindMatch changes to false and no other props change', () => {
wrapper.setProps(propsWithTrueShouldScrollToFirstUiFindMatch);
expect(wrapper.instance().shouldComponentUpdate(props)).toBe(false);
});
it('renders scrollToTopButton', () => {
render(<VirtualizedTraceView {...props} />);
expect(
screen.getByRole('button', {
name: /Scroll to top/i,
})
).toBeInTheDocument();
});
it('returns false if all props are unchanged', () => {
expect(wrapper.instance().shouldComponentUpdate(props)).toBe(false);
});
});
it('sets the trace for global state.traceTimeline', () => {
const traceID = 'some-other-id';
const _trace = { ...trace, traceID };
props = { ...props, trace: _trace };
render(<VirtualizedTraceView {...props} />);
expect(jest.mocked(props.setTrace).mock.calls).toEqual([[_trace, props.uiFind]]);
});
});

Loading…
Cancel
Save