Skip to content

Commit f1e8724

Browse files
committed
Adapt Activity widget for ads and trackers
- Updates UI text, icons, and styles based on whether the browser blocks just trackers or ads and trackers. - Introduces a `trackerType` config option ('trackersOnly' or 'adsAndTrackers') to control the display. - Adds new translation strings and a green shield icon for the ads and trackers variant. - Updates tests and mock data accordingly.
1 parent 627b5e4 commit f1e8724

File tree

14 files changed

+261
-32
lines changed

14 files changed

+261
-32
lines changed

special-pages/pages/new-tab/app/activity/ActivityProvider.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createContext, h } from 'preact';
2-
import { useEffect, useReducer, useRef } from 'preact/hooks';
2+
import { useContext, useEffect, useReducer, useRef } from 'preact/hooks';
33
import { useMessaging } from '../types.js';
44
import { reducer, useConfigSubscription, useInitialDataAndConfig } from '../service.hooks.js';
55
import { useBatchedActivityApi } from '../settings.provider.js';
@@ -77,3 +77,11 @@ export function useService(useBatched) {
7777
}, [ntp, useBatched]);
7878
return service;
7979
}
80+
81+
/**
82+
* @return {'adsAndTrackers'|'trackersOnly'}
83+
*/
84+
export function useTrackerType() {
85+
const { state } = useContext(ActivityContext);
86+
return state.config?.trackerType === 'adsAndTrackers' ? 'adsAndTrackers' : 'trackersOnly';
87+
}

