Skip to content

Commit

Permalink
Merge branch 'main' into ds/cl-506/angular-18
Browse files Browse the repository at this point in the history
  • Loading branch information
vleague2 authored Dec 3, 2024
2 parents dc574bd + c073e91 commit 762953f
Show file tree
Hide file tree
Showing 44 changed files with 777 additions and 229 deletions.
4 changes: 4 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
"groupName": "macOS/iOS bindings",
"matchPackageNames": ["core-foundation", "security-framework", "security-framework-sys"]
},
{
"groupName": "zbus",
"matchPackageNames": ["zbus", "zbus_polkit"]
},
{
"matchPackageNames": [
"base64-loader",
Expand Down
6 changes: 6 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,12 @@
"enterVerificationCodeApp": {
"message": "Enter the 6 digit verification code from your authenticator app."
},
"authenticationTimeout": {
"message": "Authentication timeout"
},
"authenticationSessionTimedOut": {
"message": "The authentication session timed out. Please restart the login process."
},
"enterVerificationCodeEmail": {
"message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
"placeholders": {
Expand Down
25 changes: 25 additions & 0 deletions apps/browser/src/popup/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
EnvironmentSelectorRouteData,
ExtensionDefaultOverlayPosition,
} from "@bitwarden/angular/auth/components/environment-selector.component";
import { TwoFactorTimeoutComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-expired.component";
import { unauthUiRefreshRedirect } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-redirect";
import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
Expand Down Expand Up @@ -38,6 +39,7 @@ import {
VaultIcon,
LoginDecryptionOptionsComponent,
DevicesIcon,
TwoFactorTimeoutIcon,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";

Expand Down Expand Up @@ -199,6 +201,29 @@ const routes: Routes = [
],
},
),
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,
children: [
{
path: "2fa-timeout",
canActivate: [unauthGuardFn(unauthRouteOverrides)],
children: [
{
path: "",
component: TwoFactorTimeoutComponent,
},
],
data: {
pageTitle: {
key: "authenticationTimeout",
},
pageIcon: TwoFactorTimeoutIcon,
elevation: 1,
} satisfies RouteDataProperties & AnonLayoutWrapperData,
},
],
},
{
path: "2fa-options",
component: TwoFactorOptionsComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@
{{ "new" | i18n }}
</button>
<bit-menu #itemOptions>
<a bitMenuItem (click)="newItemNavigate(cipherType.Login)">
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Login)">
<i class="bwi bwi-globe" slot="start" aria-hidden="true"></i>
{{ "typeLogin" | i18n }}
</a>
<a bitMenuItem (click)="newItemNavigate(cipherType.Card)">
<a bitMenuItem [routerLink]="['/add-cipher']" [queryParams]="buildQueryParams(cipherType.Card)">
<i class="bwi bwi-credit-card" slot="start" aria-hidden="true"></i>
{{ "typeCard" | i18n }}
</a>
<a bitMenuItem (click)="newItemNavigate(cipherType.Identity)">
<a
bitMenuItem
[routerLink]="['/add-cipher']"
[queryParams]="buildQueryParams(cipherType.Identity)"
>
<i class="bwi bwi-id-card" slot="start" aria-hidden="true"></i>
{{ "typeIdentity" | i18n }}
</a>
<a bitMenuItem (click)="newItemNavigate(cipherType.SecureNote)">
<a
bitMenuItem
[routerLink]="['/add-cipher']"
[queryParams]="buildQueryParams(cipherType.SecureNote)"
>
<i class="bwi bwi-sticky-note" slot="start" aria-hidden="true"></i>
{{ "note" | i18n }}
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,141 +1,163 @@
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { Router } from "@angular/router";
import { ActivatedRoute, RouterLink } from "@angular/router";
import { mock } from "jest-mock-extended";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
import { ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
import { ButtonModule, DialogService, MenuModule, NoItemsModule } from "@bitwarden/components";

import { BrowserApi } from "../../../../../platform/browser/browser-api";
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
import { AddEditQueryParams } from "../add-edit/add-edit-v2.component";
import { AddEditFolderDialogComponent } from "../add-edit-folder-dialog/add-edit-folder-dialog.component";

import { NewItemDropdownV2Component, NewItemInitialValues } from "./new-item-dropdown-v2.component";

describe("NewItemDropdownV2Component", () => {
let component: NewItemDropdownV2Component;
let fixture: ComponentFixture<NewItemDropdownV2Component>;
const open = jest.fn();
const navigate = jest.fn();
let dialogServiceMock: jest.Mocked<DialogService>;
let browserApiMock: jest.Mocked<typeof BrowserApi>;

jest
.spyOn(BrowserApi, "getTabFromCurrentWindow")
.mockResolvedValue({ url: "https://example.com" } as chrome.tabs.Tab);
const mockTab = { url: "https://example.com" };

beforeAll(() => {
jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(mockTab as chrome.tabs.Tab);
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false);
jest.spyOn(Utils, "getHostname").mockReturnValue("example.com");
});

beforeEach(async () => {
open.mockClear();
navigate.mockClear();
dialogServiceMock = mock<DialogService>();
dialogServiceMock.open.mockClear();

const activatedRouteMock = {
snapshot: { paramMap: { get: jest.fn() } },
};

const i18nServiceMock = mock<I18nService>();
const folderServiceMock = mock<FolderService>();
const folderApiServiceAbstractionMock = mock<FolderApiServiceAbstraction>();
const accountServiceMock = mock<AccountService>();

await TestBed.configureTestingModule({
imports: [NewItemDropdownV2Component, MenuModule, ButtonModule, JslibModule, CommonModule],
imports: [
CommonModule,
RouterLink,
ButtonModule,
MenuModule,
NoItemsModule,
NewItemDropdownV2Component,
],
providers: [
{ provide: I18nService, useValue: { t: (key: string) => key } },
{ provide: Router, useValue: { navigate } },
{ provide: DialogService, useValue: dialogServiceMock },
{ provide: I18nService, useValue: i18nServiceMock },
{ provide: ActivatedRoute, useValue: activatedRouteMock },
{ provide: BrowserApi, useValue: browserApiMock },
{ provide: FolderService, useValue: folderServiceMock },
{ provide: FolderApiServiceAbstraction, useValue: folderApiServiceAbstractionMock },
{ provide: AccountService, useValue: accountServiceMock },
],
})
.overrideProvider(DialogService, { useValue: { open } })
.compileComponents();
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(NewItemDropdownV2Component);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("opens new folder dialog", () => {
component.openFolderDialog();
describe("buildQueryParams", () => {
it("should build query params for a Login cipher when not popped out", async () => {
await component.ngOnInit();
component.initialValues = {
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
} as NewItemInitialValues;

expect(open).toHaveBeenCalledWith(AddEditFolderDialogComponent);
});
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false);
jest.spyOn(Utils, "getHostname").mockReturnValue("example.com");

describe("new item", () => {
const emptyParams: AddEditQueryParams = {
collectionId: undefined,
organizationId: undefined,
folderId: undefined,
};
const params = component.buildQueryParams(CipherType.Login);

beforeEach(() => {
jest.spyOn(component, "newItemNavigate");
expect(params).toEqual({
type: CipherType.Login.toString(),
collectionId: "777-888-999",
organizationId: "444-555-666",
folderId: "222-333-444",
uri: "https://example.com",
name: "example.com",
});
});

it("navigates to new login", async () => {
await component.newItemNavigate(CipherType.Login);
it("should build query params for a Login cipher when popped out", () => {
component.initialValues = {
collectionId: "777-888-999",
} as NewItemInitialValues;

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: {
type: CipherType.Login.toString(),
name: "example.com",
uri: "https://example.com",
...emptyParams,
},
});
});
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(true);

it("navigates to new card", async () => {
await component.newItemNavigate(CipherType.Card);
const params = component.buildQueryParams(CipherType.Login);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: { type: CipherType.Card.toString(), ...emptyParams },
expect(params).toEqual({
type: CipherType.Login.toString(),
collectionId: "777-888-999",
});
});

it("navigates to new identity", async () => {
await component.newItemNavigate(CipherType.Identity);
it("should build query params for a secure note", () => {
component.initialValues = {
collectionId: "777-888-999",
} as NewItemInitialValues;

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: { type: CipherType.Identity.toString(), ...emptyParams },
const params = component.buildQueryParams(CipherType.SecureNote);

expect(params).toEqual({
type: CipherType.SecureNote.toString(),
collectionId: "777-888-999",
});
});

it("navigates to new note", async () => {
await component.newItemNavigate(CipherType.SecureNote);
it("should build query params for an Identity", () => {
component.initialValues = {
collectionId: "777-888-999",
} as NewItemInitialValues;

const params = component.buildQueryParams(CipherType.Identity);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: { type: CipherType.SecureNote.toString(), ...emptyParams },
expect(params).toEqual({
type: CipherType.Identity.toString(),
collectionId: "777-888-999",
});
});

it("includes initial values", async () => {
it("should build query params for a Card", () => {
component.initialValues = {
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
} as NewItemInitialValues;

await component.newItemNavigate(CipherType.Login);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: {
type: CipherType.Login.toString(),
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
uri: "https://example.com",
name: "example.com",
},
const params = component.buildQueryParams(CipherType.Card);

expect(params).toEqual({
type: CipherType.Card.toString(),
collectionId: "777-888-999",
});
});

it("does not include name or uri when the extension is popped out", async () => {
jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(true);

it("should build query params for a SshKey", () => {
component.initialValues = {
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
} as NewItemInitialValues;

await component.newItemNavigate(CipherType.Login);
const params = component.buildQueryParams(CipherType.SshKey);

expect(navigate).toHaveBeenCalledWith(["/add-cipher"], {
queryParams: {
type: CipherType.Login.toString(),
folderId: "222-333-444",
organizationId: "444-555-666",
collectionId: "777-888-999",
},
expect(params).toEqual({
type: CipherType.SshKey.toString(),
collectionId: "777-888-999",
});
});
});
Expand Down
Loading

0 comments on commit 762953f

Please sign in to comment.