• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mozilla / fx-private-relay / 602c9b6c-cda5-4312-b549-85064b5bc164

23 Oct 2025 07:30PM UTC coverage: 88.796% (-0.008%) from 88.804%
602c9b6c-cda5-4312-b549-85064b5bc164

Pull #5992

circleci

jwhitlock
fix: Update dev e2e to use MozCloud dev
Pull Request #5992: fix: Update dev e2e to use MozCloud dev

2917 of 3937 branches covered (74.09%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 3 files covered. (100.0%)

7 existing lines in 3 files now uncovered.

18132 of 19768 relevant lines covered (91.72%)

11.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.02
/frontend/src/components/dashboard/aliases/Alias.tsx
1
import { useRef, useState, ReactNode } from "react";
4✔
2
import { useToggleState, useTooltipTriggerState } from "react-stately";
4✔
3
import {
4
  mergeProps,
5
  useToggleButton,
6
  useTooltip,
7
  useTooltipTrigger,
8
} from "react-aria";
4✔
9
import styles from "./Alias.module.scss";
4✔
10
import { ArrowDownIcon, CopyIcon, HideIcon } from "../../Icons";
4✔
11
import IllustrationHoliday from "../../../../public/illustrations/holiday.svg";
4✔
12
import IllustrationLibrary from "../../../../public/illustrations/library.svg";
4✔
13
import {
14
  AliasData,
15
  getFullAddress,
16
  isBlockingLevelOneTrackers,
17
} from "../../../hooks/api/aliases";
4✔
18
import { LabelEditor } from "./LabelEditor";
4✔
19
import { UserData } from "../../../hooks/api/user";
20
import { ProfileData } from "../../../hooks/api/profile";
21
import { renderDate } from "../../../functions/renderDate";
4✔
22
import { AliasDeletionButton } from "./AliasDeletionButton";
4✔
23
import { getRuntimeConfig } from "../../../config";
4✔
24
import { getLocale } from "../../../functions/getLocale";
4✔
25
import { BlockLevel, BlockLevelSlider } from "./BlockLevelSlider";
4✔
26
import { RuntimeData } from "../../../hooks/api/types";
27
import { isFlagActive } from "../../../functions/waffle";
4✔
28
import { isPeriodicalPremiumAvailableInCountry } from "../../../functions/getPlan";
4✔
29
import { useL10n } from "../../../hooks/l10n";
4✔
30
import { Localized } from "../../Localized";
4✔
31

32
// Deprecated, flag "mask_redesign" is in use and uses <MaskCard/> instead of <Alias/>
33

34
export type Props = {
35
  alias: AliasData;
36
  user: UserData;
37
  profile: ProfileData;
38
  onUpdate: (updatedFields: Partial<AliasData>) => void;
39
  onDelete: () => void;
40
  isOpen: boolean;
41
  onChangeOpen: (isOpen: boolean) => void;
42
  showLabelEditor?: boolean;
43
  runtimeData?: RuntimeData;
44
};
45

46
/**
47
 * A card to manage (toggle it on/off, view details, ...) a single alias.
48
 */
49
export const Alias = (props: Props) => {
187✔
50
  const l10n = useL10n();
176✔
51
  const [justCopied, setJustCopied] = useState(false);
176✔
52

53
  const expandButtonRef = useRef<HTMLButtonElement>(null);
176✔
54
  const expandButtonState = useToggleState({
176✔
55
    isSelected: props.isOpen === true,
56
    onChange: props.onChangeOpen,
57
  });
58
  const expandButtonProps = useToggleButton(
176✔
59
    {},
60
    expandButtonState,
61
    expandButtonRef,
62
  ).buttonProps;
63

64
  const address = getFullAddress(props.alias);
176✔
65

66
  const copyAddressToClipboard = () => {
176✔
67
    navigator.clipboard.writeText(address);
1✔
68
    setJustCopied(true);
1✔
69
    setTimeout(() => {
1✔
UNCOV
70
      setJustCopied(false);
×
71
    }, 1 * 1000);
72
  };
73

74
  const labelEditor = props.showLabelEditor ? (
176✔
75
    <div className={styles["label-editor-wrapper"]}>
76
      <LabelEditor
77
        label={props.alias.description}
78
        onSubmit={(newLabel) => props.onUpdate({ description: newLabel })}
4✔
79
      />
80
    </div>
81
  ) : null;
82

83
  let backgroundImage = undefined;
176✔
84
  if (
176✔
85
    [
86
      "Holiday",
87
      "Holidays",
88
      "Vakantie",
89
      "Urlaub",
90
      "Ferien",
91
      "Vacances",
92
    ].includes(props.alias.description)
93
  ) {
94
    backgroundImage = IllustrationHoliday.src;
11✔
95
  }
96
  if (
176!
97
    [
98
      "Library",
99
      "Bibliotheek",
100
      "Bibliotek",
101
      "Bibliothek",
102
      "Biblioteek",
103
      "Bibliothèque",
104
    ].includes(props.alias.description)
105
  ) {
UNCOV
106
    backgroundImage = IllustrationLibrary.src;
×
107
  }
108

109
  const setBlockLevel = (blockLevel: BlockLevel) => {
176✔
110
    if (blockLevel === "none") {
2!
111
      // The back-end rejects requests trying to set this property for free users:
UNCOV
112
      const blockPromotionals = props.profile.has_premium ? false : undefined;
×
113
      return props.onUpdate({
×
114
        enabled: true,
115
        block_list_emails: blockPromotionals,
116
      });
117
    }
118
    if (blockLevel === "promotional") {
2!
UNCOV
119
      return props.onUpdate({ enabled: true, block_list_emails: true });
×
120
    }
121
    if (blockLevel === "all") {
2!
122
      // The back-end rejects requests trying to set this property for free users:
123
      const blockPromotionals = props.profile.has_premium ? true : undefined;
2✔
124
      return props.onUpdate({
2✔
125
        enabled: false,
126
        block_list_emails: blockPromotionals,
127
      });
128
    }
129
  };
130

131
  const classNames = [
176✔
132
    styles["alias-card"],
133
    props.alias.enabled ? styles["is-enabled"] : styles["is-disabled"],
176!
134
    expandButtonState.isSelected
176!
135
      ? styles["is-expanded"]
136
      : styles["is-collapsed"],
137
    props.alias.block_list_emails
176!
138
      ? styles["is-blocking-promotionals"]
139
      : styles["is-not-blocking-promotionals"],
140
    isBlockingLevelOneTrackers(props.alias, props.profile)
176✔
141
      ? styles["is-removing-trackers"]
142
      : styles["is-not-removing-trackers"],
143
  ].join(" ");
144

145
  return (
146
    <div
147
      data-testid="alias-card"
148
      className={classNames}
149
      style={{
150
        backgroundImage: backgroundImage
176✔
151
          ? `url(${backgroundImage}), none`
152
          : undefined,
153
      }}
154
    >
155
      <div className={styles["main-data"]}>
156
        <div className={styles.controls}>
157
          <TrackerRemovalIndicator
158
            alias={props.alias}
159
            profile={props.profile}
160
          />
161
          {labelEditor}
162
          <span className={styles["copy-controls"]}>
163
            <span className={styles["copy-button-wrapper"]}>
164
              <button
165
                className={styles["copy-button"]}
166
                title={l10n.getString("profile-label-click-to-copy")}
167
                aria-label={l10n.getString("profile-label-click-to-copy-alt", {
168
                  address: address,
169
                })}
170
                onClick={copyAddressToClipboard}
171
              >
172
                <samp className={styles.address}>{address}</samp>
173
                <span className={styles["copy-icon"]}>
174
                  <CopyIcon alt="" />
175
                </span>
176
              </button>
177
              <span
178
                aria-hidden={!justCopied}
179
                className={`${styles["copied-confirmation"]} ${
180
                  justCopied ? styles["is-shown"] : ""
176✔
181
                }`}
182
              >
183
                {l10n.getString("profile-label-copied")}
184
              </span>
185
            </span>
186
          </span>
187
        </div>
188
        <div className={styles["block-level-label-wrapper"]}>
189
          <BlockLevelLabel alias={props.alias} />
190
        </div>
191
        {/* This <Stats> will be hidden on small screens: */}
192
        <Stats
193
          alias={props.alias}
194
          profile={props.profile}
195
          runtimeData={props.runtimeData}
196
        />
197
        <div className={styles["expand-toggle"]}>
198
          <button {...expandButtonProps} ref={expandButtonRef}>
199
            <ArrowDownIcon
200
              alt={l10n.getString(
201
                expandButtonState.isSelected
176!
202
                  ? "profile-details-collapse"
203
                  : "profile-details-expand",
204
              )}
205
              width={16}
206
              height={16}
207
            />
208
          </button>
209
        </div>
210
      </div>
211
      <div className={styles["secondary-data"]}>
212
        {/* This <Stats> will be hidden on large screens: */}
213
        <Stats
214
          alias={props.alias}
215
          profile={props.profile}
216
          runtimeData={props.runtimeData}
217
        />
218
        <div className={styles.row}>
219
          <BlockLevelSlider
220
            alias={props.alias}
221
            onChange={setBlockLevel}
222
            hasPremium={props.profile.has_premium}
223
            premiumAvailableInCountry={isPeriodicalPremiumAvailableInCountry(
224
              props.runtimeData,
225
            )}
226
          />
227
        </div>
228
        <div className={styles.row}>
229
          <dl>
230
            <div className={`${styles["forward-target"]} ${styles.metadata}`}>
231
              <dt>{l10n.getString("profile-label-forward-emails")}</dt>
232
              <dd>{props.user.email}</dd>
233
            </div>
234
            <div className={`${styles["date-created"]} ${styles.metadata}`}>
235
              <dt>{l10n.getString("profile-label-created")}</dt>
236
              <dd>{renderDate(props.alias.created_at, l10n)}</dd>
237
            </div>
238
          </dl>
239
          <AliasDeletionButton onDelete={props.onDelete} alias={props.alias} />
240
        </div>
241
      </div>
242
    </div>
243
  );
244
};
245

246
type StatsProps = {
247
  alias: AliasData;
248
  profile: ProfileData;
249
  runtimeData?: RuntimeData;
250
};
251
const Stats = (props: StatsProps) => {
4✔
252
  const l10n = useL10n();
350✔
253
  const numberFormatter = new Intl.NumberFormat(getLocale(l10n), {
350✔
254
    notation: "compact",
255
    compactDisplay: "short",
256
  });
257

258
  return (
259
    <div className={styles["alias-stats"]}>
260
      <BlockedTooltip>
261
        <span className={styles.number}>
262
          {numberFormatter.format(props.alias.num_blocked)}
263
        </span>
264
        <span className={styles.label}>
265
          {l10n.getString("profile-label-blocked")}
266
        </span>
267
      </BlockedTooltip>
268
      <ForwardedTooltip>
269
        <span className={styles.number}>
270
          {numberFormatter.format(props.alias.num_forwarded)}
271
        </span>
272
        <span className={styles.label}>
273
          {l10n.getString("profile-label-forwarded")}
274
        </span>
275
      </ForwardedTooltip>
276

277
      {/* If user is not premium, hide the replies count */}
278
      {props.profile.has_premium && (
350✔
279
        <RepliesTooltip>
280
          <span className={styles.number}>
281
            {numberFormatter.format(props.alias.num_replied)}
282
          </span>
283
          <span className={styles.label}>
284
            {l10n.getString("profile-label-replies")}
285
          </span>
286
        </RepliesTooltip>
287
      )}
288

289
      {/*
290
        If the back-end does not yet support providing tracker blocking stats,
291
        hide the blocked trackers count:
292
       */}
293
      {isFlagActive(props.runtimeData, "tracker_removal") &&
370✔
294
        typeof props.alias.num_level_one_trackers_blocked === "number" && (
295
          <TrackersRemovedTooltip>
296
            <span className={styles.number}>
297
              {numberFormatter.format(
298
                props.alias.num_level_one_trackers_blocked,
299
              )}
300
            </span>
301
            <span className={styles.label}>
302
              {l10n.getString("profile-label-trackers-removed")}
303
            </span>
304
          </TrackersRemovedTooltip>
305
        )}
306
    </div>
307
  );
308
};
309

310
type TooltipProps = {
311
  children: ReactNode;
312
};
313
const ForwardedTooltip = (props: TooltipProps) => {
4✔
314
  const l10n = useL10n();
350✔
315
  const triggerState = useTooltipTriggerState({ delay: 0 });
350✔
316
  const triggerRef = useRef<HTMLSpanElement>(null);
350✔
317
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
350✔
318

319
  const { tooltipProps } = useTooltip({}, triggerState);
350✔
320

321
  return (
322
    <div className={styles["stat-wrapper"]}>
323
      <span
324
        ref={triggerRef}
325
        {...tooltipTrigger.triggerProps}
326
        className={`${styles.stat} ${styles["forwarded-stat"]}`}
327
      >
328
        {props.children}
329
      </span>
330
      {triggerState.isOpen && (
350✔
331
        <div
332
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
333
          className={styles.tooltip}
334
        >
335
          <p>
336
            <span>{l10n.getString("profile-forwarded-copy-2")}</span>
337
          </p>
338
          <p>
339
            <strong>{l10n.getString("profile-forwarded-note")}</strong>&nbsp;
340
            <span>
341
              {l10n.getString("profile-forwarded-note-copy", {
342
                size: getRuntimeConfig().emailSizeLimitNumber,
343
                unit: getRuntimeConfig().emailSizeLimitUnit,
344
              })}
345
            </span>
346
          </p>
347
        </div>
348
      )}
349
    </div>
350
  );
351
};
352

353
const BlockedTooltip = (props: TooltipProps) => {
4✔
354
  const l10n = useL10n();
350✔
355
  const triggerState = useTooltipTriggerState({ delay: 0 });
350✔
356
  const triggerRef = useRef<HTMLSpanElement>(null);
350✔
357
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
350✔
358

359
  const { tooltipProps } = useTooltip({}, triggerState);
350✔
360
  return (
361
    <div className={styles["stat-wrapper"]}>
362
      <span
363
        ref={triggerRef}
364
        {...tooltipTrigger.triggerProps}
365
        className={`${styles.stat} ${styles["blocked-stat"]}`}
366
      >
367
        {props.children}
368
      </span>
369
      {triggerState.isOpen && (
350✔
370
        <div
371
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
372
          className={styles.tooltip}
373
        >
374
          {l10n.getString("profile-blocked-copy-2")}
375
        </div>
376
      )}
377
    </div>
378
  );
379
};
380

381
const TrackersRemovedTooltip = (props: TooltipProps) => {
4✔
382
  const l10n = useL10n();
20✔
383
  const triggerState = useTooltipTriggerState({ delay: 0 });
20✔
384
  const triggerRef = useRef<HTMLSpanElement>(null);
20✔
385
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
20✔
386

387
  const { tooltipProps } = useTooltip({}, triggerState);
20✔
388
  return (
389
    <div className={styles["stat-wrapper"]}>
390
      <span
391
        ref={triggerRef}
392
        {...tooltipTrigger.triggerProps}
393
        className={`${styles.stat} ${styles["trackers-removed-stat"]}`}
394
      >
395
        {props.children}
396
      </span>
397
      {triggerState.isOpen && (
20✔
398
        <div
399
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
400
          className={styles.tooltip}
401
        >
402
          <p>{l10n.getString("profile-trackers-removed-tooltip-part1")}</p>
403
          <Localized
404
            id="profile-trackers-removed-tooltip-part2-2"
405
            elems={{
406
              b: <b />,
407
            }}
408
          >
409
            <p />
410
          </Localized>
411
        </div>
412
      )}
413
    </div>
414
  );
415
};
416

417
const RepliesTooltip = (props: TooltipProps) => {
4✔
418
  const l10n = useL10n();
112✔
419
  const triggerState = useTooltipTriggerState({ delay: 0 });
112✔
420
  const triggerRef = useRef<HTMLSpanElement>(null);
112✔
421
  const tooltipTrigger = useTooltipTrigger({}, triggerState, triggerRef);
112✔
422

423
  const { tooltipProps } = useTooltip({}, triggerState);
112✔
424

425
  return (
426
    <div className={styles["stat-wrapper"]}>
427
      <span
428
        ref={triggerRef}
429
        {...tooltipTrigger.triggerProps}
430
        className={`${styles.stat} ${styles["replies-stat"]}`}
431
      >
432
        {props.children}
433
      </span>
434
      {triggerState.isOpen && (
112✔
435
        <div
436
          {...mergeProps(tooltipTrigger.tooltipProps, tooltipProps)}
437
          className={styles.tooltip}
438
        >
439
          {l10n.getString("profile-replies-tooltip")}
440
        </div>
441
      )}
442
    </div>
443
  );
444
};
445

446
type BlockLevelLabelProps = {
447
  alias: AliasData;
448
};
449
const BlockLevelLabel = (props: BlockLevelLabelProps) => {
4✔
450
  const l10n = useL10n();
175✔
451
  if (props.alias.enabled === false) {
175!
452
    return (
453
      <b
454
        className={`${styles["block-level-label"]} ${styles["block-level-all-label"]}`}
455
      >
456
        {l10n.getString("profile-promo-email-blocking-label-none")}
457
      </b>
458
    );
459
  }
460

461
  if (props.alias.block_list_emails === true) {
175!
462
    return (
463
      <b
464
        className={`${styles["block-level-label"]} ${styles["block-level-promotional-label"]}`}
465
      >
466
        {l10n.getString("profile-promo-email-blocking-label-promotionals")}
467
      </b>
468
    );
469
  }
470

471
  return null;
175✔
472
};
473

474
type TrackerRemovalIndicatorProps = {
475
  alias: AliasData;
476
  profile: ProfileData;
477
};
478
const TrackerRemovalIndicator = (props: TrackerRemovalIndicatorProps) => {
4✔
479
  const l10n = useL10n();
175✔
480
  const tooltipState = useTooltipTriggerState({ delay: 0 });
175✔
481
  const triggerRef = useRef<HTMLButtonElement>(null);
175✔
482
  const { triggerProps, tooltipProps: triggerTooltipProps } = useTooltipTrigger(
175✔
483
    {},
484
    tooltipState,
485
    triggerRef,
486
  );
487
  const { tooltipProps } = useTooltip(triggerTooltipProps, tooltipState);
175✔
488

489
  if (!isBlockingLevelOneTrackers(props.alias, props.profile)) {
175✔
490
    return null;
174✔
491
  }
492

493
  return (
494
    <span className={styles["tracker-removal-indicator-wrapper"]}>
495
      <button
496
        ref={triggerRef}
497
        {...triggerProps}
498
        aria-label={l10n.getString("profile-indicator-tracker-removal-alt")}
499
      >
500
        <HideIcon alt="" />
501
      </button>
502
      {tooltipState.isOpen && (
1✔
503
        <span
504
          className={styles["tracker-removal-indicator-tooltip"]}
505
          {...mergeProps(triggerTooltipProps, tooltipProps)}
506
        >
507
          {l10n.getString("profile-indicator-tracker-removal-tooltip")}
508
        </span>
509
      )}
510
    </span>
511
  );
512
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc