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

[PM-31430] Add specific messages for creating password and email protected Sends (#18692)

* [PM-31430] Add specific messages for creating password and email protected Sends

* [PM-31430] Fix tests, one bug in Send success drawer component
This commit is contained in:
Mike Amirault
2026-02-03 11:15:46 -05:00
committed by jaasen-livefront
parent cd3c8d0fe8
commit de29726d79
4 changed files with 195 additions and 6 deletions

View File

@@ -1,6 +1,6 @@
<bit-dialog dialogSize="large" disablePadding="false" background="alt">
<ng-container bitDialogTitle>
<span>{{ dialogTitle() | i18n }}</span>
<span>{{ dialogTitle | i18n }}</span>
</ng-container>
<ng-container bitDialogContent>
<div
@@ -17,7 +17,13 @@
</h3>
<p bitTypography="body1" class="tw-mb-6 tw-max-w-sm">
{{ "sendCreatedDescriptionV2" | i18n: formattedExpirationTime }}
@let translationKey =
send.authType === AuthType.Email
? "sendCreatedDescriptionEmail"
: send.authType === AuthType.Password
? "sendCreatedDescriptionPassword"
: "sendCreatedDescriptionV2";
{{ translationKey | i18n: formattedExpirationTime }}
</p>
<bit-form-field class="tw-w-full tw-max-w-sm tw-mb-4">

View File

@@ -0,0 +1,162 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { AuthType } from "@bitwarden/common/tools/send/types/auth-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import {
DIALOG_DATA,
DialogModule,
I18nMockService,
ToastService,
TypographyModule,
} from "@bitwarden/components";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { SendSuccessDrawerDialogComponent } from "./send-success-drawer-dialog.component";
describe("SendSuccessDrawerDialogComponent", () => {
let fixture: ComponentFixture<SendSuccessDrawerDialogComponent>;
let component: SendSuccessDrawerDialogComponent;
let environmentService: MockProxy<EnvironmentService>;
let platformUtilsService: MockProxy<PlatformUtilsService>;
let toastService: MockProxy<ToastService>;
let sendView: SendView;
// Translation Keys
const newTextSend = "New Text Send";
const newFileSend = "New File Send";
const oneHour = "1 hour";
const oneDay = "1 day";
const sendCreatedSuccessfully = "Send has been created successfully";
const sendCreatedDescriptionV2 = "Send ready to share with anyone";
const sendCreatedDescriptionEmail = "Email-verified Send ready to share";
const sendCreatedDescriptionPassword = "Password-protected Send ready to share";
beforeEach(async () => {
environmentService = mock<EnvironmentService>();
platformUtilsService = mock<PlatformUtilsService>();
toastService = mock<ToastService>();
sendView = {
id: "test-send-id",
authType: AuthType.None,
deletionDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
type: SendType.Text,
accessId: "abc",
urlB64Key: "123",
} as SendView;
Object.defineProperty(environmentService, "environment$", {
configurable: true,
get: () => of(new SelfHostedEnvironment({ webVault: "https://example.com" })),
});
await TestBed.configureTestingModule({
imports: [SharedModule, DialogModule, TypographyModule],
providers: [
{
provide: DIALOG_DATA,
useValue: sendView,
},
{ provide: EnvironmentService, useValue: environmentService },
{
provide: I18nService,
useFactory: () => {
return new I18nMockService({
newTextSend,
newFileSend,
sendCreatedSuccessfully,
sendCreatedDescriptionEmail,
sendCreatedDescriptionPassword,
sendCreatedDescriptionV2,
sendLink: "Send link",
copyLink: "Copy Send Link",
close: "Close",
oneHour,
durationTimeHours: (hours) => `${hours} hours`,
oneDay,
days: (days) => `${days} days`,
loading: "loading",
});
},
},
{ provide: PlatformUtilsService, useValue: platformUtilsService },
{ provide: ToastService, useValue: toastService },
],
}).compileComponents();
fixture = TestBed.createComponent(SendSuccessDrawerDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
it("should have the correct title for text Sends", () => {
sendView.type = SendType.Text;
fixture.detectChanges();
expect(component.dialogTitle).toBe("newTextSend");
});
it("should have the correct title for file Sends", () => {
fixture.componentInstance.send.type = SendType.File;
fixture.detectChanges();
expect(component.dialogTitle).toBe("newFileSend");
});
it("should show the correct message for Sends with an expiration time of one hour from now", () => {
sendView.deletionDate = new Date(Date.now() + 1 * 60 * 60 * 1000);
fixture.detectChanges();
expect(component.formattedExpirationTime).toBe(oneHour);
});
it("should show the correct message for Sends with an expiration time more than an hour but less than a day from now", () => {
const numHours = 8;
sendView.deletionDate = new Date(Date.now() + numHours * 60 * 60 * 1000);
fixture.detectChanges();
expect(component.formattedExpirationTime).toBe(`${numHours} hours`);
});
it("should have the correct title for Sends with an expiration time of one day from now", () => {
sendView.deletionDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
fixture.detectChanges();
expect(component.formattedExpirationTime).toBe(oneDay);
});
it("should have the correct title for Sends with an expiration time of multiple days from now", () => {
const numDays = 3;
sendView.deletionDate = new Date(Date.now() + numDays * 24 * 60 * 60 * 1000);
fixture.detectChanges();
expect(component.formattedExpirationTime).toBe(`${numDays} days`);
});
it("should show the correct message for successfully-created Sends with no authentication", () => {
sendView.authType = AuthType.None;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain(sendCreatedSuccessfully);
expect(fixture.nativeElement.textContent).toContain(sendCreatedDescriptionV2);
});
it("should show the correct message for successfully-created Sends with password authentication", () => {
sendView.authType = AuthType.Password;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain(sendCreatedSuccessfully);
expect(fixture.nativeElement.textContent).toContain(sendCreatedDescriptionPassword);
});
it("should show the correct message for successfully-created Sends with email authentication", () => {
sendView.authType = AuthType.Email;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain(sendCreatedSuccessfully);
expect(fixture.nativeElement.textContent).toContain(sendCreatedDescriptionEmail);
});
});

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Inject, signal, computed } from "@angular/core";
import { Component, ChangeDetectionStrategy, Inject, signal } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { ActiveSendIcon } from "@bitwarden/assets/svg";
@@ -6,6 +6,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { AuthType } from "@bitwarden/common/tools/send/types/auth-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { DIALOG_DATA, DialogModule, ToastService, TypographyModule } from "@bitwarden/components";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
@@ -16,13 +17,13 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared";
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SendSuccessDrawerDialogComponent {
readonly AuthType = AuthType;
readonly sendLink = signal<string>("");
activeSendIcon = ActiveSendIcon;
// Computed property to get the dialog title based on send type
readonly dialogTitle = computed(() => {
get dialogTitle(): string {
return this.send.type === SendType.Text ? "newTextSend" : "newFileSend";
});
}
constructor(
@Inject(DIALOG_DATA) public send: SendView,

View File

@@ -5675,6 +5675,26 @@
}
}
},
"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"
}
}
},
"durationTimeHours": {
"message": "$HOURS$ hours",
"placeholders": {