Skip to content

Commit 601d629

Browse files
committed
smarter delete-all button in header
1 parent 9e4dfd7 commit 601d629

File tree

4 files changed

+79
-7
lines changed

4 files changed

+79
-7
lines changed

special-pages/pages/history/app/components/Header.js

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import { useHistoryServiceDispatch } from '../global-state/HistoryServiceProvide
1414
export function Header() {
1515
const search = useQueryContext();
1616
const term = useComputed(() => search.value.term);
17+
const range = useComputed(() => search.value.range);
1718
const domain = useComputed(() => search.value.domain);
1819
return (
1920
<div class={styles.root}>
20-
<Controls term={term} />
21+
<Controls term={term} range={range} />
2122
<div class={styles.search}>
2223
<SearchForm term={term} domain={domain} />
2324
</div>
@@ -30,22 +31,52 @@ export function Header() {
3031
*
3132
* @param {Object} props - Properties passed to the component.
3233
* @param {import("@preact/signals").Signal<string|null>} props.term
34+
* @param {import("@preact/signals").Signal<string|null>} props.range
3335
*/
34-
function Controls({ term }) {
36+
function Controls({ term, range }) {
3537
const { t } = useTypedTranslation();
38+
const { results } = useData();
3639
const selected = useSelected();
3740
const dispatch = useHistoryServiceDispatch();
41+
42+
/**
43+
* Aria labels + title text is derived from the current result set.
44+
*/
45+
const ariaDisabled = useComputed(() => (results.value.items.length === 0 ? 'true' : 'false'));
46+
const title = useComputed(() => (results.value.items.length === 0 ? t('delete_none') : ''));
47+
48+
/**
49+
* The button text should alternate between 'delete' and 'delete all' depending on the
50+
* state of the current query. It should only read 'delete all' when the query is the default one
51+
* and there are no selections
52+
*/
3853
const buttonTxt = useComputed(() => {
3954
const hasTerm = term.value !== null && term.value.trim() !== '';
4055
const hasSelections = selected.value.size > 0;
41-
return hasTerm || hasSelections ? t('delete_some') : t('delete_all');
56+
const hasRange = range.value !== null;
57+
return hasTerm || hasSelections || hasRange ? t('delete_some') : t('delete_all');
4258
});
43-
const { results } = useData();
44-
const ariaDisabled = useComputed(() => (results.value.items.length === 0 ? 'true' : 'false'));
45-
const title = useComputed(() => (results.value.items.length === 0 ? t('delete_none') : ''));
59+
60+
/**
61+
* Which action should the delete button take?
62+
*
63+
* - if there are selections, they should be deleted
64+
* - if there's a range selected, that should be deleted
65+
* - or fallback to deleting all
66+
*/
67+
function onClick() {
68+
if (selected.value.size > 0) {
69+
return dispatch({ kind: 'delete-entries-by-index', value: [...selected.value] });
70+
}
71+
if (range.value !== null) {
72+
return dispatch({ kind: 'delete-range', value: range.value });
73+
}
74+
dispatch({ kind: 'delete-all' });
75+
}
76+
4677
return (
4778
<div class={styles.controls}>
48-
<button class={styles.largeButton} onClick={() => dispatch({ kind: 'delete-all' })} aria-disabled={ariaDisabled} title={title}>
79+
<button class={styles.largeButton} onClick={onClick} aria-disabled={ariaDisabled} title={title}>
4980
<Trash />
5081
<span>{buttonTxt}</span>
5182
</button>

special-pages/pages/history/integration-tests/history-selections.spec.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ test.describe('history selections', () => {
135135
await hp.selectsRowIndex(0);
136136
await hp.deleteAllButtonReflectsSelection();
137137
});
138+
test('`delete` in header respects selection', async ({ page }, workerInfo) => {
139+
const hp = HistoryTestPage.create(page, workerInfo).withEntries(2000);
140+
await hp.openPage({});
141+
await hp.selectsRowIndex(0);
142+
await hp.clicksDeleteInHeader({ action: 'delete' });
143+
await hp.didDeleteSelection([0]);
144+
});
138145
test('`deleteAll` button text changes when a query is present', async ({ page }, workerInfo) => {
139146
const hp = HistoryTestPage.create(page, workerInfo).withEntries(2000);
140147
await hp.openPage({});

special-pages/pages/history/integration-tests/history.page.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,14 @@ export class HistoryTestPage {
227227
expect(calls[0].payload.params).toStrictEqual({ range: 'today' });
228228
}
229229

230+
/**
231+
* @param {import("../types/history.js").Range} range
232+
*/
233+
async didDeleteRange(range) {
234+
const calls = await this.mocks.waitForCallCount({ method: 'deleteRange', count: 1 });
235+
expect(calls[0].payload.params).toStrictEqual({ range });
236+
}
237+
230238
/**
231239
* @param {import('../types/history.ts').DeleteRangeResponse} resp
232240
*/
@@ -259,6 +267,15 @@ export class HistoryTestPage {
259267
await expect(page.getByRole('heading', { level: 2, name: 'Nothing to see here!' })).toBeVisible();
260268
}
261269

270+
/**
271+
* @param {import('../types/history.ts').DeleteRangeResponse} resp
272+
*/
273+
async clicksDeleteInHeader(resp) {
274+
const { page } = this;
275+
this._withDialogHandling(resp);
276+
await page.locator('header').getByRole('button', { name: 'Delete', exact: true }).click();
277+
}
278+
262279
/**
263280
* @param {import('../types/history.ts').DeleteRangeResponse} resp
264281
*/
@@ -502,4 +519,14 @@ export class HistoryTestPage {
502519
// await this.page.pause();
503520
await this.page.getByRole('button', { name: `Delete history for ${range}` }).hover();
504521
}
522+
523+
/**
524+
* @param {number[]} indexes
525+
*/
526+
async didDeleteSelection(indexes) {
527+
const items = generateSampleData({ count: this.entries, offset: 0 });
528+
const ids = indexes.map((index) => items[index].id);
529+
const calls = await this.mocks.waitForCallCount({ method: 'entries_delete', count: 1 });
530+
expect(calls[0].payload.params).toStrictEqual({ ids });
531+
}
505532
}

special-pages/pages/history/integration-tests/history.spec.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ test.describe('history', () => {
116116
await hp.openPage({});
117117
await hp.deletesAllHistoryFromHeader({ action: 'delete' });
118118
});
119+
test('deleting range from the header', async ({ page }, workerInfo) => {
120+
const hp = HistoryTestPage.create(page, workerInfo).withEntries(2000);
121+
await hp.openPage({});
122+
await hp.selectsToday();
123+
await hp.clicksDeleteInHeader({ action: 'delete' });
124+
await hp.didDeleteRange('today');
125+
});
119126
test('3 dots menu on Section title', async ({ page }, workerInfo) => {
120127
const hp = HistoryTestPage.create(page, workerInfo).withEntries(2000);
121128
await hp.openPage({});

0 commit comments

Comments
 (0)