Skip to content

Commit 12b3ab3

Browse files
shakyShaneShane Osbourne
andauthored
ntp: stats - don't show expansion controls without recent items (#1243)
* ntp: stats - don't show expansion controls without recent items * Ensure the bar visualization updates correctly --------- Co-authored-by: Shane Osbourne <[email protected]>
1 parent 73d72ac commit 12b3ab3

File tree

6 files changed

+118
-16
lines changed

6 files changed

+118
-16
lines changed

special-pages/pages/new-tab/app/privacy-stats/PrivacyStats.js

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -109,18 +109,19 @@ export function Heading({ expansion, trackerCompanies, totalCount, onToggle, but
109109
</span>
110110
{none && <p className={styles.title}>{t('trackerStatsNoRecent')}</p>}
111111
{some && <p className={styles.title}>{alltimeTitle}</p>}
112-
<span className={styles.expander}>
113-
<ShowHideButton
114-
buttonAttrs={{
115-
...buttonAttrs,
116-
hidden: trackerCompanies.length === 0,
117-
'aria-expanded': expansion === 'expanded',
118-
'aria-pressed': expansion === 'expanded',
119-
}}
120-
onClick={onToggle}
121-
text={expansion === 'expanded' ? t('trackerStatsHideLabel') : t('trackerStatsToggleLabel')}
122-
/>
123-
</span>
112+
{recent > 0 && (
113+
<span className={styles.expander}>
114+
<ShowHideButton
115+
buttonAttrs={{
116+
...buttonAttrs,
117+
'aria-expanded': expansion === 'expanded',
118+
'aria-pressed': expansion === 'expanded',
119+
}}
120+
onClick={onToggle}
121+
text={expansion === 'expanded' ? t('trackerStatsHideLabel') : t('trackerStatsToggleLabel')}
122+
/>
123+
</span>
124+
)}
124125
<p className={styles.subtitle}>
125126
{recent === 0 && t('trackerStatsNoActivity')}
126127
{recent > 0 && recentTitle}
@@ -136,10 +137,10 @@ export function Heading({ expansion, trackerCompanies, totalCount, onToggle, but
136137
*/
137138
// eslint-disable-next-line no-redeclare
138139
export function Body({ trackerCompanies, listAttrs = {} }) {
139-
const max = trackerCompanies[0]?.count ?? 0;
140140
const { t } = useTypedTranslation();
141141
const [formatter] = useState(() => new Intl.NumberFormat());
142142
const sorted = sortStatsForDisplay(trackerCompanies);
143+
const max = sorted[0]?.count ?? 0;
143144

144145
return (
145146
<ul {...listAttrs} class={styles.list} data-testid="CompanyList">

special-pages/pages/new-tab/app/privacy-stats/integration-tests/privacy-stats.spec.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ test.describe('newtab privacy stats', () => {
55
test('fetches config + stats', async ({ page }, workerInfo) => {
66
const ntp = NewtabPage.create(page, workerInfo);
77
await ntp.reducedMotion();
8-
await ntp.openPage();
8+
await ntp.openPage({ additional: { stats: 'few' } });
99

1010
const calls1 = await ntp.mocks.waitForCallCount({ method: 'initialSetup', count: 1 });
1111
const calls2 = await ntp.mocks.waitForCallCount({ method: 'stats_getData', count: 1 });
@@ -22,5 +22,45 @@ test.describe('newtab privacy stats', () => {
2222
expect(await listItems.nth(2).textContent()).toBe('Amazon67');
2323
expect(await listItems.nth(3).textContent()).toBe('Google Ads2');
2424
expect(await listItems.nth(4).textContent()).toBe('Other210');
25+
26+
// show/hide
27+
await page.getByLabel('Hide recent activity').click();
28+
await page.getByLabel('Show recent activity').click();
2529
});
30+
test(
31+
'hiding the expander when empty',
32+
{
33+
annotation: {
34+
type: 'issue',
35+
description: 'https://app.asana.com/0/0/1208792040873366/f',
36+
},
37+
},
38+
async ({ page }, workerInfo) => {
39+
const ntp = NewtabPage.create(page, workerInfo);
40+
await ntp.reducedMotion();
41+
await ntp.openPage({ additional: { stats: 'none' } });
42+
await page.getByText('No recent tracking activity').waitFor();
43+
await expect(page.getByLabel('Hide recent activity')).not.toBeVisible();
44+
await expect(page.getByLabel('Show recent activity')).not.toBeVisible();
45+
},
46+
);
47+
test(
48+
'bar width',
49+
{
50+
annotation: {
51+
type: 'issue',
52+
description: 'https://app.asana.com/0/0/1208800221025230/f',
53+
},
54+
},
55+
async ({ page }, workerInfo) => {
56+
const ntp = NewtabPage.create(page, workerInfo);
57+
await ntp.reducedMotion();
58+
await ntp.openPage({ additional: { stats: 'willUpdate', 'stats-update-count': '2' } });
59+
60+
//
61+
// Checking the first + last bar widths due to a regression
62+
await page.getByText('Google Ads5').locator('[style="width: 100%;"]').waitFor();
63+
await page.getByText('Facebook1').locator('[style="width: 20%;"]').waitFor();
64+
},
65+
);
2666
});

special-pages/pages/new-tab/app/privacy-stats/mocks/stats.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,29 @@ export const stats = {
4747
totalCount: 0,
4848
trackerCompanies: [],
4949
},
50+
willUpdate: {
51+
totalCount: 481_113,
52+
trackerCompanies: [
53+
{
54+
displayName: 'Facebook',
55+
count: 1,
56+
},
57+
{
58+
displayName: 'Google',
59+
count: 1,
60+
},
61+
{
62+
displayName: DDG_STATS_OTHER_COMPANY_IDENTIFIER,
63+
count: 1,
64+
},
65+
{
66+
displayName: 'Amazon',
67+
count: 1,
68+
},
69+
{
70+
displayName: 'Google Ads',
71+
count: 1,
72+
},
73+
],
74+
},
5075
};

special-pages/pages/new-tab/app/privacy-stats/privacy-stats.utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { DDG_STATS_OTHER_COMPANY_IDENTIFIER } from './constants.js';
88
* @return {TrackerCompany[]}
99
*/
1010
export function sortStatsForDisplay(stats) {
11-
const sorted = stats.sort((a, b) => b.count - a.count);
11+
const sorted = stats.slice().sort((a, b) => b.count - a.count);
1212
const other = sorted.findIndex((x) => x.displayName === DDG_STATS_OTHER_COMPANY_IDENTIFIER);
1313
if (other > -1) {
1414
const popped = sorted.splice(other, 1);

special-pages/pages/new-tab/integration-tests/new-tab.page.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ export class NewtabPage {
5858
* @param {boolean} [params.willThrow] - Optional flag to simulate an exception
5959
* @param {string|number} [params.favorites] - Optional flag to preload a list of favorites
6060
* @param {string|string[]} [params.nextSteps] - Optional flag to load Next Steps cards
61+
* @param {Record<string, any>} [params.additional] - Optional map of key/values to add
6162
* @param {string} [params.rmf] - Optional flag to add certain rmf example
6263
* @param {string} [params.updateNotification] - Optional flag to point to display=components view with certain rmf example visible
6364
* @param {string} [params.platformName] - Optional parameters for opening the page.
6465
*/
65-
async openPage({ mode = 'debug', platformName, willThrow = false, favorites, nextSteps, rmf, updateNotification } = {}) {
66+
async openPage({ mode = 'debug', additional, platformName, willThrow = false, favorites, nextSteps, rmf, updateNotification } = {}) {
6667
await this.mocks.install();
6768
const searchParams = new URLSearchParams({ mode, willThrow: String(willThrow) });
6869

@@ -92,6 +93,10 @@ export class NewtabPage {
9293
searchParams.set('update-notification', updateNotification);
9394
}
9495

96+
for (const [key, value] of Object.entries(additional || {})) {
97+
searchParams.set(key, value);
98+
}
99+
95100
await this.page.goto('/new-tab' + '?' + searchParams.toString());
96101
}
97102

special-pages/pages/new-tab/src/js/mock-transport.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,33 @@ export function mockTransport() {
254254

255255
return () => controller.abort();
256256
}
257+
case 'stats_onDataUpdate': {
258+
const statsVariant = url.searchParams.get('stats');
259+
if (statsVariant !== 'willUpdate') return () => {};
260+
261+
const count = url.searchParams.get('stats-update-count');
262+
const max = Math.min(parseInt(count || '0'), 10);
263+
if (max === 0) return () => {};
264+
265+
let inc = 1;
266+
const int = setInterval(() => {
267+
if (inc === max) return clearInterval(int);
268+
const next = {
269+
...stats.willUpdate,
270+
trackerCompanies: stats.willUpdate.trackerCompanies.map((x, index) => {
271+
return {
272+
...x,
273+
count: x.count + inc * index,
274+
};
275+
}),
276+
};
277+
cb(next);
278+
inc++;
279+
}, 500);
280+
return () => {
281+
clearInterval(int);
282+
};
283+
}
257284
case 'favorites_onConfigUpdate': {
258285
const controller = new AbortController();
259286
channel.addEventListener(
@@ -280,6 +307,10 @@ export function mockTransport() {
280307
const msg = /** @type {any} */ (_msg);
281308
switch (msg.method) {
282309
case 'stats_getData': {
310+
const statsVariant = url.searchParams.get('stats');
311+
if (statsVariant && statsVariant in stats) {
312+
return Promise.resolve(stats[statsVariant]);
313+
}
283314
return Promise.resolve(stats.few);
284315
}
285316
case 'stats_getConfig': {

0 commit comments

Comments
 (0)