-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-11395] [Defect] View Login - TOTP premium badge does nothing when…
… clicked (#10857) * Add MessagingService to LoginCredentialView component. * Add comments. * Add WIP PremiumUpgradeService * Simplify web PremiumUpgradeServices into one service. * Relocate service files. * Add browser version of PremiumUpgradePromptService. * Cleanup debug comments. * Run prettier. * rework promptForPremium to take organization id and add test. * Add test for browser * Rework imports to fix linter errors. * Add Shane's reworked WebVaultPremiumUpgradePromptService.
- Loading branch information
1 parent
1940256
commit 6c1d74a
Showing
10 changed files
with
225 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { TestBed } from "@angular/core/testing"; | ||
import { Router } from "@angular/router"; | ||
import { mock, MockProxy } from "jest-mock-extended"; | ||
|
||
import { BrowserPremiumUpgradePromptService } from "./browser-premium-upgrade-prompt.service"; | ||
|
||
describe("BrowserPremiumUpgradePromptService", () => { | ||
let service: BrowserPremiumUpgradePromptService; | ||
let router: MockProxy<Router>; | ||
|
||
beforeEach(async () => { | ||
router = mock<Router>(); | ||
await TestBed.configureTestingModule({ | ||
providers: [BrowserPremiumUpgradePromptService, { provide: Router, useValue: router }], | ||
}).compileComponents(); | ||
|
||
service = TestBed.inject(BrowserPremiumUpgradePromptService); | ||
}); | ||
|
||
describe("promptForPremium", () => { | ||
it("navigates to the premium update screen", async () => { | ||
await service.promptForPremium(); | ||
expect(router.navigate).toHaveBeenCalledWith(["/premium"]); | ||
}); | ||
}); | ||
}); |
18 changes: 18 additions & 0 deletions
18
apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { inject } from "@angular/core"; | ||
import { Router } from "@angular/router"; | ||
|
||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; | ||
|
||
/** | ||
* This class handles the premium upgrade process for the browser extension. | ||
*/ | ||
export class BrowserPremiumUpgradePromptService implements PremiumUpgradePromptService { | ||
private router = inject(Router); | ||
|
||
async promptForPremium() { | ||
/** | ||
* Navigate to the premium update screen. | ||
*/ | ||
await this.router.navigate(["/premium"]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { DialogRef } from "@angular/cdk/dialog"; | ||
import { TestBed } from "@angular/core/testing"; | ||
import { Router } from "@angular/router"; | ||
import { of, lastValueFrom } from "rxjs"; | ||
|
||
import { OrganizationId } from "@bitwarden/common/types/guid"; | ||
import { DialogService } from "@bitwarden/components"; | ||
|
||
import { | ||
ViewCipherDialogCloseResult, | ||
ViewCipherDialogResult, | ||
} from "../individual-vault/view.component"; | ||
|
||
import { WebVaultPremiumUpgradePromptService } from "./web-premium-upgrade-prompt.service"; | ||
|
||
describe("WebVaultPremiumUpgradePromptService", () => { | ||
let service: WebVaultPremiumUpgradePromptService; | ||
let dialogServiceMock: jest.Mocked<DialogService>; | ||
let routerMock: jest.Mocked<Router>; | ||
let dialogRefMock: jest.Mocked<DialogRef<ViewCipherDialogCloseResult>>; | ||
|
||
beforeEach(() => { | ||
dialogServiceMock = { | ||
openSimpleDialog: jest.fn(), | ||
} as unknown as jest.Mocked<DialogService>; | ||
|
||
routerMock = { | ||
navigate: jest.fn(), | ||
} as unknown as jest.Mocked<Router>; | ||
|
||
dialogRefMock = { | ||
close: jest.fn(), | ||
} as unknown as jest.Mocked<DialogRef<ViewCipherDialogCloseResult>>; | ||
|
||
TestBed.configureTestingModule({ | ||
providers: [ | ||
WebVaultPremiumUpgradePromptService, | ||
{ provide: DialogService, useValue: dialogServiceMock }, | ||
{ provide: Router, useValue: routerMock }, | ||
{ provide: DialogRef, useValue: dialogRefMock }, | ||
], | ||
}); | ||
|
||
service = TestBed.inject(WebVaultPremiumUpgradePromptService); | ||
}); | ||
|
||
it("prompts for premium upgrade and navigates to organization billing if organizationId is provided", async () => { | ||
dialogServiceMock.openSimpleDialog.mockReturnValue(lastValueFrom(of(true))); | ||
const organizationId = "test-org-id" as OrganizationId; | ||
|
||
await service.promptForPremium(organizationId); | ||
|
||
expect(dialogServiceMock.openSimpleDialog).toHaveBeenCalledWith({ | ||
title: { key: "upgradeOrganization" }, | ||
content: { key: "upgradeOrganizationDesc" }, | ||
acceptButtonText: { key: "upgradeOrganization" }, | ||
type: "info", | ||
}); | ||
expect(routerMock.navigate).toHaveBeenCalledWith([ | ||
"organizations", | ||
organizationId, | ||
"billing", | ||
"subscription", | ||
]); | ||
expect(dialogRefMock.close).toHaveBeenCalledWith({ | ||
action: ViewCipherDialogResult.PremiumUpgrade, | ||
}); | ||
}); | ||
|
||
it("prompts for premium upgrade and navigates to premium subscription if organizationId is not provided", async () => { | ||
dialogServiceMock.openSimpleDialog.mockReturnValue(lastValueFrom(of(true))); | ||
|
||
await service.promptForPremium(); | ||
|
||
expect(dialogServiceMock.openSimpleDialog).toHaveBeenCalledWith({ | ||
title: { key: "premiumRequired" }, | ||
content: { key: "premiumRequiredDesc" }, | ||
acceptButtonText: { key: "upgrade" }, | ||
type: "success", | ||
}); | ||
expect(routerMock.navigate).toHaveBeenCalledWith(["settings/subscription/premium"]); | ||
expect(dialogRefMock.close).toHaveBeenCalledWith({ | ||
action: ViewCipherDialogResult.PremiumUpgrade, | ||
}); | ||
}); | ||
|
||
it("does not navigate or close dialog if upgrade is no action is taken", async () => { | ||
dialogServiceMock.openSimpleDialog.mockReturnValue(lastValueFrom(of(false))); | ||
|
||
await service.promptForPremium("test-org-id" as OrganizationId); | ||
|
||
expect(routerMock.navigate).not.toHaveBeenCalled(); | ||
expect(dialogRefMock.close).not.toHaveBeenCalled(); | ||
}); | ||
}); |
57 changes: 57 additions & 0 deletions
57
apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { DialogRef } from "@angular/cdk/dialog"; | ||
import { Injectable } from "@angular/core"; | ||
import { Router } from "@angular/router"; | ||
|
||
import { OrganizationId } from "@bitwarden/common/types/guid"; | ||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; | ||
import { DialogService } from "@bitwarden/components"; | ||
|
||
import { | ||
ViewCipherDialogCloseResult, | ||
ViewCipherDialogResult, | ||
} from "../individual-vault/view.component"; | ||
|
||
/** | ||
* This service is used to prompt the user to upgrade to premium. | ||
*/ | ||
@Injectable() | ||
export class WebVaultPremiumUpgradePromptService implements PremiumUpgradePromptService { | ||
constructor( | ||
private dialogService: DialogService, | ||
private router: Router, | ||
private dialog: DialogRef<ViewCipherDialogCloseResult>, | ||
) {} | ||
|
||
/** | ||
* Prompts the user to upgrade to premium. | ||
* @param organizationId The ID of the organization to upgrade. | ||
*/ | ||
async promptForPremium(organizationId?: OrganizationId) { | ||
let upgradeConfirmed; | ||
if (organizationId) { | ||
upgradeConfirmed = await this.dialogService.openSimpleDialog({ | ||
title: { key: "upgradeOrganization" }, | ||
content: { key: "upgradeOrganizationDesc" }, | ||
acceptButtonText: { key: "upgradeOrganization" }, | ||
type: "info", | ||
}); | ||
if (upgradeConfirmed) { | ||
await this.router.navigate(["organizations", organizationId, "billing", "subscription"]); | ||
} | ||
} else { | ||
upgradeConfirmed = await this.dialogService.openSimpleDialog({ | ||
title: { key: "premiumRequired" }, | ||
content: { key: "premiumRequiredDesc" }, | ||
acceptButtonText: { key: "upgrade" }, | ||
type: "success", | ||
}); | ||
if (upgradeConfirmed) { | ||
await this.router.navigate(["settings/subscription/premium"]); | ||
} | ||
} | ||
|
||
if (upgradeConfirmed) { | ||
this.dialog.close({ action: ViewCipherDialogResult.PremiumUpgrade }); | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
libs/common/src/vault/abstractions/premium-upgrade-prompt.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* This interface defines the a contract for a service that prompts the user to upgrade to premium. | ||
* It ensures that PremiumUpgradePromptService contains a promptForPremium method. | ||
*/ | ||
export abstract class PremiumUpgradePromptService { | ||
abstract promptForPremium(organizationId?: string): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters