mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-12 11:44:45 -04:00
Apply runner filters to requests/iterations as well as tests [INS-3348] (#10353)
* Hide empty iterations/requests in runner results if there are no tests matching the filter * Add an empty state in case there are no tests/requests matching the selected filter * Add tests to cover changes in runner filter behavior * fix: minor improvements * chore: rewording Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: George He <hexxa@outlook.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
4 files changed
+172
-23
No files matched your search
@@ -0,0 +1,107 @@
|
||||
import type { RequestTestResult } from 'insomnia-data';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { filterTestResults, hasMatchingTestResults, isFilterEngaged } from '../request-test-result-pane';
|
||||
|
||||
const testResult = (overrides: Partial<RequestTestResult> = {}): RequestTestResult => ({
|
||||
testCase: 'renders a response',
|
||||
status: 'passed',
|
||||
executionTime: 1,
|
||||
category: 'after-response',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('filterTestResults', () => {
|
||||
const results: RequestTestResult[] = [
|
||||
testResult({ testCase: 'status code is 200', status: 'passed' }),
|
||||
testResult({ testCase: 'has expected body', status: 'failed' }),
|
||||
testResult({ testCase: 'pre-request runs', status: 'skipped' }),
|
||||
];
|
||||
|
||||
it('returns everything, with original indexes, when targetTests is "all" and there is no text filter', () => {
|
||||
expect(filterTestResults(results, 'all', '')).toEqual([
|
||||
{ result: results[0], index: 0 },
|
||||
{ result: results[1], index: 1 },
|
||||
{ result: results[2], index: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('filters to only passed results', () => {
|
||||
expect(filterTestResults(results, 'passed', '')).toEqual([{ result: results[0], index: 0 }]);
|
||||
});
|
||||
|
||||
it('filters to only failed results', () => {
|
||||
expect(filterTestResults(results, 'failed', '')).toEqual([{ result: results[1], index: 1 }]);
|
||||
});
|
||||
|
||||
it('filters to only skipped results', () => {
|
||||
expect(filterTestResults(results, 'skipped', '')).toEqual([{ result: results[2], index: 2 }]);
|
||||
});
|
||||
|
||||
it('applies the text filter on top of the status filter', () => {
|
||||
expect(filterTestResults(results, 'all', 'expected')).toEqual([{ result: results[1], index: 1 }]);
|
||||
});
|
||||
|
||||
it('returns nothing when the text filter matches no test case in the selected status', () => {
|
||||
expect(filterTestResults(results, 'passed', 'expected')).toEqual([]);
|
||||
});
|
||||
|
||||
it('ignores a whitespace-only text filter', () => {
|
||||
expect(filterTestResults(results, 'all', ' ')).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('returns an empty array when there are no test results to filter', () => {
|
||||
expect(filterTestResults([], 'all', '')).toEqual([]);
|
||||
expect(filterTestResults([], 'passed', '')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isFilterEngaged', () => {
|
||||
it('is not engaged for the default "all" status with no text filter', () => {
|
||||
expect(isFilterEngaged('all', '')).toBe(false);
|
||||
});
|
||||
|
||||
it('is not engaged when the text filter is only whitespace', () => {
|
||||
expect(isFilterEngaged('all', ' ')).toBe(false);
|
||||
});
|
||||
|
||||
it('is engaged when a non-"all" status is selected', () => {
|
||||
expect(isFilterEngaged('passed', '')).toBe(true);
|
||||
expect(isFilterEngaged('failed', '')).toBe(true);
|
||||
expect(isFilterEngaged('skipped', '')).toBe(true);
|
||||
});
|
||||
|
||||
it('is engaged when a text filter is present', () => {
|
||||
expect(isFilterEngaged('all', 'expected')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasMatchingTestResults', () => {
|
||||
const results: RequestTestResult[] = [testResult({ testCase: 'status code is 200', status: 'passed' })];
|
||||
|
||||
it('is true when no filter is engaged, even with no test results at all', () => {
|
||||
expect(hasMatchingTestResults([], 'all', '')).toBe(true);
|
||||
expect(hasMatchingTestResults(results, 'all', '')).toBe(true);
|
||||
});
|
||||
|
||||
it('is true when a filter is engaged and at least one result matches', () => {
|
||||
expect(hasMatchingTestResults(results, 'passed', '')).toBe(true);
|
||||
});
|
||||
|
||||
it('is false when a status filter is engaged and no result matches', () => {
|
||||
expect(hasMatchingTestResults(results, 'failed', '')).toBe(false);
|
||||
expect(hasMatchingTestResults(results, 'skipped', '')).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when a status filter is engaged and there are no results at all', () => {
|
||||
expect(hasMatchingTestResults([], 'failed', '')).toBe(false);
|
||||
});
|
||||
|
||||
it('is false when a text filter is engaged and no result matches', () => {
|
||||
expect(hasMatchingTestResults(results, 'all', 'nonexistent test name')).toBe(false);
|
||||
});
|
||||
|
||||
it('is true when a text filter is engaged and a result matches', () => {
|
||||
expect(hasMatchingTestResults(results, 'all', 'status code')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
type RunnerLiveItem,
|
||||
} from '../../../common/runner-feedback';
|
||||
import { RenderedText } from '../rendered-text';
|
||||
import { RequestTestResultRows } from './request-test-result-pane';
|
||||
import { hasMatchingTestResults, RequestTestResultRows, type TargetTestType } from './request-test-result-pane';
|
||||
|
||||
interface Props {
|
||||
item: RunnerResultPerRequest | RunnerLiveItem;
|
||||
resultFilter?: string;
|
||||
targetTests?: string;
|
||||
targetTests?: TargetTestType;
|
||||
onSkip?: () => void;
|
||||
testId?: string;
|
||||
defaultExpanded?: boolean;
|
||||
@@ -49,6 +49,10 @@ export const RequestResultCard: FC<Props> = ({
|
||||
|
||||
const requestId = 'requestId' in item ? item.requestId : undefined;
|
||||
|
||||
if (!hasMatchingTestResults(results, targetTests, resultFilter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-testid={testId} className="m-3 rounded-sm border border-solid border-(--hl-md) p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
|
||||
@@ -3,7 +3,7 @@ import { fuzzyMatch } from 'insomnia-data/common';
|
||||
import React, { type FC, useState } from 'react';
|
||||
import { Toolbar } from 'react-aria-components';
|
||||
|
||||
type TargetTestType = 'all' | 'passed' | 'failed' | 'skipped';
|
||||
export type TargetTestType = 'all' | 'passed' | 'failed' | 'skipped';
|
||||
|
||||
const filterClassnames =
|
||||
'mx-1 w-24 text-center rounded-md h-(--line-height-xxs) text-sm cursor-pointer outline-hidden select-none px-2 py-1 hover:bg-[rgba(var(--color-surprise-rgb),50%)] text-(--hl) aria-selected:text-(--color-font-surprise) hover:text-(--color-font-surprise) aria-selected:bg-[rgba(var(--color-surprise-rgb),40%)] transition-colors duration-300';
|
||||
@@ -13,23 +13,15 @@ const activeFilterClassnames =
|
||||
export interface RequestTestResultRowsProps {
|
||||
requestTestResults: RequestTestResult[];
|
||||
resultFilter: string;
|
||||
targetTests: string;
|
||||
targetTests: TargetTestType;
|
||||
}
|
||||
|
||||
export const RequestTestResultRows: FC<RequestTestResultRowsProps> = ({
|
||||
requestTestResults,
|
||||
resultFilter,
|
||||
targetTests,
|
||||
}: RequestTestResultRowsProps) => {
|
||||
if (requestTestResults.length === 0) {
|
||||
return (
|
||||
<div className="my-2 w-full pl-3 text-sm text-neutral-400">
|
||||
No test was detected, add test cases in scripts to see results.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const testResultRows = requestTestResults
|
||||
export function filterTestResults(
|
||||
requestTestResults: RequestTestResult[],
|
||||
targetTests: TargetTestType,
|
||||
resultFilter: string,
|
||||
): { result: RequestTestResult; index: number }[] {
|
||||
return requestTestResults
|
||||
.map((result, index) => ({ result, index }))
|
||||
.filter(({ result }) => {
|
||||
switch (targetTests) {
|
||||
@@ -56,7 +48,38 @@ export const RequestTestResultRows: FC<RequestTestResultRowsProps> = ({
|
||||
}
|
||||
|
||||
return Boolean(fuzzyMatch(resultFilter, result.testCase, { splitSpace: false, loose: true })?.indexes);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export function isFilterEngaged(targetTests: TargetTestType, resultFilter: string): boolean {
|
||||
return targetTests !== 'all' || resultFilter.trim() !== '';
|
||||
}
|
||||
|
||||
export function hasMatchingTestResults(
|
||||
requestTestResults: RequestTestResult[],
|
||||
targetTests: TargetTestType,
|
||||
resultFilter: string,
|
||||
): boolean {
|
||||
return (
|
||||
!isFilterEngaged(targetTests, resultFilter) ||
|
||||
filterTestResults(requestTestResults, targetTests, resultFilter).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
export const RequestTestResultRows: FC<RequestTestResultRowsProps> = ({
|
||||
requestTestResults,
|
||||
resultFilter,
|
||||
targetTests,
|
||||
}: RequestTestResultRowsProps) => {
|
||||
if (requestTestResults.length === 0) {
|
||||
return (
|
||||
<div className="my-2 w-full pl-3 text-sm text-neutral-400">
|
||||
No test was detected, add test cases in scripts to see results.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const testResultRows = filterTestResults(requestTestResults, targetTests, resultFilter)
|
||||
.map(({ result, index }) => {
|
||||
const statusText = {
|
||||
passed: 'PASS',
|
||||
|
||||
@@ -6,8 +6,7 @@ import { useRootLoaderData } from '~/root';
|
||||
import { Hotkey } from '~/ui/components/hotkey';
|
||||
|
||||
import { RequestResultCard } from './request-result-card';
|
||||
|
||||
type TargetTestType = 'all' | 'passed' | 'failed' | 'skipped';
|
||||
import { hasMatchingTestResults, type TargetTestType } from './request-test-result-pane';
|
||||
|
||||
const filterClassnames =
|
||||
'mx-1 w-24 text-center rounded-md h-(--line-height-xxs) text-sm cursor-pointer outline-hidden select-none px-2 py-1 hover:bg-[rgba(var(--color-surprise-rgb),50%)] text-(--hl) aria-selected:text-(--color-font-surprise) hover:text-(--color-font-surprise) aria-selected:bg-[rgba(var(--color-surprise-rgb),40%)] transition-colors duration-300';
|
||||
@@ -56,6 +55,14 @@ export const RunnerTestResultPane: FC<Props> = ({ result }) => {
|
||||
const key = `runner-test-result-iteration-${i + 1}`;
|
||||
|
||||
if (Array.isArray(iterationResults)) {
|
||||
const hasVisibleRequest = iterationResults.some(
|
||||
requestTestResult => hasMatchingTestResults(requestTestResult.results ?? [], targetTests, resultFilter),
|
||||
);
|
||||
|
||||
if (!hasVisibleRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resultByRequest = iterationResults.map((requestTestResult: RunnerResultPerRequest, reqIndex: number) => {
|
||||
const key = `request-test-result-${reqIndex}`;
|
||||
return (
|
||||
@@ -80,9 +87,11 @@ export const RunnerTestResultPane: FC<Props> = ({ result }) => {
|
||||
return <div key={key}>Invalid test result format</div>;
|
||||
});
|
||||
|
||||
const hasVisibleResults = resultsByIteration.some(Boolean);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex h-full flex-col divide-y divide-solid divide-(--hl-md)">
|
||||
<div className="flex h-[calc(100%-var(--line-height-sm))] flex-col divide-y divide-solid divide-(--hl-md)">
|
||||
<div className="h-[calc(100%-var(--line-height-sm))]">
|
||||
<Toolbar className="box-border flex h-(--line-height-sm) flex-row items-center overflow-x-auto border-b border-solid border-b-(--hl-md) pl-2 text-(--font-size-sm)">
|
||||
<button
|
||||
@@ -111,7 +120,13 @@ export const RunnerTestResultPane: FC<Props> = ({ result }) => {
|
||||
</button>
|
||||
</Toolbar>
|
||||
<div className="h-[calc(100%-var(--line-height-sm))] w-auto overflow-x-auto overflow-y-auto">
|
||||
{resultsByIteration}
|
||||
{hasVisibleResults ? (
|
||||
resultsByIteration
|
||||
) : (
|
||||
<div className="mt-5 text-center">
|
||||
<div>No matching requests/tests to display</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Toolbar className="box-border flex h-(--line-height-sm) shrink-0 flex-row items-center overflow-x-auto text-(--font-size-sm)">
|
||||
|
||||
Reference in new issue
Block a user