1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 21:20:27 +00:00

Merge branch 'main' into SM-1570

This commit is contained in:
cd-bitwarden
2025-10-23 12:06:51 -04:00
committed by GitHub
4 changed files with 117 additions and 16 deletions

View File

@@ -103,7 +103,7 @@ describe("InsertAutofillContentService", () => {
delay_between_operations: 20,
},
metadata: {},
autosubmit: null,
autosubmit: [],
savedUrls: ["https://bitwarden.com"],
untrustedIframe: false,
itemType: "login",
@@ -218,28 +218,21 @@ describe("InsertAutofillContentService", () => {
await insertAutofillContentService.fillForm(fillScript);
expect(insertAutofillContentService["userCancelledInsecureUrlAutofill"]).toHaveBeenCalled();
expect(
insertAutofillContentService["userCancelledUntrustedIframeAutofill"],
).toHaveBeenCalled();
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenCalledTimes(3);
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith(
1,
fillScript.script[0],
0,
fillScript.script,
);
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith(
2,
fillScript.script[1],
1,
fillScript.script,
);
expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith(
3,
fillScript.script[2],
2,
fillScript.script,
);
});
});
@@ -623,14 +616,12 @@ describe("InsertAutofillContentService", () => {
});
});
it("will set the `value` attribute of any passed input or textarea elements", () => {
document.body.innerHTML = `<input type="text" id="username" /><textarea id="bio"></textarea>`;
it("will set the `value` attribute of any passed input or textarea elements if the value differs", () => {
document.body.innerHTML = `<input type="text" id="username" value="old" /><textarea id="bio">old</textarea>`;
const value1 = "test";
const value2 = "test2";
const textInputElement = document.getElementById("username") as HTMLInputElement;
textInputElement.value = value1;
const textareaElement = document.getElementById("bio") as HTMLTextAreaElement;
textareaElement.value = value2;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](textInputElement, value1);
@@ -647,6 +638,45 @@ describe("InsertAutofillContentService", () => {
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"],
).toHaveBeenCalledWith(textareaElement, expect.any(Function));
});
it("will NOT set the `value` attribute of any passed input or textarea elements if they already have values matching the passed value", () => {
document.body.innerHTML = `<input type="text" id="username" /><textarea id="bio"></textarea>`;
const value1 = "test";
const value2 = "test2";
const textInputElement = document.getElementById("username") as HTMLInputElement;
textInputElement.value = value1;
const textareaElement = document.getElementById("bio") as HTMLTextAreaElement;
textareaElement.value = value2;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](textInputElement, value1);
expect(textInputElement.value).toBe(value1);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"],
).not.toHaveBeenCalled();
insertAutofillContentService["insertValueIntoField"](textareaElement, value2);
expect(textareaElement.value).toBe(value2);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"],
).not.toHaveBeenCalled();
});
it("skips filling when the field already has the target value", () => {
const value = "test";
document.body.innerHTML = `<input type="text" id="username" value="${value}"/>`;
const element = document.getElementById("username") as FillableFormFieldElement;
jest.spyOn(insertAutofillContentService as any, "handleInsertValueAndTriggerSimulatedEvents");
insertAutofillContentService["insertValueIntoField"](element, value);
expect(
insertAutofillContentService["handleInsertValueAndTriggerSimulatedEvents"],
).not.toHaveBeenCalled();
expect(element.value).toBe(value);
});
});
describe("handleInsertValueAndTriggerSimulatedEvents", () => {

View File

@@ -49,8 +49,9 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
return;
}
const fillActionPromises = fillScript.script.map(this.runFillScriptAction);
await Promise.all(fillActionPromises);
for (let index = 0; index < fillScript.script.length; index++) {
await this.runFillScriptAction(fillScript.script[index], index);
}
}
/**
@@ -189,10 +190,14 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf
const elementCanBeReadonly =
elementIsInputElement(element) || elementIsTextAreaElement(element);
const elementCanBeFilled = elementCanBeReadonly || elementIsSelectElement(element);
const elementValue = (element as HTMLInputElement)?.value || element?.innerText || "";
const elementAlreadyHasTheValue = !!(elementValue?.length && elementValue === value);
if (
!element ||
!value ||
elementAlreadyHasTheValue ||
(elementCanBeReadonly && element.readOnly) ||
(elementCanBeFilled && element.disabled)
) {

View File

@@ -16,6 +16,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga
import { ProductTierType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { DialogRef, DialogService } from "@bitwarden/components";
import { OrganizationBillingClient } from "@bitwarden/web-vault/app/billing/clients";
import {
@@ -37,6 +38,7 @@ describe("OrganizationWarningsService", () => {
let i18nService: MockProxy<I18nService>;
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
let organizationBillingClient: MockProxy<OrganizationBillingClient>;
let platformUtilsService: MockProxy<PlatformUtilsService>;
let router: MockProxy<Router>;
const organization = {
@@ -58,10 +60,13 @@ describe("OrganizationWarningsService", () => {
i18nService = mock<I18nService>();
organizationApiService = mock<OrganizationApiServiceAbstraction>();
organizationBillingClient = mock<OrganizationBillingClient>();
platformUtilsService = mock<PlatformUtilsService>();
router = mock<Router>();
(openChangePlanDialog as jest.Mock).mockReset();
platformUtilsService.isSelfHost.mockReturnValue(false);
i18nService.t.mockImplementation((key: string, ...args: any[]) => {
switch (key) {
case "freeTrialEndPromptCount":
@@ -94,6 +99,7 @@ describe("OrganizationWarningsService", () => {
{ provide: I18nService, useValue: i18nService },
{ provide: OrganizationApiServiceAbstraction, useValue: organizationApiService },
{ provide: OrganizationBillingClient, useValue: organizationBillingClient },
{ provide: PlatformUtilsService, useValue: platformUtilsService },
{ provide: Router, useValue: router },
],
});
@@ -111,6 +117,16 @@ describe("OrganizationWarningsService", () => {
});
});
it("should return null when platform is self-hosted", (done) => {
platformUtilsService.isSelfHost.mockReturnValue(true);
service.getFreeTrialWarning$(organization).subscribe((result) => {
expect(result).toBeNull();
expect(organizationBillingClient.getWarnings).not.toHaveBeenCalled();
done();
});
});
it("should return warning with count message when remaining trial days >= 2", (done) => {
const warning = { remainingTrialDays: 5 };
organizationBillingClient.getWarnings.mockResolvedValue({
@@ -206,6 +222,16 @@ describe("OrganizationWarningsService", () => {
});
});
it("should return null when platform is self-hosted", (done) => {
platformUtilsService.isSelfHost.mockReturnValue(true);
service.getResellerRenewalWarning$(organization).subscribe((result) => {
expect(result).toBeNull();
expect(organizationBillingClient.getWarnings).not.toHaveBeenCalled();
done();
});
});
it("should return upcoming warning with correct type and message", (done) => {
const renewalDate = new Date(2024, 11, 31);
const warning = {
@@ -298,6 +324,16 @@ describe("OrganizationWarningsService", () => {
});
});
it("should return null when platform is self-hosted", (done) => {
platformUtilsService.isSelfHost.mockReturnValue(true);
service.getTaxIdWarning$(organization).subscribe((result) => {
expect(result).toBeNull();
expect(organizationBillingClient.getWarnings).not.toHaveBeenCalled();
done();
});
});
it("should return tax_id_missing type when tax ID is missing", (done) => {
const warning = { type: TaxIdWarningTypes.Missing };
organizationBillingClient.getWarnings.mockResolvedValue({
@@ -427,6 +463,16 @@ describe("OrganizationWarningsService", () => {
});
});
it("should not show dialog when platform is self-hosted", (done) => {
platformUtilsService.isSelfHost.mockReturnValue(true);
service.showInactiveSubscriptionDialog$(organization).subscribe(() => {
expect(dialogService.openSimpleDialog).not.toHaveBeenCalled();
expect(organizationBillingClient.getWarnings).not.toHaveBeenCalled();
done();
});
});
it("should show contact provider dialog for contact_provider resolution", (done) => {
const warning = { resolution: "contact_provider" };
organizationBillingClient.getWarnings.mockResolvedValue({
@@ -570,6 +616,18 @@ describe("OrganizationWarningsService", () => {
});
});
it("should not show dialog when platform is self-hosted", (done) => {
platformUtilsService.isSelfHost.mockReturnValue(true);
service.showSubscribeBeforeFreeTrialEndsDialog$(organization).subscribe({
complete: () => {
expect(organizationApiService.getSubscription).not.toHaveBeenCalled();
expect(organizationBillingClient.getWarnings).not.toHaveBeenCalled();
done();
},
});
});
it("should open trial payment dialog when free trial warning exists", (done) => {
const warning = { remainingTrialDays: 2 };
const subscription = { id: "sub-123" } as OrganizationSubscriptionResponse;

View File

@@ -8,6 +8,7 @@ import {
map,
merge,
Observable,
of,
Subject,
switchMap,
tap,
@@ -17,6 +18,7 @@ import { take } from "rxjs/operators";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { DialogService } from "@bitwarden/components";
import { OrganizationBillingClient } from "@bitwarden/web-vault/app/billing/clients";
@@ -56,6 +58,7 @@ export class OrganizationWarningsService {
private i18nService: I18nService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationBillingClient: OrganizationBillingClient,
private platformUtilsService: PlatformUtilsService,
private router: Router,
) {}
@@ -281,12 +284,17 @@ export class OrganizationWarningsService {
organization: Organization,
extract: (response: OrganizationWarningsResponse) => T | null | undefined,
bypassCache: boolean = false,
): Observable<T | null> =>
this.readThroughWarnings$(organization, bypassCache).pipe(
): Observable<T | null> => {
if (this.platformUtilsService.isSelfHost()) {
return of(null);
}
return this.readThroughWarnings$(organization, bypassCache).pipe(
map((response) => {
const value = extract(response);
return value ? value : null;
}),
take(1),
);
};
}