diff --git a/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx
new file mode 100644
index 00000000000..ec39fff98e8
--- /dev/null
+++ b/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.test.tsx
@@ -0,0 +1,102 @@
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { createLokiDatasource } from '../../mocks';
+import { LokiVisualQueryBinary } from '../types';
+
+import { NestedQuery, Props as NestedQueryProps } from './NestedQuery';
+
+type Operator = '+' | '-' | '*' | '/' | '%' | '^' | '==' | '!=' | '>' | '<' | '>=' | '<=';
+type VectorMatchType = 'on' | 'ignoring';
+
+const createMockProps = (
+ operator: Operator = '/',
+ vectorMatchesType: VectorMatchType = 'on',
+ showExplain = false
+): NestedQueryProps => {
+ const nestedQuery: LokiVisualQueryBinary = {
+ operator: operator,
+ vectorMatchesType: vectorMatchesType,
+ query: {
+ labels: [],
+ operations: [],
+ },
+ };
+
+ const datasource = createLokiDatasource();
+
+ const props: NestedQueryProps = {
+ nestedQuery: nestedQuery,
+ index: 0,
+ datasource: datasource,
+ onChange: jest.fn(),
+ onRemove: jest.fn(),
+ onRunQuery: jest.fn(),
+ showExplain: showExplain,
+ };
+
+ return props;
+};
+
+// All test assertions need to be awaited for, because the component uses `useEffect` to update the state.
+
+describe('render all elements', () => {
+ it('renders the operator label', async () => {
+ const props = createMockProps();
+ render();
+ expect(await screen.findByText('Operator')).toBeInTheDocument();
+ });
+
+ it('renders the expected operator value', async () => {
+ const props = createMockProps('!=');
+ render();
+ expect(await screen.findByText('!=')).toBeInTheDocument();
+ });
+
+ it('renders the vector matches label', async () => {
+ const props = createMockProps();
+ render();
+ expect(await screen.findByText('Vector matches')).toBeInTheDocument();
+ });
+
+ it('renders the expected vector matches value', async () => {
+ const props = createMockProps(undefined, 'ignoring');
+ render();
+ expect(await screen.findByText('ignoring')).toBeInTheDocument();
+ });
+});
+
+describe('exit the nested query', () => {
+ it('onRemove is called when clicking (x)', async () => {
+ const props = createMockProps();
+ render();
+ fireEvent.click(await screen.findByLabelText('Remove nested query'));
+ await waitFor(() => expect(props.onRemove).toHaveBeenCalledTimes(1));
+ });
+});
+
+describe('change operator', () => {
+ it('onChange is called with the correct args', async () => {
+ const props = createMockProps('/', 'on');
+ render();
+ userEvent.click(await screen.findByLabelText('Select operator'));
+ fireEvent.click(await screen.findByText('+'));
+ await waitFor(() => expect(props.onChange).toHaveBeenCalledTimes(1));
+ await waitFor(() =>
+ expect(props.onChange).toHaveBeenCalledWith(0, {
+ operator: '+',
+ vectorMatchesType: 'on',
+ query: { labels: [], operations: [] },
+ })
+ );
+ });
+});
+
+describe('explain mode', () => {
+ it('shows the explanation when set to true', async () => {
+ const props = createMockProps(undefined, undefined, true);
+ render();
+ expect(await screen.findByText('Fetch all log lines matching label filters.')).toBeInTheDocument();
+ });
+});
diff --git a/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.tsx b/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.tsx
index 3e0910efec8..569332f62a7 100644
--- a/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.tsx
+++ b/public/app/plugins/datasource/loki/querybuilder/components/NestedQuery.tsx
@@ -29,6 +29,7 @@ export const NestedQuery = React.memo(
- onRemove(index)} />
+ onRemove(index)} />