special-pages/pages/new-tab/app/activity/components/Activity.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Fragment, h } from 'preact';
22
import styles from './Activity.module.css';
33
import { useContext, useEffect, useId, useRef } from 'preact/hooks';
44
import { memo } from 'preact/compat';
5-
import { ActivityContext, ActivityProvider, ActivityServiceContext } from '../ActivityProvider.js';
5+
import { ActivityContext, ActivityProvider, ActivityServiceContext, useTrackerType } from '../ActivityProvider.js';
66
import { useTypedTranslationWith } from '../../types.js';
77
import { useVisibility } from '../../widget-list/widget-config.provider.js';
88
import { useOnMiddleClick } from '../../utils.js';
@@ -43,6 +43,7 @@ export function Activity({ expansion, toggle, trackerCount, itemCount, batched,
4343
const WIDGET_ID = useId();
4444
const TOGGLE_ID = useId();
4545
const { didClick } = useContext(ActivityInteractionsContext);
46+
const trackerType = useTrackerType();
4647

4748
const ref = useRef(null);
4849
useOnMiddleClick(ref, didClick);
@@ -52,6 +53,7 @@ export function Activity({ expansion, toggle, trackerCount, itemCount, batched,
5253
<div class={styles.root} onClick={didClick} ref={ref}>
5354
<ActivityHeading
5455
trackerCount={trackerCount}
56+
trackerType={trackerType}
5557
itemCount={itemCount}
5658
onToggle={toggle}
5759
expansion={expansion}
@@ -194,6 +196,7 @@ function TrackerStatus({ id, trackersFound }) {
194196
const status = useComputed(() => activity.value.trackingStatus[id]);
195197
const other = status.value.trackerCompanies.slice(DDG_MAX_TRACKER_ICONS - 1);
196198
const companyIconsMax = other.length === 0 ? DDG_MAX_TRACKER_ICONS : DDG_MAX_TRACKER_ICONS - 1;
199+
const trackerType = useTrackerType();
197200

198201
const icons = status.value.trackerCompanies.slice(0, companyIconsMax).map((item, _index) => {
199202
return <CompanyIcon displayName={item.displayName} key={item} />;
@@ -210,10 +213,12 @@ function TrackerStatus({ id, trackersFound }) {
210213
}
211214

212215
if (status.value.totalCount === 0) {
213-
// prettier-ignore
214-
const text = trackersFound
215-
? t('activity_no_trackers_blocked')
216-
: t('activity_no_trackers')
216+
let text;
217+
if (trackersFound) {
218+
text = trackerType === 'trackersOnly' ? t('activity_no_trackers_blocked') : t('activity_no_adsAndTrackers_blocked');
219+
} else {
220+
text = trackerType === 'trackersOnly' ? t('activity_no_trackers') : t('activity_no_adsAndTrackers');
221+
}
217222
return (
218223
<p class={styles.companiesIconRow} data-testid="TrackerStatus">
219224
{text}
@@ -228,7 +233,11 @@ function TrackerStatus({ id, trackersFound }) {
228233
{otherIcon}
229234
</div>
230235
<div class={styles.companiesText}>
231-
<Trans str={t('activity_countBlockedPlural', { count: String(status.value.totalCount) })} values={{}} />
236+
{trackerType === 'trackersOnly' ? (
237+
<Trans str={t('activity_countBlockedPlural', { count: String(status.value.totalCount) })} values={{}} />
238+
) : (
239+
<Trans str={t('activity_countBlockedAdsAndTrackersPlural', { count: String(status.value.totalCount) })} values={{}} />
240+
)}
232241
</div>
233242
</div>
234243
);

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

Lines changed: 91 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,24 @@ export class ActivityPage {
293293
await page.pause();
294294
}
295295

296-
async showsEmptyTrackerState() {
296+
async showsTrackersOnlyTrackerStates() {
297+
await expect(this.context().getByTestId('ActivityItem').nth(0)).toMatchAriaSnapshot(`
298+
- listitem:
299+
- link "example.com"
300+
- button "Add example.com to favorites":
301+
- img
302+
- button "Clear browsing history and data for example.com":
303+
- img
304+
- text: +1 56 tracking attempts blocked
305+
- list:
306+
- listitem:
307+
- link "/bathrooms/toilets"
308+
- text: Just now
309+
- listitem:
310+
- link "/kitchen/sinks"
311+
- text: 50 mins ago
312+
`);
313+
297314
await expect(this.context().getByTestId('ActivityItem').nth(3)).toMatchAriaSnapshot(`
298315
- listitem:
299316
- link "twitter.com"
@@ -305,21 +322,69 @@ export class ActivityPage {
305322
- list:
306323
- listitem:
307324
- link "Trending Topics"
308-
- text: 2 days ago`);
325+
- text: 2 days ago
326+
`);
309327

310328
await expect(this.context().getByTestId('ActivityItem').nth(4)).toMatchAriaSnapshot(`
311-
- listitem:
312-
- link "app.linkedin.com"
313-
- button "Add app.linkedin.com to favorites":
314-
- img
315-
- button "Clear browsing history and data for app.linkedin.com":
316-
- img
317-
- paragraph: No trackers found
318-
- list:
319-
- listitem:
320-
- link "Profile Page"
321-
- text: 2 hrs ago
329+
- listitem:
330+
- link "app.linkedin.com"
331+
- button "Add app.linkedin.com to favorites":
332+
- img
333+
- button "Clear browsing history and data for app.linkedin.com":
334+
- img
335+
- paragraph: No trackers found
336+
- list:
337+
- listitem:
338+
- link "Profile Page"
339+
- text: 2 hrs ago
340+
`);
341+
}
342+
343+
async showsAdsAndTrackersTrackerStates() {
344+
await expect(this.context().getByTestId('ActivityItem').nth(0)).toMatchAriaSnapshot(`
345+
- listitem:
346+
- link "example.com"
347+
- button "Add example.com to favorites":
348+
- img
349+
- button "Clear browsing history and data for example.com":
350+
- img
351+
- text: +1 56 ads + Tracking attempts blocked
352+
- list:
353+
- listitem:
354+
- link "/bathrooms/toilets"
355+
- text: Just now
356+
- listitem:
357+
- link "/kitchen/sinks"
358+
- text: 50 mins ago
359+
`);
360+
361+
await expect(this.context().getByTestId('ActivityItem').nth(3)).toMatchAriaSnapshot(`
362+
- listitem:
363+
- link "twitter.com"
364+
- button "Add twitter.com to favorites":
365+
- img
366+
- button "Clear browsing history and data for twitter.com":
367+
- img
368+
- paragraph: No ads + Tracking attempts blocked
369+
- list:
370+
- listitem:
371+
- link "Trending Topics"
372+
- text: 2 days ago
322373
`);
374+
375+
await expect(this.context().getByTestId('ActivityItem').nth(4)).toMatchAriaSnapshot(`
376+
- listitem:
377+
- link "app.linkedin.com"
378+
- button "Add app.linkedin.com to favorites":
379+
- img
380+
- button "Clear browsing history and data for app.linkedin.com":
381+
- img
382+
- paragraph: No ads + Tracking attempts found
383+
- list:
384+
- listitem:
385+
- link "Profile Page"
386+
- text: 2 hrs ago
387+
`);
323388
}
324389

325390
async hasEmptyTitle() {
@@ -330,7 +395,8 @@ export class ActivityPage {
330395
- paragraph: Recently visited sites will appear here. Keep browsing to see how many trackers we block.
331396
`);
332397
}
333-
async hasPopuplatedTitle() {
398+
399+
async hasPopulatedTrackersOnlyTitle() {
334400
const { page } = this;
335401
await expect(page.getByTestId('ActivityHeading')).toMatchAriaSnapshot(`
336402
- img "Privacy Shield"
@@ -340,4 +406,15 @@ export class ActivityPage {
340406
- paragraph: Past 7 days
341407
`);
342408
}
409+
410+
async hasPopulatedAdsAndTrackersTitle() {
411+
const { page } = this;
412+
await expect(page.getByTestId('ActivityHeading')).toMatchAriaSnapshot(`
413+
- img "Privacy Shield"
414+
- heading "Total of 0 ads & tracking attempts blocked" [level=2]
415+
- button "Hide recent activity" [expanded] [pressed]:
416+
- img
417+
- paragraph: Past 7 days
418+
`);
419+
}
343420
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ test.describe('activity widget', () => {
6161
await ntp.openPage({ additional: { feed: 'activity', activity: 'empty' } });
6262
await ap.hasEmptyTitle();
6363
await ntp.openPage({ additional: { feed: 'activity', activity: 'onlyTopLevel' } });
64-
await ap.hasPopuplatedTitle();
64+
await ap.hasPopulatedTrackersOnlyTitle();
65+
await ntp.openPage({ additional: { feed: 'activity', activity: 'empty', trackerType: 'adsAndTrackers' } });
66+
await ap.hasEmptyTitle();
67+
await ntp.openPage({ additional: { feed: 'activity', activity: 'onlyTopLevel', trackerType: 'adsAndTrackers' } });
68+
await ap.hasPopulatedAdsAndTrackersTitle();
6569
});
6670
test('favorite item', async ({ page }, workerInfo) => {
6771
const ntp = NewtabPage.create(page, workerInfo);
@@ -119,13 +123,16 @@ test.describe('activity widget', () => {
119123
await ap.didRender();
120124
await ap.listsAtMost3TrackerCompanies();
121125
});
122-
test('supported empty trackers states', async ({ page }, workerInfo) => {
126+
test('tracker states', async ({ page }, workerInfo) => {
123127
const ntp = NewtabPage.create(page, workerInfo);
124128
const ap = new ActivityPage(page, ntp);
125129
await ntp.reducedMotion();
126130
await ntp.openPage({ additional: { feed: 'activity' } });
127131
await ap.didRender();
128-
await ap.showsEmptyTrackerState();
132+
await ap.showsTrackersOnlyTrackerStates();
133+
await ntp.openPage({ additional: { feed: 'activity', trackerType: 'adsAndTrackers' } });
134+
await ap.didRender();
135+
await ap.showsAdsAndTrackersTrackerStates();
129136
});
130137
test('after rendering and navigating to a new tab, data is re-requested on return', async ({ page }, workerInfo) => {
131138
const ntp = NewtabPage.create(page, workerInfo);

special-pages/pages/new-tab/app/activity/mocks/activity.mock-transport.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ export function activityMockTransport() {
217217
/** @type {import('../../../types/new-tab.ts').ActivityConfig} */
218218
const config = {
219219
expansion: 'expanded',
220+
trackerType: url.searchParams.get('trackerType') === 'adsAndTrackers' ? 'adsAndTrackers' : 'trackersOnly',
220221
};
221222
return Promise.resolve(config);
222223
}

special-pages/pages/new-tab/app/activity/strings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@
1919
"title": "<b>{count}</b> tracking attempts blocked",
2020
"note": "The main headline indicating that more than 1 attempt has been blocked. Eg: '2 tracking attempts blocked'"
2121
},
22+
"activity_no_adsAndTrackers": {
23+
"title": "No ads + Tracking attempts found",
24+
"note": "Placeholder message indicating that no ads and trackers are detected"
25+
},
26+
"activity_no_adsAndTrackers_blocked": {
27+
"title": "No ads + Tracking attempts blocked",
28+
"note": "Placeholder message indicating that no ads and trackers are blocked"
29+
},
30+
"activity_countBlockedAdsAndTrackersPlural": {
31+
"title": "<b>{count}</b> ads + Tracking attempts blocked",
32+
"note": "The main headline indicating that more than 1 attempt has been blocked. Eg: '2 ads + Tracking attempts blocked'"
33+
},
2234
"activity_favoriteAdd": {
2335
"title": "Add {domain} to favorites",
2436
"note": "Button label, allows the user to add the specified domain to their favorites"

special-pages/pages/new-tab/app/privacy-stats/components/ActivityHeading.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import styles from './PrivacyStats.module.css';
44
import { ShowHideButtonCircle } from '../../components/ShowHideButton.jsx';
55
import cn from 'classnames';
66
import { h } from 'preact';
7+
import { Trans } from '../../../../../shared/components/TranslationsProvider.js';
78

89
/**
910
* @import enStrings from "../strings.json"
@@ -12,28 +13,52 @@ import { h } from 'preact';
1213
* @param {object} props
1314
* @param {import('../../../types/new-tab.js').Expansion} props.expansion
1415
* @param {number} props.trackerCount
16+
* @param {'trackersOnly' | 'adsAndTrackers'} [props.trackerType='trackersOnly']
1517
* @param {number} props.itemCount
1618
* @param {boolean} props.canExpand
1719
* @param {() => void} props.onToggle
1820
* @param {import('preact').ComponentProps<'button'>} [props.buttonAttrs]
1921
*/
20-
export function ActivityHeading({ expansion, canExpand, itemCount, trackerCount, onToggle, buttonAttrs = {} }) {
22+
export function ActivityHeading({
23+
expansion,
24+
canExpand,
25+
itemCount,
26+
trackerCount,
27+
trackerType = 'trackersOnly',
28+
onToggle,
29+
buttonAttrs = {},
30+
}) {
2131
const { t } = useTypedTranslationWith(/** @type {Strings} */ ({}));
2232
const [formatter] = useState(() => new Intl.NumberFormat());
2333

2434
const none = itemCount === 0;
2535
const someItems = itemCount > 0;
2636
const trackerCountFormatted = formatter.format(trackerCount);
27-
const allTimeString =
28-
trackerCount === 1 ? t('stats_countBlockedSingular') : t('stats_countBlockedPlural', { count: trackerCountFormatted });
37+
38+
let allTimeString;
39+
if (trackerCount === 1) {
40+
allTimeString = trackerType === 'trackersOnly' ? t('stats_countBlockedSingular') : t('stats_countBlockedAdsAndTrackersSingular');
41+
} else {
42+
allTimeString =
43+
trackerType === 'trackersOnly'
44+
? t('stats_countBlockedPlural', { count: trackerCountFormatted })
45+
: t('stats_countBlockedAdsAndTrackersPlural', { count: trackerCountFormatted });
46+
}
2947

3048
return (
31-
<div className={cn(styles.heading, styles.activityVariant)} data-testid={'ActivityHeading'}>
49+
<div
50+
className={cn(styles.heading, styles.activityVariant, { [styles.adsAndTrackersVariant]: trackerType === 'adsAndTrackers' })}
51+
data-testid={'ActivityHeading'}
52+
>
3253
<span className={styles.headingIcon}>
33-
<img src="./icons/shield.svg" alt="Privacy Shield" />
54+
<img src={trackerType === 'trackersOnly' ? './icons/shield.svg' : './icons/shield-green.svg'} alt="Privacy Shield" />
3455
</span>
3556
{none && <h2 className={styles.title}>{t('activity_noRecent_title')}</h2>}
36-
{someItems && <h2 className={styles.title}>{allTimeString}</h2>}
57+
{someItems && (
58+
<h2 className={styles.title}>
59+
<Trans str={allTimeString} values={{ count: trackerCountFormatted }} />
60+
</h2>
61+
)}
3762
{canExpand && (
3863
<span className={styles.widgetExpander}>
3964
<ShowHideButtonCircle
@@ -48,7 +73,11 @@ export function ActivityHeading({ expansion, canExpand, itemCount, trackerCount,
4873
</span>
4974
)}
5075
{itemCount === 0 && <p className={styles.subtitle}>{t('activity_noRecent_subtitle')}</p>}
51-
{itemCount > 0 && <p className={cn(styles.subtitle, styles.uppercase)}>{t('stats_feedCountBlockedPeriod')}</p>}
76+
{itemCount > 0 && (
77+
<p className={cn(styles.subtitle, { [styles.uppercase]: trackerType === 'trackersOnly' })}>
78+
{t('stats_feedCountBlockedPeriod')}
79+
</p>
80+
)}
5281
</div>
5382
);
5483
}

0 commit comments

Comments
 (0)