From 6469a6eaebc8c67ff28407455b0bb4571c8420e3 Mon Sep 17 00:00:00 2001 From: Vivek Thuravupala <2700229+godfrzero@users.noreply.github.com> Date: Mon, 10 Aug 2026 23:10:59 -0700 Subject: [PATCH] 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 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../request-test-result-pane.test.ts | 107 ++++++++++++++++++ .../components/panes/request-result-card.tsx | 8 +- .../panes/request-test-result-pane.tsx | 57 +++++++--- .../panes/runner-test-result-pane.tsx | 23 +++- 4 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 packages/insomnia/src/ui/components/panes/__tests__/request-test-result-pane.test.ts diff --git a/packages/insomnia/src/ui/components/panes/__tests__/request-test-result-pane.test.ts b/packages/insomnia/src/ui/components/panes/__tests__/request-test-result-pane.test.ts new file mode 100644 index 0000000000..9cb3198ce0 --- /dev/null +++ b/packages/insomnia/src/ui/components/panes/__tests__/request-test-result-pane.test.ts @@ -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 => ({ + 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); + }); +}); diff --git a/packages/insomnia/src/ui/components/panes/request-result-card.tsx b/packages/insomnia/src/ui/components/panes/request-result-card.tsx index 6449a8064b..03a7eddc01 100644 --- a/packages/insomnia/src/ui/components/panes/request-result-card.tsx +++ b/packages/insomnia/src/ui/components/panes/request-result-card.tsx @@ -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 = ({ const requestId = 'requestId' in item ? item.requestId : undefined; + if (!hasMatchingTestResults(results, targetTests, resultFilter)) { + return null; + } + return (
diff --git a/packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx b/packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx index 2d34786508..9bccd193cb 100644 --- a/packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/request-test-result-pane.tsx @@ -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 = ({ - requestTestResults, - resultFilter, - targetTests, -}: RequestTestResultRowsProps) => { - if (requestTestResults.length === 0) { - return ( -
- No test was detected, add test cases in scripts to see results. -
- ); - } - - 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 = ({ } 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 = ({ + requestTestResults, + resultFilter, + targetTests, +}: RequestTestResultRowsProps) => { + if (requestTestResults.length === 0) { + return ( +
+ No test was detected, add test cases in scripts to see results. +
+ ); + } + + const testResultRows = filterTestResults(requestTestResults, targetTests, resultFilter) .map(({ result, index }) => { const statusText = { passed: 'PASS', diff --git a/packages/insomnia/src/ui/components/panes/runner-test-result-pane.tsx b/packages/insomnia/src/ui/components/panes/runner-test-result-pane.tsx index ad171edc4e..98b9919f88 100644 --- a/packages/insomnia/src/ui/components/panes/runner-test-result-pane.tsx +++ b/packages/insomnia/src/ui/components/panes/runner-test-result-pane.tsx @@ -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 = ({ 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 = ({ result }) => { return
Invalid test result format
; }); + const hasVisibleResults = resultsByIteration.some(Boolean); + return ( <> -
+
- {resultsByIteration} + {hasVisibleResults ? ( + resultsByIteration + ) : ( +
+
No matching requests/tests to display
+
+ )}