From 4ce6df304097f69d719814a0bc01495031eae391 Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Wed, 9 Oct 2024 04:28:39 -0700
Subject: [PATCH] [PM-11926] - fix send created page text (#11467)
* fix send created page
* remove unused i18n key
* remove superfluous spec
* fix failing tests
---
apps/browser/src/_locales/en/messages.json | 20 +++-
.../send-created/send-created.component.html | 4 +-
.../send-created.component.spec.ts | 106 +++++++++++++-----
.../send-created/send-created.component.ts | 19 +++-
4 files changed, 117 insertions(+), 32 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 7b30e5aa33..e52f78583d 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2498,7 +2498,25 @@
"message": "Send created successfully!",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
- "sendAvailability": {
+ "sendExpiresInHoursSingle": {
+ "message": "The Send will be available to anyone with the link for the next 1 hour.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
+ },
+ "sendExpiresInHours": {
+ "message": "The Send will be available to anyone with the link for the next $HOURS$ hours.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
+ "placeholders": {
+ "hours": {
+ "content": "$1",
+ "example": "5"
+ }
+ }
+ },
+ "sendExpiresInDaysSingle": {
+ "message": "The Send will be available to anyone with the link for the next 1 day.",
+ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
+ },
+ "sendExpiresInDays": {
"message": "The Send will be available to anyone with the link for the next $DAYS$ days.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
"placeholders": {
diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html
index c97d3da139..7c65cbeb17 100644
--- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html
+++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html
@@ -16,7 +16,9 @@
>
{{ "createdSendSuccessfully" | i18n }}
- {{ "sendAvailability" | i18n: daysAvailable }}
+
+ {{ formatExpirationDate() }}
+
{{ "copyLink" | i18n }}
diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts
index bcc4d2e2cc..24186ad427 100644
--- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts
+++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.spec.ts
@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing";
import { MockProxy, mock } from "jest-mock-extended";
-import { of } from "rxjs";
+import { BehaviorSubject, of } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
@@ -13,7 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
-import { ButtonModule, IconModule, ToastService } from "@bitwarden/components";
+import { ButtonModule, I18nMockService, IconModule, ToastService } from "@bitwarden/components";
import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component";
import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component";
@@ -26,7 +26,6 @@ import { SendCreatedComponent } from "./send-created.component";
describe("SendCreatedComponent", () => {
let component: SendCreatedComponent;
let fixture: ComponentFixture;
- let i18nService: MockProxy;
let platformUtilsService: MockProxy;
let sendService: MockProxy;
let toastService: MockProxy;
@@ -36,17 +35,10 @@ describe("SendCreatedComponent", () => {
let router: MockProxy;
const sendId = "test-send-id";
- const deletionDate = new Date();
- deletionDate.setDate(deletionDate.getDate() + 7);
- const sendView: SendView = {
- id: sendId,
- deletionDate,
- accessId: "abc",
- urlB64Key: "123",
- } as SendView;
+ let sendView: SendView;
+ let sendViewsSubject: BehaviorSubject;
beforeEach(async () => {
- i18nService = mock();
platformUtilsService = mock();
sendService = mock();
toastService = mock();
@@ -54,6 +46,17 @@ describe("SendCreatedComponent", () => {
activatedRoute = mock();
environmentService = mock();
router = mock();
+
+ sendView = {
+ id: sendId,
+ deletionDate: new Date(),
+ accessId: "abc",
+ urlB64Key: "123",
+ } as SendView;
+
+ sendViewsSubject = new BehaviorSubject([sendView]);
+ sendService.sendViews$ = sendViewsSubject.asObservable();
+
Object.defineProperty(environmentService, "environment$", {
configurable: true,
get: () => of(new SelfHostedEnvironment({ webVault: "https://example.com" })),
@@ -65,8 +68,6 @@ describe("SendCreatedComponent", () => {
},
} as any;
- sendService.sendViews$ = of([sendView]);
-
await TestBed.configureTestingModule({
imports: [
CommonModule,
@@ -82,7 +83,25 @@ describe("SendCreatedComponent", () => {
SendCreatedComponent,
],
providers: [
- { provide: I18nService, useValue: i18nService },
+ {
+ provide: I18nService,
+ useFactory: () => {
+ return new I18nMockService({
+ back: "back",
+ loading: "loading",
+ copyLink: "copyLink",
+ close: "close",
+ createdSend: "createdSend",
+ createdSendSuccessfully: "createdSendSuccessfully",
+ popOutNewWindow: "popOutNewWindow",
+ sendExpiresInHours: (hours) => `sendExpiresInHours ${hours}`,
+ sendExpiresInHoursSingle: "sendExpiresInHoursSingle",
+ sendExpiresInDays: (days) => `sendExpiresInDays ${days}`,
+ sendExpiresInDaysSingle: "sendExpiresInDaysSingle",
+ sendLinkCopied: "sendLinkCopied",
+ });
+ },
+ },
{ provide: PlatformUtilsService, useValue: platformUtilsService },
{ provide: SendService, useValue: sendService },
{ provide: ToastService, useValue: toastService },
@@ -94,40 +113,73 @@ describe("SendCreatedComponent", () => {
{ provide: Router, useValue: router },
],
}).compileComponents();
- });
- beforeEach(() => {
fixture = TestBed.createComponent(SendCreatedComponent);
component = fixture.componentInstance;
+ fixture.detectChanges();
});
it("should create", () => {
- fixture.detectChanges();
expect(component).toBeTruthy();
});
- it("should initialize send and daysAvailable", () => {
- fixture.detectChanges();
+ it("should initialize send, daysAvailable, and hoursAvailable", () => {
expect(component["send"]).toBe(sendView);
- expect(component["daysAvailable"]).toBe(7);
+ expect(component["daysAvailable"]).toBe(0);
+ expect(component["hoursAvailable"]).toBe(0);
});
it("should navigate back to send list on close", async () => {
- fixture.detectChanges();
await component.close();
expect(router.navigate).toHaveBeenCalledWith(["/tabs/send"]);
});
- describe("getDaysAvailable", () => {
- it("returns the correct number of days", () => {
+ describe("getHoursAvailable", () => {
+ it("returns the correct number of hours", () => {
+ sendView.deletionDate.setDate(sendView.deletionDate.getDate() + 7);
+ sendViewsSubject.next([sendView]);
fixture.detectChanges();
- expect(component.getDaysAvailable(sendView)).toBe(7);
+
+ expect(component.getHoursAvailable(sendView)).toBeCloseTo(168, 0);
+ });
+ });
+
+ describe("formatExpirationDate", () => {
+ it("returns days plural if expiry is more than 24 hours", () => {
+ sendView.deletionDate.setDate(sendView.deletionDate.getDate() + 7);
+ sendViewsSubject.next([sendView]);
+ fixture.detectChanges();
+
+ expect(component.formatExpirationDate()).toBe("sendExpiresInDays 7");
+ });
+
+ it("returns days singular if expiry is 24 hours", () => {
+ sendView.deletionDate.setDate(sendView.deletionDate.getDate() + 1);
+ sendViewsSubject.next([sendView]);
+ fixture.detectChanges();
+
+ expect(component.formatExpirationDate()).toBe("sendExpiresInDaysSingle");
+ });
+
+ it("returns hours plural if expiry is more than 1 hour but less than 24", () => {
+ sendView.deletionDate.setHours(sendView.deletionDate.getHours() + 2);
+ sendViewsSubject.next([sendView]);
+ fixture.detectChanges();
+
+ expect(component.formatExpirationDate()).toBe("sendExpiresInHours 2");
+ });
+
+ it("returns hours singular if expiry is in 1 hour", () => {
+ sendView.deletionDate.setHours(sendView.deletionDate.getHours() + 1);
+ sendViewsSubject.next([sendView]);
+ fixture.detectChanges();
+
+ expect(component.formatExpirationDate()).toBe("sendExpiresInHoursSingle");
});
});
describe("copyLink", () => {
it("should copy link and show toast", async () => {
- fixture.detectChanges();
const link = "https://example.com/#/send/abc/123";
await component.copyLink();
@@ -136,7 +188,7 @@ describe("SendCreatedComponent", () => {
expect(toastService.showToast).toHaveBeenCalledWith({
variant: "success",
title: null,
- message: i18nService.t("sendLinkCopied"),
+ message: "sendLinkCopied",
});
});
});
diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts
index 4ed4da2f81..ae66d14d3f 100644
--- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts
+++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.ts
@@ -39,6 +39,7 @@ export class SendCreatedComponent {
protected sendCreatedIcon = SendCreatedIcon;
protected send: SendView;
protected daysAvailable = 0;
+ protected hoursAvailable = 0;
constructor(
private i18nService: I18nService,
@@ -54,14 +55,26 @@ export class SendCreatedComponent {
this.sendService.sendViews$.pipe(takeUntilDestroyed()).subscribe((sendViews) => {
this.send = sendViews.find((s) => s.id === sendId);
if (this.send) {
- this.daysAvailable = this.getDaysAvailable(this.send);
+ this.hoursAvailable = this.getHoursAvailable(this.send);
+ this.daysAvailable = Math.ceil(this.hoursAvailable / 24);
}
});
}
- getDaysAvailable(send: SendView): number {
+ formatExpirationDate(): string {
+ if (this.hoursAvailable < 24) {
+ return this.hoursAvailable === 1
+ ? this.i18nService.t("sendExpiresInHoursSingle")
+ : this.i18nService.t("sendExpiresInHours", this.hoursAvailable);
+ }
+ return this.daysAvailable === 1
+ ? this.i18nService.t("sendExpiresInDaysSingle")
+ : this.i18nService.t("sendExpiresInDays", this.daysAvailable);
+ }
+
+ getHoursAvailable(send: SendView): number {
const now = new Date().getTime();
- return Math.max(0, Math.ceil((send.deletionDate.getTime() - now) / (1000 * 60 * 60 * 24)));
+ return Math.max(0, Math.ceil((send.deletionDate.getTime() - now) / (1000 * 60 * 60)));
}
async close() {