Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions src/__tests__/CompareResults/ResultsTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1365,3 +1365,121 @@ describe('Advanced-columns toggle for mann-whitney-u testVersion', () => {
expect(advancedParam()).toBeNull();
});
});

describe('cookie persistence vs. shareable URLs', () => {
it('seeds filters from cookies and marks the URL initialized on a fresh URL', async () => {
document.cookie = 'perfcompare_filter_status=regression; path=/';
const { testCompareData } = getTestData();
setupAndRender(testCompareData, 'test_version=student-t');

await screen.findByText('a11yr');

// The remembered cookie is applied to the view...
expect(summarizeVisibleRows()).toEqual([
'a11yr dhtml.html spam opt e10s fission stylo webrender',
' - Linux 18.04, Regression, 1.85 %, Medium',
]);
// ...and materialised into the URL, which is now marked initialized so the
// link reproduces this exact view for anyone.
expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['regression'] });
expect(new URLSearchParams(window.location.search).get('initialized')).toBe(
'1',
);
});

it('ignores cookies when the URL is already initialized', async () => {
// A different viewer's cookie must not change what an initialized (shared)
// URL displays.
document.cookie = 'perfcompare_filter_status=regression; path=/';
const { testCompareData } = getTestData();
setupAndRender(testCompareData, 'test_version=student-t&initialized=1');

await screen.findByText('a11yr');

// Cookie is ignored: every status stays visible.
expect(summarizeVisibleRows()).toEqual([
'a11yr dhtml.html spam opt e10s fission stylo webrender',
' - Linux 18.04, Regression, 1.85 %, Medium',
' - macOS 10.15, Improvement, 1.08 %, Low',
' - Windows 10, -, -24 %, -',
' - Windows 10, -, -2.4 %, High',
]);
// ...and the cookie is not written into the URL.
expect(summarizeTableFiltersFromUrl()).toEqual({});
});

it('keeps the initialized marker after toggling a filter', async () => {
const { testCompareData } = getTestData();
setupAndRender(testCompareData, 'test_version=student-t');

await screen.findByText('a11yr');

const user = userEvent.setup({
advanceTimers: jest.advanceTimersByTime,
});
await clickMenuItem(user, 'Status', /No changes/);

expect(summarizeTableFiltersFromUrl()).toEqual({
status: ['improvement', 'regression'],
});
expect(new URLSearchParams(window.location.search).get('initialized')).toBe(
'1',
);
});

it('keeps the initialized marker and seeded filters after a search-term change', async () => {
document.cookie = 'perfcompare_filter_status=regression; path=/';
const { testCompareData } = getTestData();
setupAndRender(testCompareData, 'test_version=student-t');

await screen.findByText('a11yr');

// Seeded from the cookie and marked initialized.
expect(summarizeTableFiltersFromUrl()).toEqual({ status: ['regression'] });
expect(new URLSearchParams(window.location.search).get('initialized')).toBe(
'1',
);

const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
// Submit with Enter so the write happens immediately (bypasses the input's
// debounce, which fake timers don't flush after typing).
await user.type(
screen.getByPlaceholderText('Filter results'),
'linux{Enter}',
);

// The search term is written, and the out-of-band params survive.
const params = new URLSearchParams(window.location.search);
expect(params.get('search')).toBe('linux');
expect(params.get('initialized')).toBe('1');
expect(params.get('filter_status')).toBe('regression');
});

