diff --git a/eslint.config.mjs b/eslint.config.mjs index bb044cad0..4ff2f8773 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -82,6 +82,15 @@ export default defineConfig([ projectService: true, }, }, + + rules: { + // Allow intentionally-unused args to be marked with a leading underscore + // (e.g. interface methods that ignore a parameter). + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_' }, + ], + }, }, { // Typescript test files diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index b7645d421..378d344db 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'; import App, { router } from '../components/App'; import getTestData from './utils/fixtures'; -import { act, render, screen } from './utils/test-utils'; +import { act, render, screen, within } from './utils/test-utils'; describe('App', () => { beforeEach(() => { @@ -398,11 +398,13 @@ describe('App', () => { // Wait for the page to load await screen.findByText('a11yr'); - // Verify that Mann-Whitney-U specific columns are rendered - // (this indicates the testVersion defaulted to mann-whitney-u) - expect(screen.getByText('CD')).toBeInTheDocument(); - expect(screen.getByText('Sig')).toBeInTheDocument(); - expect(screen.getByText('CLES (%)')).toBeInTheDocument(); + // Verify a Mann-Whitney-U specific column is rendered — "Δ Median" is + // unique to the Mann-Whitney strategy (Student-T uses "Delta"), so its + // presence in the table header indicates the testVersion defaulted to + // mann-whitney-u. Scope to the header since the "How to read the results" + // panel also names it. + const tableHeader = screen.getByTestId('table-header'); + expect(within(tableHeader).getByText('Δ Median')).toBeInTheDocument(); // Verify the Stats Test Version dropdown shows Mann-Whitney-U as selected const testVersionDropdown = screen.getByRole('combobox', { diff --git a/src/__tests__/CompareResults/ResultsTable.test.tsx b/src/__tests__/CompareResults/ResultsTable.test.tsx index c663ada1d..9dfc08bb3 100644 --- a/src/__tests__/CompareResults/ResultsTable.test.tsx +++ b/src/__tests__/CompareResults/ResultsTable.test.tsx @@ -14,7 +14,13 @@ import getTestData, { augmentCompareMannWhitneyDataWithSeveralRevisions, augmentCompareMannWhitneyDataWithSeveralTests, } from '../utils/fixtures'; -import { renderWithRouter, screen, waitFor, within } from '../utils/test-utils'; +import { + renderWithRouter, + screen, + waitFor, + within, + enableAdvancedColumns, +} from '../utils/test-utils'; function renderWithRoute(component: ReactElement, extraParameters?: string) { return renderWithRouter(component, { @@ -48,7 +54,7 @@ function setupAndRender( // This handy function parses the results page and returns an array of visible // rows. It makes it easy to assert visible rows when filtering them in a // user-friendly way without using snapshots. -function summarizeVisibleRows(testVersion?: TestVersion) { +function summarizeVisibleRows(testVersion?: TestVersion, advanced = false) { const rowGroups = screen.getAllByRole('rowgroup'); const result = []; @@ -83,17 +89,34 @@ function summarizeVisibleRows(testVersion?: TestVersion) { for (const row of rows) { const rowClasses = testVersion === 'mann-whitney-u' - ? [ - '.platform span', - '.median-diff', - '.status', - '.delta', - '.significance', - '.effects', - ] + ? advanced + ? [ + '.platform span', + '.median-diff', + '.status-hint', + '.delta', + '.significance', + '.effects', + ] + : [ + '.platform span', + '.median-diff', + '.status-hint', + '.magnitude', + '.significance', + ] : ['.platform span', '.status', '.delta', '.confidence']; const rowString = rowClasses - .map((selector) => row.querySelector(selector)?.textContent?.trim()) + .map((selector) => { + const cell = row.querySelector(selector); + if (!cell) return undefined; + // Strip icon text (e.g. the median-diff normality warning) + // so the data-focused expectations stay stable; icon presence is + // asserted separately. + const clone = cell.cloneNode(true) as HTMLElement; + clone.querySelectorAll('svg').forEach((svg) => svg.remove()); + return clone.textContent?.trim(); + }) .join(', '); result.push(' - ' + rowString); @@ -690,6 +713,13 @@ describe('Results Table', () => { }); describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersion', () => { + // These tests exercise the full (advanced) table — CD, CLES, Sig columns and + // their sort/filter behavior. The simplified default view (which hides + // CD/CLES and shows the Magnitude column) is covered separately below. + beforeEach(() => { + enableAdvancedColumns(); + }); + it('Should match snapshot', async () => { const { testCompareMannWhitneyData } = getTestData(); @@ -719,12 +749,12 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio setupAndRender(simplerTestCompareData, 'test_version=mann-whitney-u'); await screen.findByText('a11yr'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: spam', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ' rev: devilrabbit', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(screen.getByRole('rowgroup')).toMatchSnapshot(); }); @@ -746,25 +776,25 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); await screen.findByText('a11yr'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - inexistant, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - inexistant, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({}); const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); await clickMenuItem(user, 'Platform', /Windows/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ platform: ['osx', 'linux', 'android', 'ios'], @@ -773,78 +803,78 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio // Clicking Windows again should remove the search param and make the // "inexitant" platform visible again. await clickMenuItem(user, 'Platform', /Windows/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - inexistant, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - inexistant, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({}); await clickMenuItem(user, 'Platform', /Windows/); await clickMenuItem(user, 'Platform', /Linux/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ platform: ['osx', 'android', 'ios'], }); await clickMenuItem(user, 'Platform', /Linux/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ platform: ['osx', 'android', 'ios', 'linux'], }); await clickMenuItem(user, 'Platform', 'Select all values'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - inexistant, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - inexistant, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({}); await clickMenuItem(user, 'Platform', /macOS/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ platform: ['windows', 'linux', 'android', 'ios'], }); await clickMenuItem(user, 'Platform', /Android/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ platform: ['windows', 'linux', 'ios'], }); await clickMenuItem(user, 'Platform', /Select only.*Android/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Android, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - Android, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ platform: ['android'], @@ -858,16 +888,16 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - // Filter only "Significant" + // Filter only "Real" (significant) const signifianceMenu = await screen.findByRole('button', { name: /Sig.*filter/, }); await user.click(signifianceMenu); expect(signifianceMenu).toMatchSnapshot(); - // significant item 0 and Not significant item 1 + // "Real" is item 0, "Noise" (not significant) is item 1 const significantOptions = await screen.findAllByRole('menuitemcheckbox', { - name: /Significant/, + name: /Real|Noise/, }); await user.click(significantOptions[1]); await user.keyboard('[Escape]'); @@ -881,30 +911,30 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); await screen.findByText('a11yr'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({}); const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); await clickMenuItem(user, 'Status', /No changes/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['improvement', 'regression'], }); await clickMenuItem(user, 'Status', /Improvement/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['regression'], @@ -912,39 +942,39 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio await clickMenuItem(user, 'Status', /Select all values/); await clickMenuItem(user, 'Status', /Regression/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['none', 'improvement'], }); await clickMenuItem(user, 'Status', /Regression/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({}); await clickMenuItem(user, 'Status', /Select only.*Regression/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['regression'], }); await clickMenuItem(user, 'Status', /Select only.*Improvement/); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['improvement'], @@ -959,14 +989,14 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio ); await screen.findByText('dhtml.html'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html spam opt e10s fission stylo webrender', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', ]); const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); expect(await summarizeTableFiltersFromCheckboxes(user)).toEqual({ 'Platform(2)': ['macOS', 'Android'], - 'Sig(2)': ['Significant', 'Not Significant-'], + 'Sig(2)': ['Real', 'Noise'], 'Status(3)': ['No changes', 'Improvement', 'Regression'], }); @@ -994,29 +1024,29 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio expect(deltaButton).toMatchSnapshot(); // // Sort descending - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr aria.html opt e10s fission stylo webrender', ' rev: spam', - ' - Linux 18.04, 1.849 %, Regression, 1.2, -, 44.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 1.3, -, 24.00 %', - ' - Windows 10, -, , 1.2, , 99.00 %', - ' - Windows 10, -2.401 %, , 1.2, , 49.00 %', + ' - Linux 18.04, +1.85%, Regression, 1.2, Noise, 44.00 %', + ' - macOS 10.15, +1.08%, Improvement, 1.3, Noise, 24.00 %', + ' - Windows 10, -, , 1.2, Real, 99.00 %', + ' - Windows 10, -2.40%, , 1.2, Real, 49.00 %', ' rev: tictactoe', - ' - Linux 18.04, 1.849 %, Regression, 2, -, 43.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 2.1, -, 23.00 %', - ' - Windows 10, -, , 2, , 98.00 %', - ' - Windows 10, -2.401 %, , 2, , 48.00 %', + ' - Linux 18.04, +1.85%, Regression, 2, Noise, 43.00 %', + ' - macOS 10.15, +1.08%, Improvement, 2.1, Noise, 23.00 %', + ' - Windows 10, -, , 2, Real, 98.00 %', + ' - Windows 10, -2.40%, , 2, Real, 48.00 %', 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: spam', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ' rev: tictactoe', - ' - Linux 18.04, 1.849 %, Regression, 0.8, -, 44.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.9, -, 24.00 %', - ' - Windows 10, -, , 0.8, , 99.00 %', - ' - Windows 10, -2.401 %, , 0.8, , 49.00 %', + ' - Linux 18.04, +1.85%, Regression, 0.8, Noise, 44.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.9, Noise, 24.00 %', + ' - Windows 10, -, , 0.8, Real, 99.00 %', + ' - Windows 10, -2.40%, , 0.8, Real, 49.00 %', ]); // It should have the "descending" SVG. expect(deltaButton).toMatchSnapshot(); @@ -1025,29 +1055,29 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio // sort ascending await user.click(deltaButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr aria.html opt e10s fission stylo webrender', ' rev: tictactoe', - ' - macOS 10.15, 1.078 %, Improvement, 2.1, -, 23.00 %', - ' - Linux 18.04, 1.849 %, Regression, 2, -, 43.00 %', - ' - Windows 10, -2.401 %, , 2, , 48.00 %', - ' - Windows 10, -, , 2, , 98.00 %', + ' - macOS 10.15, +1.08%, Improvement, 2.1, Noise, 23.00 %', + ' - Linux 18.04, +1.85%, Regression, 2, Noise, 43.00 %', + ' - Windows 10, -2.40%, , 2, Real, 48.00 %', + ' - Windows 10, -, , 2, Real, 98.00 %', ' rev: spam', - ' - macOS 10.15, 1.078 %, Improvement, 1.3, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 1.2, -, 44.00 %', - ' - Windows 10, -2.401 %, , 1.2, , 49.00 %', - ' - Windows 10, -, , 1.2, , 99.00 %', + ' - macOS 10.15, +1.08%, Improvement, 1.3, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 1.2, Noise, 44.00 %', + ' - Windows 10, -2.40%, , 1.2, Real, 49.00 %', + ' - Windows 10, -, , 1.2, Real, 99.00 %', 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: tictactoe', - ' - macOS 10.15, 1.078 %, Improvement, 0.9, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 0.8, -, 44.00 %', - ' - Windows 10, -2.401 %, , 0.8, , 49.00 %', - ' - Windows 10, -, , 0.8, , 99.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.9, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 0.8, Noise, 44.00 %', + ' - Windows 10, -2.40%, , 0.8, Real, 49.00 %', + ' - Windows 10, -, , 0.8, Real, 99.00 %', ' rev: spam', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', - ' - Windows 10, -, , -, , 100.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', ]); // It should have the "ascending" SVG. expect(deltaButton).toMatchSnapshot(); @@ -1059,29 +1089,29 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio name: /Sig.*sort/, }); await user.click(significanceButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr aria.html opt e10s fission stylo webrender', ' rev: tictactoe', - ' - macOS 10.15, 1.078 %, Improvement, 2.1, -, 23.00 %', - ' - Linux 18.04, 1.849 %, Regression, 2, -, 43.00 %', - ' - Windows 10, -, , 2, , 98.00 %', - ' - Windows 10, -2.401 %, , 2, , 48.00 %', + ' - macOS 10.15, +1.08%, Improvement, 2.1, Noise, 23.00 %', + ' - Linux 18.04, +1.85%, Regression, 2, Noise, 43.00 %', + ' - Windows 10, -, , 2, Real, 98.00 %', + ' - Windows 10, -2.40%, , 2, Real, 48.00 %', ' rev: spam', - ' - macOS 10.15, 1.078 %, Improvement, 1.3, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 1.2, -, 44.00 %', - ' - Windows 10, -, , 1.2, , 99.00 %', - ' - Windows 10, -2.401 %, , 1.2, , 49.00 %', + ' - macOS 10.15, +1.08%, Improvement, 1.3, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 1.2, Noise, 44.00 %', + ' - Windows 10, -, , 1.2, Real, 99.00 %', + ' - Windows 10, -2.40%, , 1.2, Real, 49.00 %', 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: tictactoe', - ' - macOS 10.15, 1.078 %, Improvement, 0.9, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 0.8, -, 44.00 %', - ' - Windows 10, -, , 0.8, , 99.00 %', - ' - Windows 10, -2.401 %, , 0.8, , 49.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.9, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 0.8, Noise, 44.00 %', + ' - Windows 10, -, , 0.8, Real, 99.00 %', + ' - Windows 10, -2.40%, , 0.8, Real, 49.00 %', ' rev: spam', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ]); // It should have the "descending" SVG. expect(significanceButton).toMatchSnapshot(); @@ -1090,29 +1120,29 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio // Sort by Significance ascending await user.click(significanceButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: spam', - ' - Windows 10, -2.401 %, , -, , 50.00 %', - ' - Windows 10, -, , -, , 100.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', ' rev: tictactoe', - ' - Windows 10, -2.401 %, , 0.8, , 49.00 %', - ' - Windows 10, -, , 0.8, , 99.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.9, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 0.8, -, 44.00 %', + ' - Windows 10, -2.40%, , 0.8, Real, 49.00 %', + ' - Windows 10, -, , 0.8, Real, 99.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.9, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 0.8, Noise, 44.00 %', 'a11yr aria.html opt e10s fission stylo webrender', ' rev: spam', - ' - Windows 10, -2.401 %, , 1.2, , 49.00 %', - ' - Windows 10, -, , 1.2, , 99.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 1.3, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 1.2, -, 44.00 %', + ' - Windows 10, -2.40%, , 1.2, Real, 49.00 %', + ' - Windows 10, -, , 1.2, Real, 99.00 %', + ' - macOS 10.15, +1.08%, Improvement, 1.3, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 1.2, Noise, 44.00 %', ' rev: tictactoe', - ' - Windows 10, -2.401 %, , 2, , 48.00 %', - ' - Windows 10, -, , 2, , 98.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 2.1, -, 23.00 %', - ' - Linux 18.04, 1.849 %, Regression, 2, -, 43.00 %', + ' - Windows 10, -2.40%, , 2, Real, 48.00 %', + ' - Windows 10, -, , 2, Real, 98.00 %', + ' - macOS 10.15, +1.08%, Improvement, 2.1, Noise, 23.00 %', + ' - Linux 18.04, +1.85%, Regression, 2, Noise, 43.00 %', ]); // It should have the "descending" SVG. expect(significanceButton).toMatchSnapshot(); @@ -1121,32 +1151,32 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio // Sort by Effect Size (%) descending const effectSizeButton = screen.getByRole('button', { - name: /CLES \(%\).*sort/, + name: /CLES.*sort/, }); await user.click(effectSizeButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: spam', - ' - Windows 10, -, , -, , 100.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - Windows 10, -2.401 %, , -, , 50.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', ' rev: tictactoe', - ' - Windows 10, -, , 0.8, , 99.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.9, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 0.8, -, 44.00 %', - ' - Windows 10, -2.401 %, , 0.8, , 49.00 %', + ' - Windows 10, -, , 0.8, Real, 99.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.9, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 0.8, Noise, 44.00 %', + ' - Windows 10, -2.40%, , 0.8, Real, 49.00 %', 'a11yr aria.html opt e10s fission stylo webrender', ' rev: spam', - ' - Windows 10, -, , 1.2, , 99.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 1.3, -, 24.00 %', - ' - Linux 18.04, 1.849 %, Regression, 1.2, -, 44.00 %', - ' - Windows 10, -2.401 %, , 1.2, , 49.00 %', + ' - Windows 10, -, , 1.2, Real, 99.00 %', + ' - macOS 10.15, +1.08%, Improvement, 1.3, Noise, 24.00 %', + ' - Linux 18.04, +1.85%, Regression, 1.2, Noise, 44.00 %', + ' - Windows 10, -2.40%, , 1.2, Real, 49.00 %', ' rev: tictactoe', - ' - Windows 10, -, , 2, , 98.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 2.1, -, 23.00 %', - ' - Linux 18.04, 1.849 %, Regression, 2, -, 43.00 %', - ' - Windows 10, -2.401 %, , 2, , 48.00 %', + ' - Windows 10, -, , 2, Real, 98.00 %', + ' - macOS 10.15, +1.08%, Improvement, 2.1, Noise, 23.00 %', + ' - Linux 18.04, +1.85%, Regression, 2, Noise, 43.00 %', + ' - Windows 10, -2.40%, , 2, Real, 48.00 %', ]); expect(effectSizeButton).toMatchSnapshot(); @@ -1155,46 +1185,46 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio // Sort by Effect Size (%) ascending await user.click(effectSizeButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ 'a11yr dhtml.html opt e10s fission stylo webrender', ' rev: spam', - ' - Windows 10, -2.401 %, , -, , 50.00 %', - ' - Linux 18.04, 1.849 %, Regression, -, -, 45.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.1, -, 25.00 %', - ' - Windows 10, -, , -, , 100.00 %', + ' - Windows 10, -2.40%, , -, Real, 50.00 %', + ' - Linux 18.04, +1.85%, Regression, -, Noise, 45.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.1, Noise, 25.00 %', + ' - Windows 10, -, , -, Real, 100.00 %', ' rev: tictactoe', - ' - Windows 10, -2.401 %, , 0.8, , 49.00 %', - ' - Linux 18.04, 1.849 %, Regression, 0.8, -, 44.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 0.9, -, 24.00 %', - ' - Windows 10, -, , 0.8, , 99.00 %', + ' - Windows 10, -2.40%, , 0.8, Real, 49.00 %', + ' - Linux 18.04, +1.85%, Regression, 0.8, Noise, 44.00 %', + ' - macOS 10.15, +1.08%, Improvement, 0.9, Noise, 24.00 %', + ' - Windows 10, -, , 0.8, Real, 99.00 %', 'a11yr aria.html opt e10s fission stylo webrender', ' rev: spam', - ' - Windows 10, -2.401 %, , 1.2, , 49.00 %', - ' - Linux 18.04, 1.849 %, Regression, 1.2, -, 44.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 1.3, -, 24.00 %', - ' - Windows 10, -, , 1.2, , 99.00 %', + ' - Windows 10, -2.40%, , 1.2, Real, 49.00 %', + ' - Linux 18.04, +1.85%, Regression, 1.2, Noise, 44.00 %', + ' - macOS 10.15, +1.08%, Improvement, 1.3, Noise, 24.00 %', + ' - Windows 10, -, , 1.2, Real, 99.00 %', ' rev: tictactoe', - ' - Windows 10, -2.401 %, , 2, , 48.00 %', - ' - Linux 18.04, 1.849 %, Regression, 2, -, 43.00 %', - ' - macOS 10.15, 1.078 %, Improvement, 2.1, -, 23.00 %', - ' - Windows 10, -, , 2, , 98.00 %', + ' - Windows 10, -2.40%, , 2, Real, 48.00 %', + ' - Linux 18.04, +1.85%, Regression, 2, Noise, 43.00 %', + ' - macOS 10.15, +1.08%, Improvement, 2.1, Noise, 23.00 %', + ' - Windows 10, -, , 2, Real, 98.00 %', ]); expect(effectSizeButton).toMatchSnapshot(); // It should be persisted in the URL expectParameterToHaveValue('sort', 'effects|asc'); - // Sort by MD(%) descending + // Sort by Δ Median descending const medianDiffButton = screen.getByRole('button', { - name: /MD \(%\).*sort/, + name: /Δ Median.*sort/, }); await user.click(medianDiffButton); - expect(summarizeVisibleRows('mann-whitney-u')).toMatchSnapshot(); + expect(summarizeVisibleRows('mann-whitney-u', true)).toMatchSnapshot(); expect(medianDiffButton).toMatchSnapshot(); expectParameterToHaveValue('sort', 'median-diff|desc'); // Sort by MD(%) ascending await user.click(medianDiffButton); - expect(summarizeVisibleRows('mann-whitney-u')).toMatchSnapshot(); + expect(summarizeVisibleRows('mann-whitney-u', true)).toMatchSnapshot(); expect(medianDiffButton).toMatchSnapshot(); expectParameterToHaveValue('sort', 'median-diff|asc'); }); @@ -1248,3 +1278,110 @@ describe('Results Table for MannWhitneyResultsItem for mann-whitney-u testVersio }); }); }); + +describe('Advanced-columns toggle for mann-whitney-u testVersion', () => { + it('shows Sig but hides CD/CLES in the simplified (default) view', async () => { + const { testCompareMannWhitneyData } = getTestData(); + setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); + await screen.findByText('a11yr'); + + const header = screen.getByTestId('table-header'); + // Sig and Magnitude are shown in the simplified view; CD/CLES are hidden. + expect(header.querySelector('.significance-header')).toBeTruthy(); + expect(header.querySelector('.magnitude-header')).toBeTruthy(); + expect(header.querySelector('.delta-header')).toBeFalsy(); + expect(header.querySelector('.effects-header')).toBeFalsy(); + }); + + it('reveals CD/CLES and hides Magnitude when advanced columns are enabled (Sig stays)', async () => { + enableAdvancedColumns(); + const { testCompareMannWhitneyData } = getTestData(); + setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); + await screen.findByText('a11yr'); + + const header = screen.getByTestId('table-header'); + expect(header.querySelector('.delta-header')).toBeTruthy(); + expect(header.querySelector('.effects-header')).toBeTruthy(); + expect(header.querySelector('.significance-header')).toBeTruthy(); + expect(header.querySelector('.magnitude-header')).toBeFalsy(); + }); + + it('can filter on the Magnitude column', async () => { + const { testCompareMannWhitneyData } = getTestData(); + setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); + await screen.findByText('a11yr'); + expect(summarizeTableFiltersFromUrl()).toEqual({}); + + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + await clickMenuItem(user, 'Magnitude', /Select only.*Negligible/); + expect(summarizeTableFiltersFromUrl()).toEqual({ + magnitude: ['negligible'], + }); + }); + + it('toggles Cliff’s Delta and CLES independently from the Advanced columns dropdown', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const { testCompareMannWhitneyData } = getTestData(); + setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); + await screen.findByText('a11yr'); + + const header = () => screen.getByTestId('table-header'); + // Simplified default: neither advanced column. + expect(header().querySelector('.delta-header')).toBeFalsy(); + expect(header().querySelector('.effects-header')).toBeFalsy(); + + // Open the dropdown and enable Cliff's Delta only. + await user.click(screen.getByRole('button', { name: /Advanced columns/ })); + await user.click(screen.getByRole('checkbox', { name: "Cliff's Delta" })); + expect(header().querySelector('.delta-header')).toBeTruthy(); + expect(header().querySelector('.effects-header')).toBeFalsy(); + + // Enable CLES too — both show. + await user.click(screen.getByRole('checkbox', { name: 'CLES' })); + expect(header().querySelector('.delta-header')).toBeTruthy(); + expect(header().querySelector('.effects-header')).toBeTruthy(); + + // Turn Cliff's Delta back off — only CLES remains. + await user.click(screen.getByRole('checkbox', { name: "Cliff's Delta" })); + expect(header().querySelector('.delta-header')).toBeFalsy(); + expect(header().querySelector('.effects-header')).toBeTruthy(); + }); + + it('shows the advanced columns named in the advanced_columns URL param', async () => { + const { testCompareMannWhitneyData } = getTestData(); + setupAndRender( + testCompareMannWhitneyData, + 'test_version=mann-whitney-u&advanced_columns=cliffs_delta', + ); + await screen.findByText('a11yr'); + + const header = screen.getByTestId('table-header'); + expect(header.querySelector('.delta-header')).toBeTruthy(); + expect(header.querySelector('.effects-header')).toBeFalsy(); + }); + + it('persists the advanced-column selection to the advanced_columns URL param', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const { testCompareMannWhitneyData } = getTestData(); + setupAndRender(testCompareMannWhitneyData, 'test_version=mann-whitney-u'); + await screen.findByText('a11yr'); + + const advancedParam = () => + new URLSearchParams(window.location.search).get('advanced_columns'); + expect(advancedParam()).toBeNull(); + + await user.click(screen.getByRole('button', { name: /Advanced columns/ })); + await user.click(screen.getByRole('checkbox', { name: "Cliff's Delta" })); + expect(advancedParam()).toBe('cliffs_delta'); + + await user.click(screen.getByRole('checkbox', { name: 'CLES' })); + expect(advancedParam()).toBe('cliffs_delta,cles'); + + // Turning a column off updates the param; turning the last one off removes it. + await user.click(screen.getByRole('checkbox', { name: "Cliff's Delta" })); + expect(advancedParam()).toBe('cles'); + + await user.click(screen.getByRole('checkbox', { name: 'CLES' })); + expect(advancedParam()).toBeNull(); + }); +}); diff --git a/src/__tests__/CompareResults/ResultsView.test.tsx b/src/__tests__/CompareResults/ResultsView.test.tsx index bb5f9e370..4f5200859 100644 --- a/src/__tests__/CompareResults/ResultsView.test.tsx +++ b/src/__tests__/CompareResults/ResultsView.test.tsx @@ -564,4 +564,24 @@ describe('Results View', () => { expect(screen.queryAllByTestId(/ExpandLessIcon/)).toHaveLength(0); }); }); + + it('shows the "How to read the results" panel by default and lets the user dismiss it', async () => { + renderWithRoute(<ResultsView title={Strings.metaData.pageTitle.results} />); + await screen.findByText('a11yr'); + + // Shown by default. + const panel = screen.getByTestId('how-to-read-results'); + expect(panel).toBeInTheDocument(); + + // Dismiss via the panel's close button. + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + await user.click(screen.getByRole('button', { name: /close/i })); + expect(screen.queryByTestId('how-to-read-results')).not.toBeInTheDocument(); + + // The "How to read the results" checkbox brings it back. + await user.click( + screen.getByRole('checkbox', { name: /How to read the results/i }), + ); + expect(screen.getByTestId('how-to-read-results')).toBeInTheDocument(); + }); }); diff --git a/src/__tests__/CompareResults/RevisionRow.test.tsx b/src/__tests__/CompareResults/RevisionRow.test.tsx index ae86e4950..6f542eab5 100644 --- a/src/__tests__/CompareResults/RevisionRow.test.tsx +++ b/src/__tests__/CompareResults/RevisionRow.test.tsx @@ -11,7 +11,11 @@ import { useSubtestRegressionCount } from '../../hooks/useSubtestRegressionCount import { CompareResultsItem, MannWhitneyResultsItem } from '../../types/state'; import { Platform } from '../../types/types'; import getTestData from '../utils/fixtures'; -import { screen, renderWithRouter } from '../utils/test-utils'; +import { + screen, + renderWithRouter, + enableAdvancedColumns, +} from '../utils/test-utils'; jest.mock('../../hooks/useSubtestRegressionCount'); const mockUseSubtestRegressionCount = useSubtestRegressionCount as jest.Mock; @@ -427,6 +431,7 @@ describe('Expanded row', () => { }); it('should display mean for base or new in row headers for mann-whitney-u testVersion', async () => { + enableAdvancedColumns(); const { testCompareMannWhitneyData: rowData } = getTestData(); renderWithRoute( <RevisionRow @@ -541,7 +546,7 @@ describe('Expanded row', () => { }; } - it('shows dash when neither distribution is normal', async () => { + it('shows value with warning icon when neither distribution is normal', async () => { const result = makeResult(tooFewRuns, tooFewRuns); expect(isDistributionNormal(result)).toBe(false); renderWithRoute( @@ -555,7 +560,10 @@ describe('Expanded row', () => { />, ); const roles = await screen.findAllByRole('cell'); - expect(roles[4]).toHaveTextContent('-'); + // The median difference is rank-based, so it's shown even for non-normal + // data; only a warning icon flags the shape. + expect(roles[4]).toHaveTextContent('%'); + expect(roles[4].querySelector('svg[role="img"]')).toBeTruthy(); }); it('shows value with warning icon when only one distribution is normal', async () => { @@ -572,7 +580,7 @@ describe('Expanded row', () => { />, ); const roles = await screen.findAllByRole('cell'); - expect(roles[4]).not.toHaveTextContent('-'); + expect(roles[4]).toHaveTextContent('%'); expect(roles[4].querySelector('svg[role="img"]')).toBeTruthy(); }); @@ -590,7 +598,7 @@ describe('Expanded row', () => { />, ); const roles = await screen.findAllByRole('cell'); - expect(roles[4]).not.toHaveTextContent('-'); + expect(roles[4]).toHaveTextContent('%'); expect(roles[4].querySelector('svg[role="img"]')).toBeFalsy(); }); }); diff --git a/src/__tests__/CompareResults/SubtestsResultsView.test.tsx b/src/__tests__/CompareResults/SubtestsResultsView.test.tsx index bff3fb298..07711829e 100644 --- a/src/__tests__/CompareResults/SubtestsResultsView.test.tsx +++ b/src/__tests__/CompareResults/SubtestsResultsView.test.tsx @@ -10,7 +10,11 @@ import type { CombinedResultsItemType } from '../../types/state'; import { TestVersion } from '../../types/types'; import { getLocationOrigin } from '../../utils/location'; import getTestData from '../utils/fixtures'; -import { renderWithRouter, screen } from '../utils/test-utils'; +import { + renderWithRouter, + screen, + enableAdvancedColumns, +} from '../utils/test-utils'; jest.mock('../../utils/location'); const mockedGetLocationOrigin = getLocationOrigin as jest.Mock; @@ -48,7 +52,7 @@ const setup = ({ // This handy function parses the results page and returns an array of visible // rows. It makes it easy to assert visible rows when filtering them in a // user-friendly way without using snapshots. -function summarizeVisibleRows(testVersion?: TestVersion) { +function summarizeVisibleRows(testVersion?: TestVersion, advanced = false) { const rows = screen.getAllByRole('row'); const result = []; for (const row of rows) { @@ -59,10 +63,19 @@ function summarizeVisibleRows(testVersion?: TestVersion) { } const rowClasses = testVersion === 'mann-whitney-u' - ? ['.median-diff', '.delta', '.significance', '.effects'] + ? advanced + ? ['.median-diff', '.delta', '.significance', '.effects'] + : ['.median-diff', '.status-hint', '.magnitude', '.significance'] : ['.delta', '.confidence']; const rowString = rowClasses - .map((selector) => row.querySelector(selector)?.textContent.trim()) + .map((selector) => { + const cell = row.querySelector(selector); + if (!cell) return undefined; + // Strip icon <title> text (e.g. the median-diff normality warning). + const clone = cell.cloneNode(true) as HTMLElement; + clone.querySelectorAll('svg').forEach((svg) => svg.remove()); + return clone.textContent?.trim(); + }) .join(', '); result.push(`${subtest}: ${rowString}`); } @@ -497,6 +510,11 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( }); describe('table sorting', () => { + // These sort by CD and Significance, which are advanced-only columns. + beforeEach(() => { + enableAdvancedColumns(); + }); + async function setupForSorting({ extraParameters, }: Partial<{ @@ -524,12 +542,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( it('can sort the table and persist the information to the URL of mann-whitney-u values', async () => { await setupForSorting(); // Initial view (alphabetical ordered, even if "sort by subtests" isn't specified - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', ]); // Sort by Delta @@ -539,12 +557,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( expect(window.location.search).not.toContain('sort='); // Sort descending await user.click(deltaButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', ]); // It should have the "descending" SVG. @@ -554,12 +572,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( // Sort ascending await user.click(deltaButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', ]); // It should have the "ascending" SVG. expect(deltaButton).toMatchSnapshot(); @@ -571,12 +589,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( name: /Sig.*sort/, }); await user.click(significanceButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', ]); // It should have the "no sort" SVG. expect(deltaButton).toMatchSnapshot(); @@ -587,26 +605,26 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( // Sort by Significance ascending await user.click(significanceButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', ]); expectParameterToHaveValue('sort', 'significance|asc'); // Sort by Effect Size descending const effectButton = screen.getByRole('button', { - name: /CLES \(%\).*sort/, + name: /CLES.*sort/, }); await user.click(effectButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', ]); // It should have the "descending" SVG. @@ -616,12 +634,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( // Sort by Effect Size ascending await user.click(effectButton); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', ]); expectParameterToHaveValue('sort', 'effects|asc'); }); @@ -629,12 +647,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( it('initializes the sort from the URL at load time for an ascending sort', async () => { await setupForSorting({ extraParameters: 'sort=delta|asc' }); await screen.findByText('dhtml.html'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'regression.html: 1.135 %, 0.12, , 25.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'regression.html: +1.14%, 0.12, Real, 25.00%', ]); // It should have the "ascending" SVG. expect(screen.getByRole('button', { name: /CD/ })).toMatchSnapshot(); @@ -643,12 +661,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( it('initializes the sort from the URL at load time for an implicit descending sort', async () => { await setupForSorting({ extraParameters: 'sort=delta' }); await screen.findByText('dhtml.html'); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', ]); // It should have the "descending" SVG. expect(screen.getByRole('button', { name: /CD/ })).toMatchSnapshot(); @@ -656,12 +674,12 @@ describe('SubtestsResultsView Component Tests for mann-whitney-u testVersion', ( it('initializes the sort from the URL at load time for a descending sort', async () => { await setupForSorting({ extraParameters: 'sort=delta|desc' }); - expect(summarizeVisibleRows('mann-whitney-u')).toEqual([ - 'regression.html: 1.135 %, 0.12, , 25.00%', - 'improvement.html: 0.963 %, -0.05, , 50.00%', - 'browser.html: 0.963 %, -0.04, -, 15.00%', - 'dhtml.html: 1.135 %, 0.02, , 60.00%', - 'tablemutation.html: 0.98 %, 0.01, -, 45.00%', + expect(summarizeVisibleRows('mann-whitney-u', true)).toEqual([ + 'regression.html: +1.14%, 0.12, Real, 25.00%', + 'improvement.html: +1.14%, -0.05, Real, 50.00%', + 'browser.html: +1.14%, -0.04, Noise, 15.00%', + 'dhtml.html: +1.14%, 0.02, Real, 60.00%', + 'tablemutation.html: +0.98%, 0.01, -, 45.00%', ]); // It should have the "descending" SVG. expect(screen.getByRole('button', { name: /CD/ })).toMatchSnapshot(); diff --git a/src/__tests__/CompareResults/SubtestsRevisionRow.test.tsx b/src/__tests__/CompareResults/SubtestsRevisionRow.test.tsx index d8941e030..1b328e56a 100644 --- a/src/__tests__/CompareResults/SubtestsRevisionRow.test.tsx +++ b/src/__tests__/CompareResults/SubtestsRevisionRow.test.tsx @@ -7,7 +7,11 @@ import { loader } from '../../components/CompareResults/loader'; import SubtestsRevisionRow from '../../components/CompareResults/SubtestsResults/SubtestsRevisionRow'; import { MannWhitneyResultsItem } from '../../types/state'; import getTestData from '../utils/fixtures'; -import { screen, renderWithRouter } from '../utils/test-utils'; +import { + screen, + renderWithRouter, + enableAdvancedColumns, +} from '../utils/test-utils'; function renderWithRoute(component: ReactElement) { fetchMock @@ -178,6 +182,7 @@ describe('SubtestsRevisionRow Component', () => { }); it('should display cliffs delta, significance, and effects size in subtests for mann-whitney-u testVersion', async () => { + enableAdvancedColumns(); const { subtestsMannWhitneyResult } = getTestData(); const mockGridTemplateColumns = '1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr'; renderWithRoute( @@ -194,7 +199,7 @@ describe('SubtestsRevisionRow Component', () => { expect(effects).toHaveTextContent('60.00%'); const significanceCell = roles[8]; - expect(significanceCell?.querySelector('svg')).not.toBeNull(); + expect(significanceCell).toHaveTextContent('Real'); const cliffs_delta = roles[6]?.childNodes[1]; expect(cliffs_delta).toHaveTextContent('0.02'); @@ -213,7 +218,7 @@ describe('SubtestsRevisionRow Component', () => { ); const roles = await screen.findAllByRole('cell'); - const status = roles[5]?.childNodes[0]; + const status = roles[5]?.querySelector('.status-hint'); expect(status).toHaveTextContent('Regression'); expect(status).toHaveClass('status-hint-regression'); }); @@ -231,7 +236,7 @@ describe('SubtestsRevisionRow Component', () => { ); const roles1 = await screen.findAllByRole('cell'); - const status1 = roles1[5]?.childNodes[0]; + const status1 = roles1[5]?.querySelector('.status-hint'); expect(status1).toHaveTextContent('Improvement'); expect(status1).toHaveClass('status-hint-improvement'); }); @@ -253,7 +258,7 @@ describe('SubtestsRevisionRow Component', () => { }; } - it('shows dash when neither distribution is normal', async () => { + it('shows value with warning icon when neither distribution is normal', async () => { renderWithRoute( <SubtestsRevisionRow result={makeResult(tooFewRuns, tooFewRuns)} @@ -263,7 +268,10 @@ describe('SubtestsRevisionRow Component', () => { />, ); const roles = await screen.findAllByRole('cell'); - expect(roles[4]).toHaveTextContent('-'); + // The median difference is rank-based, so it's shown even for non-normal + // data; only a warning icon flags the shape. + expect(roles[4]).toHaveTextContent('%'); + expect(roles[4].querySelector('svg[role="img"]')).toBeTruthy(); }); it('shows value with warning icon when only one distribution is normal', async () => { @@ -276,7 +284,7 @@ describe('SubtestsRevisionRow Component', () => { />, ); const roles = await screen.findAllByRole('cell'); - expect(roles[4]).not.toHaveTextContent('-'); + expect(roles[4]).toHaveTextContent('%'); expect(roles[4].querySelector('svg[role="img"]')).toBeTruthy(); }); @@ -290,7 +298,7 @@ describe('SubtestsRevisionRow Component', () => { />, ); const roles = await screen.findAllByRole('cell'); - expect(roles[4]).not.toHaveTextContent('-'); + expect(roles[4]).toHaveTextContent('%'); expect(roles[4].querySelector('svg[role="img"]')).toBeFalsy(); }); }); diff --git a/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap index dbd059e14..5a75ed17c 100644 --- a/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap @@ -10,343 +10,412 @@ exports[`Results View The table should match snapshot and other elements should class="MuiBox-root css-1rw0zd7" > <div - class="MuiGrid-root MuiGrid-container MuiGrid-direction-xs-row MuiGrid-spacing-xs-2 f1wpas00 css-13620ud-MuiGrid-root" + class="MuiGrid-root MuiGrid-container MuiGrid-direction-xs-row MuiGrid-spacing-xs-2 f1wpas00 results-controls css-13620ud-MuiGrid-root" > <div - class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-md-3 MuiGrid-grid-xs-12 css-11ufmbb-MuiGrid-root" + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-12 MuiGrid-grid-md-grow css-1l62nh8-MuiGrid-root" > - <form - aria-label="Search by title, platform, revision or options" - class="MuiBox-root css-1wxqtyp" + <div + class="MuiGrid-root MuiGrid-container MuiGrid-direction-xs-row MuiGrid-spacing-xs-2 results-controls-inputs css-13620ud-MuiGrid-root" > <div - class="MuiFormControl-root MuiTextField-root css-q16m8p-MuiFormControl-root-MuiTextField-root" - data-mui-internal-clone-element="true" + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-md-3 MuiGrid-grid-xs-12 css-11ufmbb-MuiGrid-root" + > + <form + aria-label="Search by title, platform, revision or options" + class="MuiBox-root css-1wxqtyp" + > + <div + class="MuiFormControl-root MuiTextField-root css-q16m8p-MuiFormControl-root-MuiTextField-root" + data-mui-internal-clone-element="true" + > + <div + class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall MuiInputBase-adornedStart MuiInputBase-adornedEnd css-1cqjfkd-MuiInputBase-root-MuiOutlinedInput-root" + > + <div + class="MuiInputAdornment-root MuiInputAdornment-positionStart MuiInputAdornment-outlined MuiInputAdornment-sizeSmall css-1nowbqt-MuiInputAdornment-root" + > + <span + aria-hidden="true" + class="notranslate" + > + ​ + </span> + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" + data-testid="SearchIcon" + focusable="false" + viewBox="0 0 24 24" + > + <path + d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14" + /> + </svg> + </div> + <input + aria-description="Tip: You can search with multiple words (all must match). Use -word to exclude results containing that word." + aria-invalid="false" + aria-label="Search by title, platform, revision or options" + class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall MuiInputBase-inputAdornedStart MuiInputBase-inputAdornedEnd css-3v3un6-MuiInputBase-input-MuiOutlinedInput-input" + id="_r_b_" + placeholder="Filter results" + type="search" + value="" + /> + <div + class="MuiInputAdornment-root MuiInputAdornment-positionEnd MuiInputAdornment-outlined MuiInputAdornment-sizeSmall css-elo8k2-MuiInputAdornment-root" + > + <button + aria-label="Clear the search input" + class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd MuiIconButton-sizeSmall css-13mnts1-MuiButtonBase-root-MuiIconButton-root" + tabindex="0" + type="button" + > + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" + data-testid="CloseIcon" + focusable="false" + viewBox="0 0 24 24" + > + <path + d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" + /> + </svg> + </button> + </div> + <fieldset + aria-hidden="true" + class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" + > + <legend + class="css-1nf2c5d-MuiNotchedOutlined-root" + > + <span + aria-hidden="true" + class="notranslate" + > + ​ + </span> + </legend> + </fieldset> + </div> + </div> + </form> + </div> + <div + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-md-2 MuiGrid-grid-xs-6 css-4yvdwt-MuiGrid-root" > <div - class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall MuiInputBase-adornedStart MuiInputBase-adornedEnd css-1cqjfkd-MuiInputBase-root-MuiOutlinedInput-root" + class="MuiFormControl-root css-7549ua-MuiFormControl-root" > <div - class="MuiInputAdornment-root MuiInputAdornment-positionStart MuiInputAdornment-outlined MuiInputAdornment-sizeSmall css-1nowbqt-MuiInputAdornment-root" + class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall framework-dropdown-select MuiSelect-root css-fcr66i-MuiInputBase-root-MuiOutlinedInput-root-MuiSelect-root" + data-testid="framework-select" > - <span - aria-hidden="true" - class="notranslate" + <div + aria-expanded="false" + aria-haspopup="listbox" + aria-label="Framework" + aria-labelledby="mui-component-select-framework" + class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1hw1663-MuiNativeSelect-root-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input" + id="mui-component-select-framework" + role="combobox" + tabindex="0" > - ​ - </span> + talos + </div> + <input + aria-hidden="true" + aria-invalid="false" + class="MuiSelect-nativeInput css-j0riat-MuiSelect-nativeInput" + name="framework" + tabindex="-1" + value="1" + /> <svg aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" - data-testid="SearchIcon" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-h42w4p-MuiSvgIcon-root-MuiNativeSelect-root-MuiSelect-icon" + data-testid="ArrowDropDownIcon" focusable="false" viewBox="0 0 24 24" > <path - d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14" + d="M7 10l5 5 5-5z" /> </svg> - </div> - <input - aria-description="Tip: You can search with multiple words (all must match). Use -word to exclude results containing that word." - aria-invalid="false" - aria-label="Search by title, platform, revision or options" - class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall MuiInputBase-inputAdornedStart MuiInputBase-inputAdornedEnd css-3v3un6-MuiInputBase-input-MuiOutlinedInput-input" - id="_r_a_" - placeholder="Filter results" - type="search" - value="" - /> - <div - class="MuiInputAdornment-root MuiInputAdornment-positionEnd MuiInputAdornment-outlined MuiInputAdornment-sizeSmall css-elo8k2-MuiInputAdornment-root" - > - <button - aria-label="Clear the search input" - class="MuiButtonBase-root MuiIconButton-root MuiIconButton-edgeEnd MuiIconButton-sizeSmall css-13mnts1-MuiButtonBase-root-MuiIconButton-root" - tabindex="0" - type="button" + <fieldset + aria-hidden="true" + class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" > - <svg - aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" - data-testid="CloseIcon" - focusable="false" - viewBox="0 0 24 24" + <legend + class="css-1nf2c5d-MuiNotchedOutlined-root" > - <path - d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" - /> - </svg> - </button> + <span + aria-hidden="true" + class="notranslate" + > + ​ + </span> + </legend> + </fieldset> </div> - <fieldset - aria-hidden="true" - class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" - > - <legend - class="css-1nf2c5d-MuiNotchedOutlined-root" - > - <span - aria-hidden="true" - class="notranslate" - > - ​ - </span> - </legend> - </fieldset> </div> </div> - </form> - </div> - <div - class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-md-2 MuiGrid-grid-xs-6 css-4yvdwt-MuiGrid-root" - > - <div - class="MuiFormControl-root css-7549ua-MuiFormControl-root" - > <div - class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall framework-dropdown-select MuiSelect-root css-fcr66i-MuiInputBase-root-MuiOutlinedInput-root-MuiSelect-root" - data-testid="framework-select" + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-md-2 MuiGrid-grid-xs-6 css-4yvdwt-MuiGrid-root" > <div - aria-expanded="false" - aria-haspopup="listbox" - aria-label="Framework" - aria-labelledby="mui-component-select-framework" - class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1hw1663-MuiNativeSelect-root-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input" - id="mui-component-select-framework" - role="combobox" - tabindex="0" - > - talos - </div> - <input - aria-hidden="true" - aria-invalid="false" - class="MuiSelect-nativeInput css-j0riat-MuiSelect-nativeInput" - name="framework" - tabindex="-1" - value="1" - /> - <svg - aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-h42w4p-MuiSvgIcon-root-MuiNativeSelect-root-MuiSelect-icon" - data-testid="ArrowDropDownIcon" - focusable="false" - viewBox="0 0 24 24" - > - <path - d="M7 10l5 5 5-5z" - /> - </svg> - <fieldset - aria-hidden="true" - class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" + class="MuiFormControl-root css-7549ua-MuiFormControl-root" > - <legend - class="css-1nf2c5d-MuiNotchedOutlined-root" + <div + class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall test-version-select MuiSelect-root css-fcr66i-MuiInputBase-root-MuiOutlinedInput-root-MuiSelect-root" + data-testid="test-version-select" > - <span + <div + aria-expanded="false" + aria-haspopup="listbox" + aria-label="Stats Test Version" + aria-labelledby="test-version-type mui-component-select-test_version" + class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1hw1663-MuiNativeSelect-root-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input" + id="mui-component-select-test_version" + role="combobox" + tabindex="0" + > + Mann-Whitney-U + </div> + <input + aria-hidden="true" + aria-invalid="false" + class="MuiSelect-nativeInput css-j0riat-MuiSelect-nativeInput" + name="test_version" + tabindex="-1" + value="mann-whitney-u" + /> + <svg aria-hidden="true" - class="notranslate" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-h42w4p-MuiSvgIcon-root-MuiNativeSelect-root-MuiSelect-icon" + data-testid="ArrowDropDownIcon" + focusable="false" + viewBox="0 0 24 24" > - ​ - </span> - </legend> - </fieldset> - </div> - </div> - </div> - <div - class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-md-2 MuiGrid-grid-xs-6 css-4yvdwt-MuiGrid-root" - > - <div - class="MuiFormControl-root css-7549ua-MuiFormControl-root" - > - <div - class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-formControl MuiInputBase-sizeSmall test-version-select MuiSelect-root css-fcr66i-MuiInputBase-root-MuiOutlinedInput-root-MuiSelect-root" - data-testid="test-version-select" - > - <div - aria-expanded="false" - aria-haspopup="listbox" - aria-label="Stats Test Version" - aria-labelledby="test-version-type mui-component-select-test_version" - class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1hw1663-MuiNativeSelect-root-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input" - id="mui-component-select-test_version" - role="combobox" - tabindex="0" - > - Mann-Whitney-U - </div> - <input - aria-hidden="true" - aria-invalid="false" - class="MuiSelect-nativeInput css-j0riat-MuiSelect-nativeInput" - name="test_version" - tabindex="-1" - value="mann-whitney-u" - /> - <svg - aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-h42w4p-MuiSvgIcon-root-MuiNativeSelect-root-MuiSelect-icon" - data-testid="ArrowDropDownIcon" - focusable="false" - viewBox="0 0 24 24" - > - <path - d="M7 10l5 5 5-5z" - /> - </svg> - <fieldset - aria-hidden="true" - class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" - > - <legend - class="css-1nf2c5d-MuiNotchedOutlined-root" - > - <span + <path + d="M7 10l5 5 5-5z" + /> + </svg> + <fieldset aria-hidden="true" - class="notranslate" + class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" > - ​ - </span> - </legend> - </fieldset> + <legend + class="css-1nf2c5d-MuiNotchedOutlined-root" + > + <span + aria-hidden="true" + class="notranslate" + > + ​ + </span> + </legend> + </fieldset> + </div> + </div> </div> - </div> - </div> - <div - class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-grow css-t3of1j-MuiGrid-root" - > - <div - class="MuiBox-root css-0" - data-testid="revision-select" - > <div - class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-sizeSmall f96or0t MuiSelect-root css-fcr66i-MuiInputBase-root-MuiOutlinedInput-root-MuiSelect-root" + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-grow css-vvfr7q-MuiGrid-root" > <div - aria-expanded="false" - aria-haspopup="listbox" - aria-label="Filter by revision" - class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1hw1663-MuiNativeSelect-root-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input" - role="combobox" - tabindex="0" + class="MuiBox-root css-0" + data-testid="revision-select" > <div - class="fbc330 MuiBox-root css-0" + class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-colorPrimary MuiInputBase-sizeSmall f96or0t MuiSelect-root css-fcr66i-MuiInputBase-root-MuiOutlinedInput-root-MuiSelect-root" > + <div + aria-expanded="false" + aria-haspopup="listbox" + aria-label="Filter by revision" + class="MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputSizeSmall css-1hw1663-MuiNativeSelect-root-MuiSelect-select-MuiInputBase-input-MuiOutlinedInput-input" + role="combobox" + tabindex="0" + > + <div + class="fbc330 MuiBox-root css-0" + > + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" + focusable="false" + viewBox="0 0 24 24" + > + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" + data-testid="SortIcon" + focusable="false" + viewBox="0 0 24 24" + > + <path + d="M3 18h6v-2H3zM3 6v2h18V6zm0 7h12v-2H3z" + /> + </svg> + </svg> + All revisions + </div> + </div> + <input + aria-hidden="true" + aria-invalid="false" + class="MuiSelect-nativeInput css-j0riat-MuiSelect-nativeInput" + tabindex="-1" + value="all-revisions" + /> <svg aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-h42w4p-MuiSvgIcon-root-MuiNativeSelect-root-MuiSelect-icon" + data-testid="ArrowDropDownIcon" focusable="false" viewBox="0 0 24 24" > - <svg - aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" - data-testid="SortIcon" - focusable="false" - viewBox="0 0 24 24" - > - <path - d="M3 18h6v-2H3zM3 6v2h18V6zm0 7h12v-2H3z" - /> - </svg> + <path + d="M7 10l5 5 5-5z" + /> </svg> - All revisions + <fieldset + aria-hidden="true" + class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" + > + <legend + class="css-1nf2c5d-MuiNotchedOutlined-root" + > + <span + aria-hidden="true" + class="notranslate" + > + ​ + </span> + </legend> + </fieldset> </div> </div> - <input - aria-hidden="true" - aria-invalid="false" - class="MuiSelect-nativeInput css-j0riat-MuiSelect-nativeInput" - tabindex="-1" - value="all-revisions" - /> - <svg - aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium MuiSelect-icon MuiSelect-iconOutlined css-h42w4p-MuiSvgIcon-root-MuiNativeSelect-root-MuiSelect-icon" - data-testid="ArrowDropDownIcon" - focusable="false" - viewBox="0 0 24 24" - > - <path - d="M7 10l5 5 5-5z" - /> - </svg> - <fieldset - aria-hidden="true" - class="MuiOutlinedInput-notchedOutline css-18p5xg2-MuiNotchedOutlined-root-MuiOutlinedInput-notchedOutline" + </div> + <div + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-grow css-wautn6-MuiGrid-root" + > + <div + class="f6hg2jo" > - <legend - class="css-1nf2c5d-MuiNotchedOutlined-root" + <button + class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedSecondary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorSecondary MuiButton-disableElevation css-14hwkrj-MuiButtonBase-root-MuiButton-root" + tabindex="0" + type="button" > - <span - aria-hidden="true" - class="notranslate" - > - ​ - </span> - </legend> - </fieldset> + Download JSON + </button> + </div> </div> </div> </div> <div - class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-grow css-t3of1j-MuiGrid-root" + class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-12 MuiGrid-grid-md-auto css-1cmvap5-MuiGrid-root" > <div - class="f6hg2jo" + class="results-controls-selections MuiBox-root css-1qjdzf5" > <button - class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedSecondary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorSecondary MuiButton-disableElevation css-14hwkrj-MuiButtonBase-root-MuiButton-root" + aria-haspopup="true" + aria-label="Advanced columns" + class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedInherit MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButton-colorInherit MuiButton-disableElevation advanced-columns-button css-h55mtz-MuiButtonBase-root-MuiButton-root" + data-mui-internal-clone-element="true" tabindex="0" type="button" > - Download JSON + Advanced columns + <span + class="MuiButton-icon MuiButton-endIcon MuiButton-iconSizeSmall css-dp0v3c-MuiButton-endIcon" + > + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-120he73-MuiSvgIcon-root" + data-testid="KeyboardArrowDownIcon" + focusable="false" + viewBox="0 0 24 24" + > + <path + d="M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6z" + /> + </svg> + </span> </button> - </div> - </div> - <div - class="MuiGrid-root MuiGrid-direction-xs-row MuiGrid-grid-xs-auto css-zh2j38-MuiGrid-root" - > - <label - aria-label="Expand all rows" - class="MuiFormControlLabel-root MuiFormControlLabel-labelPlacementEnd css-1bsjtco-MuiFormControlLabel-root" - data-mui-internal-clone-element="true" - > - <span - class="MuiButtonBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall PrivateSwitchBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall css-1nff2up-MuiButtonBase-root-MuiSwitchBase-root-MuiCheckbox-root" + <label + aria-label="Show the "How to read the results" guide above the table" + class="MuiFormControlLabel-root MuiFormControlLabel-labelPlacementEnd css-1bsjtco-MuiFormControlLabel-root" + data-mui-internal-clone-element="true" > - <input - class="PrivateSwitchBase-input css-12xagqm-MuiSwitchBase-root" - data-indeterminate="false" - type="checkbox" - /> - <svg - aria-hidden="true" - class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-54rgmy-MuiSvgIcon-root" - data-testid="CheckBoxOutlineBlankIcon" - focusable="false" - viewBox="0 0 24 24" + <span + class="MuiButtonBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall PrivateSwitchBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall Mui-checked MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall css-1nff2up-MuiButtonBase-root-MuiSwitchBase-root-MuiCheckbox-root" > - <path - d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" + <input + checked="" + class="PrivateSwitchBase-input css-12xagqm-MuiSwitchBase-root" + data-indeterminate="false" + type="checkbox" /> - </svg> - </span> - <span - class="MuiTypography-root MuiTypography-body1 MuiFormControlLabel-label css-n290n7-MuiTypography-root" + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-54rgmy-MuiSvgIcon-root" + data-testid="CheckBoxIcon" + focusable="false" + viewBox="0 0 24 24" + > + <path + d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" + /> + </svg> + </span> + <span + class="MuiTypography-root MuiTypography-body1 MuiFormControlLabel-label css-n290n7-MuiTypography-root" + > + How to read the results + </span> + </label> + <label + aria-label="Expand all rows" + class="MuiFormControlLabel-root MuiFormControlLabel-labelPlacementEnd css-1bsjtco-MuiFormControlLabel-root" + data-mui-internal-clone-element="true" > - Expand all - </span> - </label> + <span + class="MuiButtonBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall PrivateSwitchBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall css-1nff2up-MuiButtonBase-root-MuiSwitchBase-root-MuiCheckbox-root" + > + <input + class="PrivateSwitchBase-input css-12xagqm-MuiSwitchBase-root" + data-indeterminate="false" + type="checkbox" + /> + <svg + aria-hidden="true" + class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-54rgmy-MuiSvgIcon-root" + data-testid="CheckBoxOutlineBlankIcon" + focusable="false" + viewBox="0 0 24 24" + > + <path + d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" + /> + </svg> + </span> + <span + class="MuiTypography-root MuiTypography-body1 MuiFormControlLabel-label css-n290n7-MuiTypography-root" + > + Expand all + </span> + </label> + </div> </div> </div> <div - class="fdtnfac f1l733lh" + class="f1bg1cba f1l733lh table-header" data-testid="table-header" role="row" > @@ -357,7 +426,7 @@ exports[`Results View The table should match snapshot and other elements should <button aria-haspopup="true" aria-label="Platform (Click to filter values)" - class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedTableHeaderButton MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButton-colorTableHeaderButton MuiButton-disableElevation css-l81dbu-MuiButtonBase-root-MuiButton-root" + class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedTableHeaderButton MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButton-colorTableHeaderButton MuiButton-disableElevation css-kne8o5-MuiButtonBase-root-MuiButton-root" data-mui-internal-clone-element="true" tabindex="0" type="button" @@ -417,13 +486,13 @@ exports[`Results View The table should match snapshot and other elements should role="columnheader" > <span - aria-label="Median Diff %: The percentage change in median from Base to New: ((New − Base) / Base) × 100." + aria-label="Shows how much the middle result changed from Base to New. We line up all the runs from smallest to biggest and pick the middle one for each side, then show the change as a percent. A positive sign means New is higher; a negative sign means New is lower." class="MuiBox-root css-1tkq7a4" data-mui-internal-clone-element="true" > <button - aria-label="MD (%) (Click to sort by this column)" - class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedTableHeaderButton MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButton-colorTableHeaderButton MuiButton-disableElevation css-7upxw3-MuiButtonBase-root-MuiButton-root" + aria-label="Δ Median (Click to sort by this column)" + class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedTableHeaderButton MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButton-colorTableHeaderButton MuiButton-disableElevation css-1w0uvhi-MuiButtonBase-root-MuiButton-root" tabindex="0" type="button" > @@ -438,10 +507,10 @@ exports[`Results View The table should match snapshot and other elements should d="M16 17.01V10h-2v7.01h-3L15 21l4-3.99zM9 3 5 6.99h3V14h2V6.99h3z" /> <title> - Not sorted by MD (%) + Not sorted by Δ Median - MD (%) + Δ Median @@ -452,7 +521,7 @@ exports[`Results View The table should match snapshot and other elements should - - -
- - +
- 0 % + +1.08%
- - -
-
- @@ -1116,7 +1169,7 @@ exports[`Results View The table should match snapshot and other elements should data-testid="expand-revision-button" >
- 0 % + -2.40%
- - -
-
- @@ -1315,7 +1362,7 @@ exports[`Results View The table should match snapshot and other elements should data-testid="expand-revision-button" >
- 0 % + -2.40%
- - -
-
- @@ -1514,7 +1555,7 @@ exports[`Results View The table should match snapshot and other elements should data-testid="expand-revision-button" >
+
-
+ +
+
+
+ + +
+ +
+ +
+ +
+
+ +
+
- + talos +
+ -
- -
- + + +
-
- -
-
-
- - -
+ +
-
-
-
-
-
- - - - +
+
-
-
-
-
- - - + Download JSON + +
-
-
-
- + + + + + + + Expand all + + +
@@ -1330,7 +1535,7 @@ exports[`Results Table Should match snapshot 1`] = `
@@ -1425,7 +1630,7 @@ exports[`Results Table Should match snapshot 1`] = ` - - -
- - +
- 0 % + +1.08%
- - -
-
- @@ -2089,7 +2278,7 @@ exports[`Results Table Should match snapshot 1`] = ` data-testid="expand-revision-button" >
- 0 % + -2.40%
- - -
-
- @@ -2288,7 +2471,7 @@ exports[`Results Table Should match snapshot 1`] = ` data-testid="expand-revision-button" >
+
-
+ +
+
+
+ + +
+ +
+ +
+ +
+
+ +
+
- + talos +
+ -
- -
- + + +
-
- -
-
-
- - -
+ +
-
-
-
-
-
- - - - +
+
-
-
-
-
- - - + Download JSON + +
-
-
-
- + + + + + + + Expand all + + +
@@ -4213,7 +4595,7 @@ exports[`Results Table for MannWhitneyResultsItem for mann-whitney-u testVersion
@@ -4308,7 +4690,7 @@ exports[`Results Table for MannWhitneyResultsItem for mann-whitney-u testVersion @@ -4409,7 +4791,7 @@ exports[`Results Table for MannWhitneyResultsItem for mann-whitney-u testVersion > + + + + + + +
- + build_metrics +
+ -
- -
- + + +
-
- - -
-
- - -
+ +
-
- -
-
-
- - - - +
+
- - -
-
- - - + Download JSON + +
-
-
-
- + + + + + + + Expand all + + +
@@ -1512,7 +1579,7 @@ exports[`Results View The table should match snapshot and other elements should
@@ -1607,7 +1674,7 @@ exports[`Results View The table should match snapshot and other elements should - - -
- - +
- 0 % + +1.08%
- - -
-
- @@ -2271,7 +2322,7 @@ exports[`Results View The table should match snapshot and other elements should data-testid="expand-revision-button" >
- 0 % + -2.40%
- - -
-
- @@ -2470,7 +2515,7 @@ exports[`Results View The table should match snapshot and other elements should data-testid="expand-revision-button" >
- 0 % + -2.40%
- - -
-
- @@ -2669,7 +2708,7 @@ exports[`Results View The table should match snapshot and other elements should data-testid="expand-revision-button" >
+
+ +
@@ -850,7 +879,7 @@ exports[`SubtestsResultsView Component Tests should render the subtests results >
@@ -939,7 +968,7 @@ exports[`SubtestsResultsView Component Tests should render the subtests results
- - -
-
- - +
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +0.98%
- -
-
- 0.00% -
+
+ +
@@ -2761,7 +2780,7 @@ exports[`SubtestsViewCompareOverTime Component Tests in mann-whitney-u testVersi >
@@ -2850,7 +2869,7 @@ exports[`SubtestsViewCompareOverTime Component Tests in mann-whitney-u testVersi
- - -
-
- - +
- 0.963 % + +1.14%
- - -0.04 -
-
- 15.00% + Negligible
- - + Noise
- 1.135 % + +1.14%
- - 0.02 -
-
- 60.00% + Negligible
- + Real
- 0.963 % + +1.14%
- - -0.05 -
-
- 50.00% + Negligible
- + Real
- 1.135 % + +1.14%
- - 0.12 -
-
- 25.00% + Small
- + Real
- 0.98 % + +0.98%
- - 0.01 -
-
- 45.00% + Negligible
+
+ +
@@ -4363,7 +4333,7 @@ exports[`SubtestsViewCompareOverTime Component Tests in mann-whitney-u testVersi >
@@ -4452,7 +4422,7 @@ exports[`SubtestsViewCompareOverTime Component Tests in mann-whitney-u testVersi
- - -
-
- - +
- 0.963 % + +1.14%
-
-
- - -0.04 + class="status-hint MuiBox-root css-1dj4wrv" + />
- 15.00% + Negligible
- - + Noise
- 1.135 % + +1.14%
- - 0.02 -
-
- 60.00% + Negligible
- + Real
- 0.963 % + +1.14%
- - -0.05 -
-
- 50.00% + Negligible
- + Real
- 1.135 % + +1.14%
- - 0.12 -
-
- 25.00% + Small
- + Real
- 0.98 % + +0.98%
- - 0.01 -
-
- 45.00% + Negligible
+
+ +
@@ -5965,7 +5886,7 @@ exports[`SubtestsViewCompareOverTime Component Tests renders over-time view when >
@@ -6054,7 +5975,7 @@ exports[`SubtestsViewCompareOverTime Component Tests renders over-time view when
- - -
-
- - +
- 0.963 % + +1.14%
- - -0.04 -
-
- 15.00% + Negligible
- - + Noise
- 1.135 % + +1.14%
- - 0.02 -
-
- 60.00% + Negligible
- + Real
- 0.963 % + +1.14%
- - -0.05 -
-
- 50.00% + Negligible
- + Real
- 1.135 % + +1.14%
- - 0.12 -
-
- 25.00% + Small
- + Real
- 0.98 % + +0.98%
- - 0.01 -
-
- 45.00% + Negligible
+
+ +
@@ -7567,7 +7439,7 @@ exports[`SubtestsViewCompareOverTime Component Tests should render the subtests >
@@ -7656,7 +7528,7 @@ exports[`SubtestsViewCompareOverTime Component Tests should render the subtests
- - -
-
- - +
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +1.14%
- -
-
- 0.00% -
- 0 % + +0.98%
- -
-
- 0.00% -
+ + + {columns.map(({ label, checked, onChange }) => ( + onChange(!checked)} + > + e.stopPropagation()} + control={ + onChange(e.target.checked)} + size='small' + /> + } + label={label} + /> + + ))} + + + ); +} + +export default AdvancedColumnsMenu; diff --git a/src/components/CompareResults/HowToReadResults.tsx b/src/components/CompareResults/HowToReadResults.tsx new file mode 100644 index 000000000..346ffe645 --- /dev/null +++ b/src/components/CompareResults/HowToReadResults.tsx @@ -0,0 +1,71 @@ +import Alert from '@mui/material/Alert'; +import AlertTitle from '@mui/material/AlertTitle'; +import Box from '@mui/material/Box'; + +import { useAppDispatch, useAppSelector } from '../../hooks/app'; +import { updateShowHowToRead } from '../../reducers/ColumnPrefsSlice'; + +// Beginner-friendly guide shown above the results table, explaining what each +// column means in plain language. +function HowToReadResults() { + const dispatch = useAppDispatch(); + const showHowToRead = useAppSelector( + (state) => state.columnPrefs.showHowToRead, + ); + + if (!showHowToRead) { + return null; + } + + const onClose = () => { + dispatch(updateShowHowToRead(false)); + localStorage.setItem('showHowToRead', 'false'); + }; + + return ( + + How to read the results + +
  • + Each row is one platform. Base is + the old build; New is your change. +
  • +
  • + Base and New show the average result + for each build, in the test's unit. +
  • +
  • + Δ Median % shows how much the middle result moved + from Base to New. A plus sign means New is higher; a minus sign means + New is lower. +
  • +
  • + Status says whether the change is an{' '} + Improvement, a Regression, or{' '} + No change. +
  • +
  • + Magnitude says how big the difference is: negligible, + small, medium, or large. Sort or filter by it to focus on the biggest + changes. +
  • +
  • + Significance tells you whether the difference is + likely a Real change or just Noise + (random run-to-run variation). +
  • +
  • + Tick Advanced columns to add the expert stats + (Cliff's Delta and CLES). +
  • +
    +
    + ); +} + +export default HowToReadResults; diff --git a/src/components/CompareResults/ResultsControls.tsx b/src/components/CompareResults/ResultsControls.tsx index 103ec22cf..b74f95406 100644 --- a/src/components/CompareResults/ResultsControls.tsx +++ b/src/components/CompareResults/ResultsControls.tsx @@ -1,3 +1,4 @@ +import Box from '@mui/material/Box'; import Checkbox from '@mui/material/Checkbox'; import FormControl from '@mui/material/FormControl'; import FormControlLabel from '@mui/material/FormControlLabel'; @@ -5,10 +6,12 @@ import Grid from '@mui/material/Grid'; import Tooltip from '@mui/material/Tooltip'; import { style } from 'typestyle'; +import AdvancedColumnsMenu from './AdvancedColumnsMenu'; import { DownloadButton } from './DownloadButton'; import RevisionSelect from './RevisionSelect'; import SearchInput from './SearchInput'; -import { useAppSelector } from '../../hooks/app'; +import { useAppDispatch, useAppSelector } from '../../hooks/app'; +import { updateShowHowToRead } from '../../reducers/ColumnPrefsSlice'; import { Strings } from '../../resources/Strings'; import type { CombinedResultsItemType } from '../../types/state'; import type { Framework, TestVersion } from '../../types/types'; @@ -44,71 +47,118 @@ export default function ResultsControls({ onExpandAllChange, }: Props) { const mode = useAppSelector((state) => state.theme.mode); + const dispatch = useAppDispatch(); + const showHowToRead = useAppSelector( + (state) => state.columnPrefs.showHowToRead, + ); + const onShowHowToReadChange = (checked: boolean) => { + dispatch(updateShowHowToRead(checked)); + localStorage.setItem('showHowToRead', String(checked)); + }; return ( - - - - - - - - - - - - - - - - - - - - - - - onExpandAllChange(e.target.checked)} + + {/* Group 1: the input controls. Grows to fill the available width, and + stacks full-width above the selections group once the screen narrows. */} + + + + + + + + + + + + + - } - label='Expand all' - /> - + + + + + + + + + + + + {/* Group 2: the checkbox-style selections. Sits as a compact column on + the right on wide screens; drops to a full-width wrapping row below the + inputs on narrow screens, spreading out to use the available space. */} + + + + + onShowHowToReadChange(e.target.checked)} + size='small' + /> + } + label='How to read the results' + /> + + + onExpandAllChange(e.target.checked)} + size='small' + /> + } + label='Expand all' + /> + + ); diff --git a/src/components/CompareResults/ResultsMain.tsx b/src/components/CompareResults/ResultsMain.tsx index 85fbfb933..95a179b63 100644 --- a/src/components/CompareResults/ResultsMain.tsx +++ b/src/components/CompareResults/ResultsMain.tsx @@ -7,10 +7,15 @@ import { Container } from '@mui/system'; import { useLoaderData } from 'react-router'; import { style } from 'typestyle'; +import HowToReadResults from './HowToReadResults'; import type { LoaderReturnValue } from './loader'; import type { LoaderReturnValue as OverTimeLoaderReturnValue } from './overTimeLoader'; import ResultsTable from './ResultsTable'; -import { STUDENT_T, MANN_WHITNEY_U } from '../../common/constants'; +import { + STUDENT_T, + MANN_WHITNEY_U, + RESULTS_TABLE_MAX_WIDTH, +} from '../../common/constants'; import { useAppSelector } from '../../hooks/app'; import useRawSearchParams from '../../hooks/useRawSearchParams'; import { Strings } from '../../resources/Strings'; @@ -146,7 +151,7 @@ function ResultsMain() { return ( @@ -192,6 +197,7 @@ function ResultsMain() { {testWarnings[testVersion] ?? testWarnings[MANN_WHITNEY_U]} + ); diff --git a/src/components/CompareResults/ResultsTable.tsx b/src/components/CompareResults/ResultsTable.tsx index 3205fb5fe..8529a33b0 100644 --- a/src/components/CompareResults/ResultsTable.tsx +++ b/src/components/CompareResults/ResultsTable.tsx @@ -1,4 +1,4 @@ -import { Suspense, useState } from 'react'; +import { Suspense, useMemo, useState } from 'react'; import Box from '@mui/material/Box'; import CircularProgress from '@mui/material/CircularProgress'; @@ -10,7 +10,9 @@ import ResultsControls from './ResultsControls'; import TableContent from './TableContent'; import TableHeader from './TableHeader'; import { MANN_WHITNEY_U } from '../../common/constants'; +import useAdvancedColumns from '../../hooks/useAdvancedColumns'; import useRawSearchParams from '../../hooks/useRawSearchParams'; +import useSeedAdvancedColumnsFromUrl from '../../hooks/useSeedAdvancedColumnsFromUrl'; import useTableFilters from '../../hooks/useTableFilters'; import useTableSort from '../../hooks/useTableSort'; import { Framework, TestVersion } from '../../types/types'; @@ -35,9 +37,17 @@ export default function ResultsTable() { // This is our custom hook that updates the search params without a rerender. const [rawSearchParams, updateRawSearchParams] = useRawSearchParams(); - const columnsConfig = getColumnsConfiguration( - false, - testVersion ?? MANN_WHITNEY_U, + useSeedAdvancedColumnsFromUrl(); + const advancedColumns = useAdvancedColumns(); + + const columnsConfig = useMemo( + () => + getColumnsConfiguration( + false, + testVersion ?? MANN_WHITNEY_U, + advancedColumns, + ), + [testVersion, advancedColumns], ); // This is our custom hook that manages table filters diff --git a/src/components/CompareResults/RevisionRow.tsx b/src/components/CompareResults/RevisionRow.tsx index 5a4a18f22..672f5d181 100644 --- a/src/components/CompareResults/RevisionRow.tsx +++ b/src/components/CompareResults/RevisionRow.tsx @@ -12,6 +12,7 @@ import { RetriggerButton } from './Retrigger/RetriggerButton'; import RevisionRowExpandable from './RevisionRowExpandable'; import { compareView, compareOverTimeView } from '../../common/constants'; import { getStrategy } from '../../common/testVersions'; +import useAdvancedColumns from '../../hooks/useAdvancedColumns'; import { useSubtestRegressionCount } from '../../hooks/useSubtestRegressionCount'; import { Strings } from '../../resources/Strings'; import { FontSize, Spacing } from '../../styles'; @@ -52,6 +53,8 @@ const browserName = style({ const revisionRow = style({ borderRadius: '4px 0px 0px 4px', display: 'grid', + // Keep this gap in sync with the header grid in TableHeader. + columnGap: `${Spacing.Small}px`, margin: `${Spacing.Small}px 0px 0px 0px`, $nest: { '.cell': { @@ -246,6 +249,7 @@ function RevisionRow(props: RevisionRowProps) { : baseRuns.length; const newRunsCount = replicates ? newRunsReplicates.length : newRuns.length; const strategy = getStrategy(testVersion); + const advancedColumns = useAdvancedColumns(); const { baseAvg: baseAvgValue, newAvg: newAvgValue } = strategy.getAvgValues(result); const [expanded, setExpanded] = useState(false); @@ -312,7 +316,7 @@ function RevisionRow(props: RevisionRowProps) { ({newApp}) )}
    - {strategy.renderColumns(result)} + {strategy.renderColumns(result, advancedColumns)}
    + + + ); @@ -119,7 +124,7 @@ function SubtestsResultsMain({ view }: SubtestsResultsMainProps) { backgroundColor: themeColor100, margin: '0 auto', marginBottom: '80px', - maxWidth: '1400px', + maxWidth: RESULTS_TABLE_MAX_WIDTH, }), title: style({ margin: 0, diff --git a/src/components/CompareResults/SubtestsResults/SubtestsResultsTable.tsx b/src/components/CompareResults/SubtestsResults/SubtestsResultsTable.tsx index 138c795d6..89c31337d 100644 --- a/src/components/CompareResults/SubtestsResults/SubtestsResultsTable.tsx +++ b/src/components/CompareResults/SubtestsResults/SubtestsResultsTable.tsx @@ -8,6 +8,8 @@ import SubtestsTableContent from './SubtestsTableContent'; import NoResultsFound from '.././NoResultsFound'; import TableHeader from '.././TableHeader'; import { STUDENT_T } from '../../../common/constants'; +import useAdvancedColumns from '../../../hooks/useAdvancedColumns'; +import useSeedAdvancedColumnsFromUrl from '../../../hooks/useSeedAdvancedColumnsFromUrl'; import useTableFilters, { filterResults } from '../../../hooks/useTableFilters'; import useTableSort, { sortResults } from '../../../hooks/useTableSort'; import type { CombinedResultsItemType } from '../../../types/state'; @@ -79,9 +81,12 @@ function SubtestsResultsTable({ replicates, testVersion, }: ResultsTableProps) { - const columnsConfiguration = getColumnsConfiguration( - true, - testVersion ?? STUDENT_T, + useSeedAdvancedColumnsFromUrl(); + const advancedColumns = useAdvancedColumns(); + const columnsConfiguration = useMemo( + () => + getColumnsConfiguration(true, testVersion ?? STUDENT_T, advancedColumns), + [testVersion, advancedColumns], ); // This is our custom hook that manages table filters // and provides methods for clearing and toggling them. diff --git a/src/components/CompareResults/SubtestsResults/SubtestsRevisionRow.tsx b/src/components/CompareResults/SubtestsResults/SubtestsRevisionRow.tsx index 0529f5495..b70e34eb8 100644 --- a/src/components/CompareResults/SubtestsResults/SubtestsRevisionRow.tsx +++ b/src/components/CompareResults/SubtestsResults/SubtestsRevisionRow.tsx @@ -7,8 +7,12 @@ import { IconButton, Box } from '@mui/material'; import { style } from 'typestyle'; import RevisionRowExpandable from '.././RevisionRowExpandable'; -import { MANN_WHITNEY_U } from '../../../common/constants'; +import { + MANN_WHITNEY_U, + RESULTS_TABLE_MIN_WIDTH, +} from '../../../common/constants'; import { getStrategy } from '../../../common/testVersions'; +import useAdvancedColumns from '../../../hooks/useAdvancedColumns'; import { Strings } from '../../../resources/Strings'; import { Spacing } from '../../../styles'; import type { CombinedResultsItemType } from '../../../types/state'; @@ -17,6 +21,11 @@ import { TestVersion } from '../../../types/types'; const revisionRow = style({ borderRadius: '4px 0px 0px 4px', display: 'grid', + // Keep this gap in sync with the header grid in TableHeader. + columnGap: `${Spacing.Small}px`, + // Floor the row width to match the header, so columns can't compress into + // each other; below this the page scrolls horizontally. + minWidth: RESULTS_TABLE_MIN_WIDTH, margin: `${Spacing.Small}px 0px 0px 0px`, alignItems: 'center', $nest: { @@ -119,6 +128,7 @@ function SubtestsRevisionRow(props: RevisionRowProps) { }; const strategy = getStrategy(testVersion ?? MANN_WHITNEY_U); + const advancedColumns = useAdvancedColumns(); return ( <> @@ -127,7 +137,7 @@ function SubtestsRevisionRow(props: RevisionRowProps) { sx={{ gridTemplateColumns, backgroundColor: 'revisionRow.background' }} role='row' > - {strategy.renderSubtestColumns(result, expanded)} + {strategy.renderSubtestColumns(result, expanded, advancedColumns)}
    {name} = displayLabel + ? { + padding: '6px 12px', + fontSize: '16px', + // Allow the icon + label to wrap on narrow screens rather than + // overflowing the cell and colliding with the next column. + whiteSpace: 'normal', + flexWrap: 'wrap', + lineHeight: 1.2, + } + : { padding: 0, minWidth: '24px !important', fontSize: '14px' }; // Have some margin between the icon and the text, and some less margin at the // start, but only when there's some actual text. const inlineIconStyle = displayLabel @@ -299,12 +314,14 @@ function TableHeader({ const styles = { tableHeader: style({ display: 'grid', - // Should be kept in sync with the gridTemplateColumns from RevisionRow + // Should be kept in sync with the gridTemplateColumns and gap from RevisionRow gridTemplateColumns: toGridTemplateColumns(columnsConfiguration), + columnGap: `${Spacing.Small}px`, + minWidth: RESULTS_TABLE_MIN_WIDTH, background: themeMode == 'light' ? Colors.Background100 : Colors.Background300Dark, borderRadius: '4px', - paddingBlock: Spacing.Small, + paddingBlock: Spacing.Medium, marginTop: Spacing.Medium, marginBottom: Spacing.Large, $nest: { @@ -422,7 +439,7 @@ function TableHeader({ return (
    diff --git a/src/components/CompareResults/TableRevisionContent.tsx b/src/components/CompareResults/TableRevisionContent.tsx index 3e972f573..77ef75595 100644 --- a/src/components/CompareResults/TableRevisionContent.tsx +++ b/src/components/CompareResults/TableRevisionContent.tsx @@ -3,7 +3,11 @@ import { style } from 'typestyle'; import LinkToRevision from './LinkToRevision'; import RevisionRow from './RevisionRow'; import TestHeader from './TestHeader'; -import type { compareView, compareOverTimeView } from '../../common/constants'; +import { + RESULTS_TABLE_MIN_WIDTH, + type compareView, + type compareOverTimeView, +} from '../../common/constants'; import { Spacing } from '../../styles'; import type { CombinedResultsItemType } from '../../types/state'; import { TestVersion } from '../../types/types'; @@ -17,6 +21,9 @@ const styles = { testBlock: style({ /* Note that this padding will be added to the padding below */ paddingTop: Spacing.Small, + // Floor the row width to match the header, so columns can't compress into + // each other; below this the page scrolls horizontally. + minWidth: RESULTS_TABLE_MIN_WIDTH, }), revisionBlock: style({ paddingBottom: Spacing.Large }), }; diff --git a/src/hooks/useAdvancedColumns.ts b/src/hooks/useAdvancedColumns.ts new file mode 100644 index 000000000..e5b7fcdc6 --- /dev/null +++ b/src/hooks/useAdvancedColumns.ts @@ -0,0 +1,18 @@ +import { useMemo } from 'react'; + +import { useAppSelector } from './app'; +import type { AdvancedColumns } from '../types/types'; + +// Visibility of the advanced (power-user) statistics columns (Cliff's Delta, +// CLES) from the columnPrefs slice. Returns a stable object memoized on the +// two flags, so callers can use it directly in `useMemo` deps and pass it to +// the strategy renderers without re-rendering on unrelated state changes. +function useAdvancedColumns(): AdvancedColumns { + const cliffsDelta = useAppSelector( + (state) => state.columnPrefs.showCliffsDelta, + ); + const cles = useAppSelector((state) => state.columnPrefs.showCles); + return useMemo(() => ({ cliffsDelta, cles }), [cliffsDelta, cles]); +} + +export default useAdvancedColumns; diff --git a/src/hooks/useSeedAdvancedColumnsFromUrl.ts b/src/hooks/useSeedAdvancedColumnsFromUrl.ts new file mode 100644 index 000000000..3843f3e8d --- /dev/null +++ b/src/hooks/useSeedAdvancedColumnsFromUrl.ts @@ -0,0 +1,30 @@ +import { useEffect } from 'react'; + +import { useAppDispatch } from './app'; +import { + updateShowCliffsDelta, + updateShowCles, +} from '../reducers/ColumnPrefsSlice'; +import { + ADVANCED_COLUMNS_PARAM, + parseAdvancedColumns, +} from '../utils/advancedColumnsUrl'; + +// On mount, seed the advanced-column visibility from the URL so a shared link +// reproduces the selected columns. Toggling updates both the URL (for sharing) +// and Redux (for reactive rendering); this only handles the initial +// URL → Redux direction. Call once per results view. +function useSeedAdvancedColumnsFromUrl() { + const dispatch = useAppDispatch(); + useEffect(() => { + const params = new URLSearchParams(window.location.search); + if (!params.has(ADVANCED_COLUMNS_PARAM)) { + return; + } + const { cliffsDelta, cles } = parseAdvancedColumns(params); + dispatch(updateShowCliffsDelta(cliffsDelta)); + dispatch(updateShowCles(cles)); + }, []); +} + +export default useSeedAdvancedColumnsFromUrl; diff --git a/src/reducers/ColumnPrefsSlice.ts b/src/reducers/ColumnPrefsSlice.ts new file mode 100644 index 000000000..4dec8f2dd --- /dev/null +++ b/src/reducers/ColumnPrefsSlice.ts @@ -0,0 +1,38 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; + +// Results-view display preferences: +// - showCliffsDelta / showCles: the two advanced statistics columns, toggled +// independently from the "Advanced columns" dropdown. Persisted in the URL +// (see utils/advancedColumnsUrl) so shared links reproduce the selection; +// seeded into this slice on mount. Default off (the simplified view). +// - showHowToRead: when true the "How to read the results" helper panel is +// shown above the table. Persisted to localStorage. +const initialState: { + showCliffsDelta: boolean; + showCles: boolean; + showHowToRead: boolean; +} = { + showCliffsDelta: false, + showCles: false, + showHowToRead: localStorage.getItem('showHowToRead') !== 'false', +}; + +const columnPrefs = createSlice({ + name: 'columnPrefs', + initialState, + reducers: { + updateShowCliffsDelta(state, action: PayloadAction) { + state.showCliffsDelta = action.payload; + }, + updateShowCles(state, action: PayloadAction) { + state.showCles = action.payload; + }, + updateShowHowToRead(state, action: PayloadAction) { + state.showHowToRead = action.payload; + }, + }, +}); + +export const { updateShowCliffsDelta, updateShowCles, updateShowHowToRead } = + columnPrefs.actions; +export default columnPrefs.reducer; diff --git a/src/types/types.ts b/src/types/types.ts index 962134a0e..22867a519 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -461,6 +461,14 @@ export type TokenBearer = { export type TestVersion = 'student-t' | 'mann-whitney-u'; +// Per-column visibility for the advanced (power-user) statistics columns. +// Each is toggled independently from the "Advanced columns" dropdown, so +// either, both, or neither can be shown. +export interface AdvancedColumns { + cliffsDelta: boolean; + cles: boolean; +} + export type SortFunc = ( resultA: CombinedResultsItemType, resultB: CombinedResultsItemType, diff --git a/src/utils/advancedColumnsUrl.ts b/src/utils/advancedColumnsUrl.ts new file mode 100644 index 000000000..68315d0c9 --- /dev/null +++ b/src/utils/advancedColumnsUrl.ts @@ -0,0 +1,37 @@ +import type { AdvancedColumns } from '../types/types'; + +// The advanced (power-user) columns are persisted in the URL so a shared link +// reproduces the selected columns. Encoded as a comma-separated list of the +// enabled column keys, e.g. `?advanced_columns=cliffs_delta,cles`. An absent +// param means neither is shown (the simplified view). +export const ADVANCED_COLUMNS_PARAM = 'advanced_columns'; + +const CLIFFS_DELTA = 'cliffs_delta'; +const CLES = 'cles'; + +// Parse advanced-column visibility from a URL search string or params. +export function parseAdvancedColumns( + search: string | URLSearchParams, +): AdvancedColumns { + const params = + typeof search === 'string' ? new URLSearchParams(search) : search; + const enabled = (params.get(ADVANCED_COLUMNS_PARAM) ?? '') + .split(',') + .filter(Boolean); + return { + cliffsDelta: enabled.includes(CLIFFS_DELTA), + cles: enabled.includes(CLES), + }; +} + +// Serialize to the comma-list value, or null when no advanced column is on so +// the caller can delete the param and keep shared URLs clean. +export function serializeAdvancedColumns( + advanced: AdvancedColumns, +): string | null { + const enabled = [ + advanced.cliffsDelta ? CLIFFS_DELTA : null, + advanced.cles ? CLES : null, + ].filter(Boolean); + return enabled.length ? enabled.join(',') : null; +} diff --git a/src/utils/rowTemplateColumns.ts b/src/utils/rowTemplateColumns.ts index 1caa4d4a1..8830946ea 100644 --- a/src/utils/rowTemplateColumns.ts +++ b/src/utils/rowTemplateColumns.ts @@ -1,5 +1,5 @@ import { getColumnsForVersion } from '../common/testVersions'; -import { TableConfig, TestVersion } from '../types/types'; +import { AdvancedColumns, TableConfig, TestVersion } from '../types/types'; // Re-exported for consumers that import sort utilities from this module. export { @@ -11,6 +11,8 @@ export { export const getColumnsConfiguration = ( isSubtestTable: boolean, testVersion: TestVersion, -): TableConfig => getColumnsForVersion(testVersion, isSubtestTable); + advancedColumns: AdvancedColumns, +): TableConfig => + getColumnsForVersion(testVersion, isSubtestTable, advancedColumns); export { toGridTemplateColumns } from './gridTemplateColumns';