1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-22179] Redirect user to /setup-extension (#15375)

* add end user feature flag

* add initial setup extension component and route

* redirect users from registration completion to the setup extension page

* add `hideIcon` to anon layout for web
- matches implementation on the browser.

* integrate with anon layout for extension wrapper

* add initial loading state

* conditionally redirect the user upon initialization

* redirect the user to the vault if the extension is installed

* add initial copy for setup-extension page

* add confirmation dialog for skipping the extension installation

* add success state for setup extension page

* only show loggedin toast when end user activation is not enabled.

* add image alt

* lower threshold for polling extension

* close the dialog when linking to the vault

* update party colors

* use the platform specific registration service to to only forward the web registrations to `/setup-extension`

* call `super` rather than `/vault` directly, it could change in the future
This commit is contained in:
Nick Krantz
2025-07-03 06:14:25 -05:00
committed by GitHub
parent cef6a5e8d0
commit ab4af7deed
23 changed files with 603 additions and 14 deletions

View File

@@ -0,0 +1,124 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { By } from "@angular/platform-browser";
import { Router, RouterModule } from "@angular/router";
import { BehaviorSubject } from "rxjs";
import { DeviceType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { WebBrowserInteractionService } from "../../services/web-browser-interaction.service";
import { SetupExtensionComponent } from "./setup-extension.component";
describe("SetupExtensionComponent", () => {
let fixture: ComponentFixture<SetupExtensionComponent>;
let component: SetupExtensionComponent;
const getFeatureFlag = jest.fn().mockResolvedValue(false);
const navigate = jest.fn().mockResolvedValue(true);
const openExtension = jest.fn().mockResolvedValue(true);
const extensionInstalled$ = new BehaviorSubject<boolean | null>(null);
beforeEach(async () => {
navigate.mockClear();
openExtension.mockClear();
getFeatureFlag.mockClear().mockResolvedValue(true);
await TestBed.configureTestingModule({
imports: [SetupExtensionComponent, RouterModule.forRoot([])],
providers: [
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: ConfigService, useValue: { getFeatureFlag } },
{ provide: WebBrowserInteractionService, useValue: { extensionInstalled$, openExtension } },
{ provide: PlatformUtilsService, useValue: { getDevice: () => DeviceType.UnknownBrowser } },
],
}).compileComponents();
fixture = TestBed.createComponent(SetupExtensionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
const router = TestBed.inject(Router);
router.navigate = navigate;
});
it("initially shows the loading spinner", () => {
const spinner = fixture.debugElement.query(By.css("i"));
expect(spinner.nativeElement.title).toBe("loading");
});
it("sets webStoreUrl", () => {
expect(component["webStoreUrl"]).toBe("https://bitwarden.com/download/#downloads-web-browser");
});
describe("initialization", () => {
it("redirects to the vault if the feature flag is disabled", async () => {
Utils.isMobileBrowser = false;
getFeatureFlag.mockResolvedValue(false);
navigate.mockClear();
await component.ngOnInit();
expect(navigate).toHaveBeenCalledWith(["/vault"]);
});
it("redirects to the vault if the user is on a mobile browser", async () => {
Utils.isMobileBrowser = true;
getFeatureFlag.mockResolvedValue(true);
navigate.mockClear();
await component.ngOnInit();
expect(navigate).toHaveBeenCalledWith(["/vault"]);
});
it("does not redirect the user", async () => {
Utils.isMobileBrowser = false;
getFeatureFlag.mockResolvedValue(true);
navigate.mockClear();
await component.ngOnInit();
expect(getFeatureFlag).toHaveBeenCalledWith(FeatureFlag.PM19315EndUserActivationMvp);
expect(navigate).not.toHaveBeenCalled();
});
});
describe("extensionInstalled$", () => {
it("redirects the user to the vault when the first emitted value is true", () => {
extensionInstalled$.next(true);
expect(navigate).toHaveBeenCalledWith(["/vault"]);
});
describe("success state", () => {
beforeEach(() => {
// avoid initial redirect
extensionInstalled$.next(false);
fixture.detectChanges();
extensionInstalled$.next(true);
fixture.detectChanges();
});
it("shows link to the vault", () => {
const successLink = fixture.debugElement.query(By.css("a"));
expect(successLink.nativeElement.href).toContain("/vault");
});
it("shows open extension button", () => {
const openExtensionButton = fixture.debugElement.query(By.css("button"));
openExtensionButton.triggerEventHandler("click");
expect(openExtension).toHaveBeenCalled();
});
});
});
});