it('keeps the initialized marker and seeded filters after a test-version change', async () => {
document.cookie = 'perfcompare_filter_status=regression; path=/';
const { testCompareData } = getTestData();
setupAndRender(testCompareData, 'test_version=student-t');

await screen.findByText('a11yr');
expect(new URLSearchParams(window.location.search).get('initialized')).toBe(
'1',
);

const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
await user.click(
screen.getByRole('combobox', { name: 'Stats Test Version' }),
);
await user.click(
await screen.findByRole('option', { name: 'Mann-Whitney-U' }),
);

// The test version changes (a router navigation), and the marker + seeded
// filter ride along instead of being dropped.
await waitFor(() => {
const params = new URLSearchParams(window.location.search);
expect(params.get('test_version')).toBe('mann-whitney-u');
expect(params.get('initialized')).toBe('1');
expect(params.get('filter_status')).toBe('regression');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ exports[`SubtestsResultsView Component Tests for mann-whitney-u testVersion tabl
style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
class="MuiTouchRipple-child"
/>
</span>
<span
Expand Down Expand Up @@ -108,7 +108,7 @@ exports[`SubtestsResultsView Component Tests for mann-whitney-u testVersion tabl
style="width: 1px; height: 1px; top: -0.5px; left: -0.5px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving MuiTouchRipple-childPulsate"
class="MuiTouchRipple-child MuiTouchRipple-childPulsate"
/>
</span>
<span
Expand Down Expand Up @@ -205,7 +205,7 @@ exports[`SubtestsResultsView Component Tests for mann-whitney-u testVersion tabl
style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
class="MuiTouchRipple-child"
/>
</span>
<span
Expand Down Expand Up @@ -250,7 +250,7 @@ exports[`SubtestsResultsView Component Tests for mann-whitney-u testVersion tabl
style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
class="MuiTouchRipple-child"
/>
</span>
<span
Expand Down Expand Up @@ -1878,7 +1878,7 @@ exports[`SubtestsResultsView Component Tests table sorting can sort the table an
style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
class="MuiTouchRipple-child"
/>
</span>
<span
Expand Down Expand Up @@ -1931,7 +1931,7 @@ exports[`SubtestsResultsView Component Tests table sorting can sort the table an
style="width: 1px; height: 1px; top: -0.5px; left: -0.5px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving MuiTouchRipple-childPulsate"
class="MuiTouchRipple-child MuiTouchRipple-childPulsate"
/>
</span>
<span
Expand Down Expand Up @@ -2028,7 +2028,7 @@ exports[`SubtestsResultsView Component Tests table sorting can sort the table an
style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
class="MuiTouchRipple-child"
/>
</span>
<span
Expand Down Expand Up @@ -2117,7 +2117,7 @@ exports[`SubtestsResultsView Component Tests table sorting can sort the table an
style="width: 2.8284271247461903px; height: 2.8284271247461903px; top: -1.4142135623730951px; left: -1.4142135623730951px;"
>
<span
class="MuiTouchRipple-child MuiTouchRipple-childLeaving"
class="MuiTouchRipple-child"
/>
</span>
<span
Expand Down
7 changes: 5 additions & 2 deletions src/components/CompareResults/AdvancedColumnsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
SIGNIFICANCE,
serializeAdvancedColumns,
} from '../../utils/advancedColumnsUrl';
import { currentUrlParams } from '../../utils/tableStatePersistence';

// The advanced statistics columns are toggled independently — any combination
// can be shown. The option values reuse the URL keys so the dropdown, the URL
Expand All @@ -38,7 +39,7 @@ function AdvancedColumnsMenu() {
const dispatch = useAppDispatch();
const mode = useAppSelector((state) => state.theme.mode);
const advancedColumns = useAdvancedColumns();
const [rawSearchParams, updateRawSearchParams] = useRawSearchParams();
const [, updateRawSearchParams] = useRawSearchParams();
// Track the Select's open state so the tooltip can be suppressed while the
// dropdown is open — otherwise it renders over (and hides) the checkboxes.
const [menuOpen, setMenuOpen] = useState(false);
Expand All @@ -54,7 +55,9 @@ function AdvancedColumnsMenu() {
dispatch(updateShowCles(next.cles));
dispatch(updateShowSignificance(next.significance));

const params = new URLSearchParams(rawSearchParams);
// Build from the live URL so toggling columns doesn't drop params written
// out-of-band (e.g. the `initialized` marker and cookie-seeded filter/sort).
const params = currentUrlParams();
const value = serializeAdvancedColumns(next);
if (value) {
params.set(ADVANCED_COLUMNS_PARAM, value);
Expand Down
8 changes: 6 additions & 2 deletions src/components/CompareResults/ResultsMain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Colors, FontsRaw, FontSizeRaw, Spacing } from '../../styles';
import pencilDark from '../../theme/img/pencil-dark.svg';
import pencil from '../../theme/img/pencil.svg';
import type { TestVersion } from '../../types/types';
import { currentUrlParams } from '../../utils/tableStatePersistence';
import EditTitleInput from '../CompareResults/EditTitleInput';
import ToggleReplicatesButton from '../Shared/ToggleReplicatesButton';

Expand Down Expand Up @@ -116,8 +117,11 @@ function ResultsMain() {
};

const onSaveButtonClick = () => {
rawSearchParams.set('title', comparisonTitleName);
updateRawSearchParams(rawSearchParams);
// Build from the live URL so we don't drop params written out-of-band
// (e.g. the `initialized` marker and cookie-seeded filter/sort).
const params = currentUrlParams();
params.set('title', comparisonTitleName);
updateRawSearchParams(params);
showEditComparisonTitleInput(false);
};

Expand Down
31 changes: 22 additions & 9 deletions src/components/CompareResults/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TableContent from './TableContent';
import TableHeader from './TableHeader';
import { MANN_WHITNEY_U } from '../../common/constants';
import useAdvancedColumns from '../../hooks/useAdvancedColumns';
import useInitializeTableStateFromCookies from '../../hooks/useInitializeTableStateFromCookies';
import useRawSearchParams from '../../hooks/useRawSearchParams';
import useSeedAdvancedColumnsFromUrl from '../../hooks/useSeedAdvancedColumnsFromUrl';
import useTableFilters from '../../hooks/useTableFilters';
Expand All @@ -20,6 +21,7 @@ import {
getColumnsConfiguration,
toGridTemplateColumns,
} from '../../utils/rowTemplateColumns';
import { currentUrlParams } from '../../utils/tableStatePersistence';

type CombinedLoaderReturnValue = LoaderReturnValue | OverTimeLoaderReturnValue;
export default function ResultsTable() {
Expand All @@ -32,7 +34,7 @@ export default function ResultsTable() {
testVersion,
} = useLoaderData<CombinedLoaderReturnValue>();

const [searchParams, setSearchParams] = useSearchParams();
const [, setSearchParams] = useSearchParams();

// This is our custom hook that updates the search params without a rerender.
const [rawSearchParams, updateRawSearchParams] = useRawSearchParams();
Expand All @@ -50,6 +52,10 @@ export default function ResultsTable() {
[testVersion, advancedColumns],
);

// On a fresh (uninitialized) URL, seed filter/sort from cookies into the URL
// and mark it initialized, so shared links reproduce the same view.
useInitializeTableStateFromCookies(columnsConfig);

// This is our custom hook that manages table filters
// and provides methods for clearing and toggling them.
const { tableFilters, onClearFilter, onToggleFilter } =
Expand All @@ -65,29 +71,36 @@ export default function ResultsTable() {
);
const [expandAll, setExpandAll] = useState(false);

// These writers build from the *live* URL (currentUrlParams) rather than a
// render-time snapshot, so they preserve params written out-of-band — most
// importantly the `initialized` marker and cookie-seeded filter/sort — that a
// stale snapshot would drop (see useRawSearchParams / tableStatePersistence).
const onFrameworkChange = (newFrameworkId: Framework['id']) => {
setFrameworkIdVal(newFrameworkId);
searchParams.set('framework', newFrameworkId.toString());
setSearchParams(searchParams);
const params = currentUrlParams();
params.set('framework', newFrameworkId.toString());
setSearchParams(params);
};

const onSearchTermChange = (newSearchTerm: string) => {
setSearchTerm(newSearchTerm);
const params = currentUrlParams();
if (newSearchTerm) {
rawSearchParams.set('search', newSearchTerm);
params.set('search', newSearchTerm);
} else {
rawSearchParams.delete('search');
params.delete('search');
}
updateRawSearchParams(rawSearchParams);
updateRawSearchParams(params);
};

const onTestVersionChange = (testVersion: TestVersion): void => {
setTestVersionVal(testVersion);
searchParams.set('test_version', testVersion);
const params = currentUrlParams();
params.set('test_version', testVersion);
if (testVersion !== MANN_WHITNEY_U) {
searchParams.delete('replicates');
params.delete('replicates');
}
setSearchParams(searchParams);
setSearchParams(params);
};

const rowGridTemplateColumns = toGridTemplateColumns(columnsConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import NoResultsFound from '.././NoResultsFound';
import TableHeader from '.././TableHeader';
import { STUDENT_T } from '../../../common/constants';
import useAdvancedColumns from '../../../hooks/useAdvancedColumns';
import useInitializeTableStateFromCookies from '../../../hooks/useInitializeTableStateFromCookies';
import useSeedAdvancedColumnsFromUrl from '../../../hooks/useSeedAdvancedColumnsFromUrl';
import useTableFilters, { filterResults } from '../../../hooks/useTableFilters';
import useTableSort, { sortResults } from '../../../hooks/useTableSort';
Expand Down Expand Up @@ -88,6 +89,11 @@ function SubtestsResultsTable({
getColumnsConfiguration(true, testVersion ?? STUDENT_T, advancedColumns),
[testVersion, advancedColumns],
);

// On a fresh (uninitialized) URL, seed filter/sort from cookies into the URL
// and mark it initialized, so shared links reproduce the same view.
useInitializeTableStateFromCookies(columnsConfiguration);

// This is our custom hook that manages table filters
// and provides methods for clearing and toggling them.
const { tableFilters, onClearFilter, onToggleFilter } =
Expand Down
Loading