1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-18 02:19:18 +00:00

[PM-31620] Browser - Incorrect "Copy link" message when Send is shared with specific people (#18982)

* add existing Send creation messages to browser

* remove unused method and associated tests
This commit is contained in:
John Harrington
2026-02-16 15:54:22 -07:00
committed by GitHub
parent c415beb653
commit 5623568a2f
4 changed files with 116 additions and 28 deletions

View File

@@ -3080,6 +3080,45 @@
}
}
},
"durationTimeHours": {
"message": "$HOURS$ hours",
"placeholders": {
"hours": {
"content": "$1",
"example": "5"
}
}
},
"sendCreatedDescriptionV2": {
"message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
"placeholders": {
"time": {
"content": "$1",
"example": "7 days, 1 hour, 1 day"
}
}
},
"sendCreatedDescriptionPassword": {
"message": "Copy and share this Send link. The Send will be available to anyone with the link and password you set for the next $TIME$.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
"placeholders": {
"time": {
"content": "$1",
"example": "7 days, 1 hour, 1 day"
}
}
},
"sendCreatedDescriptionEmail": {
"message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.",
"placeholders": {
"time": {
"content": "$1",
"example": "7 days, 1 hour, 1 day"
}
}
},
"sendLinkCopied": {
"message": "Send link copied",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."

View File

@@ -20,7 +20,13 @@
{{ "createdSendSuccessfully" | i18n }}
</h3>
<p class="tw-text-center">
{{ formatExpirationDate() }}
@let translationKey =
send.authType === AuthType.Email
? "sendCreatedDescriptionEmail"
: send.authType === AuthType.Password
? "sendCreatedDescriptionPassword"
: "sendCreatedDescriptionV2";
{{ translationKey | i18n: formattedExpirationTime }}
</p>
<button bitButton type="button" buttonType="primary" (click)="copyLink()">
<b>{{ "copyLink" | i18n }}</b>

View File

@@ -13,6 +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 { AuthType } from "@bitwarden/common/tools/send/types/auth-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { ButtonModule, I18nMockService, SvgModule, ToastService } from "@bitwarden/components";
@@ -50,6 +51,7 @@ describe("SendCreatedComponent", () => {
sendView = {
id: sendId,
authType: AuthType.None,
deletionDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
type: SendType.Text,
accessId: "abc",
@@ -101,6 +103,13 @@ describe("SendCreatedComponent", () => {
sendExpiresInDays: (days) => `sendExpiresInDays ${days}`,
sendExpiresInDaysSingle: "sendExpiresInDaysSingle",
sendLinkCopied: "sendLinkCopied",
oneHour: "one hour",
durationTimeHours: (hours) => `${hours} hours`,
oneDay: "one day",
days: (days) => `${days} days`,
sendCreatedDescriptionV2: (time) => `Send ready for ${time}`,
sendCreatedDescriptionPassword: (time) => `Password-protected Send ready for ${time}`,
sendCreatedDescriptionEmail: (time) => `Email-verified Send ready for ${time}`,
});
},
},
@@ -147,37 +156,66 @@ describe("SendCreatedComponent", () => {
});
});
describe("formatExpirationDate", () => {
it("returns days plural if expiry is more than 24 hours", () => {
sendView.deletionDate = new Date(Date.now() + 168 * 60 * 60 * 1000);
describe("formattedExpirationTime", () => {
it("returns formatted time for hours plural", () => {
sendView.deletionDate = new Date(Date.now() + 5 * 60 * 60 * 1000);
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(component.formatExpirationDate()).toBe("sendExpiresInDays 7");
expect(component.formattedExpirationTime).toBe("5 hours");
});
it("returns days singular if expiry is 24 hours", () => {
sendView.deletionDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
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 = new Date(Date.now() + 2 * 60 * 60 * 1000);
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(component.formatExpirationDate()).toBe("sendExpiresInHours 2");
});
it("returns hours singular if expiry is in 1 hour", () => {
it("returns formatted time for hours singular", () => {
sendView.deletionDate = new Date(Date.now() + 1 * 60 * 60 * 1000);
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(component.formatExpirationDate()).toBe("sendExpiresInHoursSingle");
expect(component.formattedExpirationTime).toBe("one hour");
});
it("returns formatted time for days plural", () => {
sendView.deletionDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(component.formattedExpirationTime).toBe("7 days");
});
it("returns formatted time for days singular", () => {
sendView.deletionDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(component.formattedExpirationTime).toBe("one day");
});
});
describe("auth type specific messages", () => {
it("should show the correct message for Sends with no authentication", () => {
sendView.authType = AuthType.None;
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain("createdSendSuccessfully");
expect(fixture.nativeElement.textContent).toContain("Send ready for");
});
it("should show the correct message for Sends with password authentication", () => {
sendView.authType = AuthType.Password;
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain("createdSendSuccessfully");
expect(fixture.nativeElement.textContent).toContain("Password-protected Send ready for");
});
it("should show the correct message for Sends with email authentication", () => {
sendView.authType = AuthType.Email;
sendViewsSubject.next([sendView]);
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain("createdSendSuccessfully");
expect(fixture.nativeElement.textContent).toContain("Email-verified Send ready for");
});
});

View File

@@ -13,6 +13,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { AuthType } from "@bitwarden/common/tools/send/types/auth-type";
import { ButtonModule, SvgModule, ToastService } from "@bitwarden/components";
import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component";
@@ -38,6 +39,7 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page
],
})
export class SendCreatedComponent {
readonly AuthType = AuthType;
protected sendCreatedIcon = ActiveSendIcon;
protected send: SendView;
protected daysAvailable = 0;
@@ -63,15 +65,18 @@ export class SendCreatedComponent {
});
}
formatExpirationDate(): string {
get formattedExpirationTime(): string {
if (!this.send?.deletionDate) {
return "";
}
if (this.hoursAvailable < 24) {
return this.hoursAvailable === 1
? this.i18nService.t("sendExpiresInHoursSingle")
: this.i18nService.t("sendExpiresInHours", String(this.hoursAvailable));
? this.i18nService.t("oneHour").toLowerCase()
: this.i18nService.t("durationTimeHours", String(this.hoursAvailable)).toLowerCase();
}
return this.daysAvailable === 1
? this.i18nService.t("sendExpiresInDaysSingle")
: this.i18nService.t("sendExpiresInDays", String(this.daysAvailable));
? this.i18nService.t("oneDay").toLowerCase()
: this.i18nService.t("days", String(this.daysAvailable)).toLowerCase();
}
getHoursAvailable(send: SendView): number {