1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-31 08:43:54 +00:00

apply patch from non forked repo

This commit is contained in:
Isaac Ivins
2025-11-25 09:35:29 -05:00
parent 9e90e72961
commit e486e2d73c
16 changed files with 410 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ import {
} from "@bitwarden/angular/auth/guards";
import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password";
import { SetInitialPasswordComponent } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.component";
import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard";
import {
DevicesIcon,
RegistrationUserAddIcon,
@@ -39,15 +40,19 @@ import {
TwoFactorAuthGuard,
NewDeviceVerificationComponent,
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components";
import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui";
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
import { VaultV2Component } from "../vault/app/vault/vault-v2.component";
import { VaultComponent } from "../vault/app/vault-v3/vault.component";
import { Fido2PlaceholderComponent } from "./components/fido2placeholder.component";
import { UserLayoutComponent } from "./layout/user-layout.component";
import { SendComponent } from "./tools/send/send.component";
import { SendsComponent } from "./tools/send-v2/sends.component";
/**
* Data properties acceptable for use in route objects in the desktop
@@ -99,7 +104,10 @@ const routes: Routes = [
{
path: "vault",
component: VaultV2Component,
canActivate: [authGuard],
canActivate: [
authGuard,
canAccessFeature(FeatureFlag.DesktopUiMigrationMilestone1, false, "new-vault", false),
],
},
{
path: "send",
@@ -325,6 +333,21 @@ const routes: Routes = [
},
],
},
{
path: "",
component: UserLayoutComponent,
canActivate: [authGuard],
children: [
{
path: "new-vault",
component: VaultComponent,
},
{
path: "new-sends",
component: SendsComponent,
},
],
},
];
@NgModule({

View File

@@ -0,0 +1,4 @@
<bit-layout>
<ng-content select="app-desktop-side-nav, [slot=side-nav]" slot="side-nav"></ng-content>
<ng-content></ng-content>
</bit-layout>

View File

@@ -0,0 +1,61 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { RouterModule } from "@angular/router";
import { mock } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { NavigationModule } from "@bitwarden/components";
import { DesktopLayoutComponent } from "./desktop-layout.component";
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: true,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe("DesktopLayoutComponent", () => {
let component: DesktopLayoutComponent;
let fixture: ComponentFixture<DesktopLayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DesktopLayoutComponent, RouterModule.forRoot([]), NavigationModule],
providers: [
{
provide: I18nService,
useValue: mock<I18nService>(),
},
],
}).compileComponents();
fixture = TestBed.createComponent(DesktopLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
it("renders bit-layout component", () => {
const compiled = fixture.nativeElement;
const layoutElement = compiled.querySelector("bit-layout");
expect(layoutElement).toBeTruthy();
});
it("supports content projection for side-nav", () => {
const compiled = fixture.nativeElement;
const ngContent = compiled.querySelectorAll("ng-content");
expect(ngContent).toBeTruthy();
});
});

View File

@@ -0,0 +1,14 @@
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { RouterModule } from "@angular/router";
import { LayoutComponent, NavigationModule } from "@bitwarden/components";
@Component({
selector: "app-desktop-layout",
standalone: true,
imports: [CommonModule, RouterModule, LayoutComponent, NavigationModule],
templateUrl: "./desktop-layout.component.html",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DesktopLayoutComponent {}

View File

@@ -0,0 +1,14 @@
import { NgModule } from "@angular/core";
import { NavigationModule } from "@bitwarden/components";
import { DesktopLayoutComponent } from "./desktop-layout.component";
import { DesktopSideNavComponent } from "./desktop-side-nav.component";
@NgModule({
imports: [DesktopLayoutComponent, DesktopSideNavComponent],
exports: [NavigationModule, DesktopLayoutComponent, DesktopSideNavComponent],
declarations: [],
providers: [],
})
export class DesktopLayoutModule {}

View File

@@ -0,0 +1,3 @@
<bit-side-nav [variant]="variant()">
<ng-content></ng-content>
</bit-side-nav>

View File

@@ -0,0 +1,74 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { NavigationModule } from "@bitwarden/components";
import { DesktopSideNavComponent } from "./desktop-side-nav.component";
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: true,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe("DesktopSideNavComponent", () => {
let component: DesktopSideNavComponent;
let fixture: ComponentFixture<DesktopSideNavComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DesktopSideNavComponent, NavigationModule],
providers: [
{
provide: I18nService,
useValue: mock<I18nService>(),
},
],
}).compileComponents();
fixture = TestBed.createComponent(DesktopSideNavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
it("renders bit-side-nav component", () => {
const compiled = fixture.nativeElement;
const sideNavElement = compiled.querySelector("bit-side-nav");
expect(sideNavElement).toBeTruthy();
});
it("uses primary variant by default", () => {
expect(component.variant()).toBe("primary");
});
it("accepts variant input", () => {
fixture.componentRef.setInput("variant", "secondary");
fixture.detectChanges();
expect(component.variant()).toBe("secondary");
});
it("passes variant to bit-side-nav", () => {
fixture.componentRef.setInput("variant", "secondary");
fixture.detectChanges();
const compiled = fixture.nativeElement;
const sideNavElement = compiled.querySelector("bit-side-nav");
expect(sideNavElement.getAttribute("ng-reflect-variant")).toBe("secondary");
});
});

View File

@@ -0,0 +1,15 @@
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, input } from "@angular/core";
import { NavigationModule, SideNavVariant } from "@bitwarden/components";
@Component({
selector: "app-desktop-side-nav",
standalone: true,
templateUrl: "desktop-side-nav.component.html",
imports: [CommonModule, NavigationModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DesktopSideNavComponent {
readonly variant = input<SideNavVariant>("primary");
}

View File

@@ -0,0 +1,10 @@
<app-desktop-layout>
<app-desktop-side-nav slot="side-nav">
<bit-nav-logo [openIcon]="logo" route="." [label]="'passwordManager' | i18n"></bit-nav-logo>
<bit-nav-item icon="bwi-vault" [text]="'myVault' | i18n" route="new-vault"></bit-nav-item>
<bit-nav-item icon="bwi-send" [text]="'send' | i18n" route="new-sends"></bit-nav-item>
</app-desktop-side-nav>
<router-outlet></router-outlet>
</app-desktop-layout>

View File

@@ -0,0 +1,102 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { RouterModule } from "@angular/router";
import { mock } from "jest-mock-extended";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DesktopLayoutModule } from "./desktop-layout.module";
import { UserLayoutComponent } from "./user-layout.component";
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: true,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe("UserLayoutComponent", () => {
let component: UserLayoutComponent;
let fixture: ComponentFixture<UserLayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserLayoutComponent, RouterModule.forRoot([]), DesktopLayoutModule],
providers: [
{
provide: I18nService,
useValue: mock<I18nService>(),
},
],
}).compileComponents();
fixture = TestBed.createComponent(UserLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
it("renders desktop layout", () => {
const compiled = fixture.nativeElement;
const layoutElement = compiled.querySelector("app-desktop-layout");
expect(layoutElement).toBeTruthy();
});
it("renders desktop side nav", () => {
const compiled = fixture.nativeElement;
const sideNavElement = compiled.querySelector("app-desktop-side-nav");
expect(sideNavElement).toBeTruthy();
});
it("renders logo with correct properties", () => {
const compiled = fixture.nativeElement;
const logoElement = compiled.querySelector("bit-nav-logo");
expect(logoElement).toBeTruthy();
expect(logoElement.getAttribute("route")).toBe(".");
});
it("renders vault navigation item", () => {
const compiled = fixture.nativeElement;
const navItems = compiled.querySelectorAll("bit-nav-item");
const vaultItem = Array.from(navItems).find(
(item) => (item as Element).getAttribute("icon") === "bwi-vault",
) as Element | undefined;
expect(vaultItem).toBeTruthy();
expect(vaultItem?.getAttribute("route")).toBe("new-vault");
});
it("renders send navigation item", () => {
const compiled = fixture.nativeElement;
const navItems = compiled.querySelectorAll("bit-nav-item");
const sendItem = Array.from(navItems).find(
(item) => (item as Element).getAttribute("icon") === "bwi-send",
) as Element | undefined;
expect(sendItem).toBeTruthy();
expect(sendItem?.getAttribute("route")).toBe("new-sends");
});
it("renders router outlet", () => {
const compiled = fixture.nativeElement;
const routerOutlet = compiled.querySelector("router-outlet");
expect(routerOutlet).toBeTruthy();
});
it("has logo property set", () => {
expect(component["logo"]).toBeDefined();
});
});

View File

@@ -0,0 +1,19 @@
import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PasswordManagerLogo } from "@bitwarden/assets/svg";
import { DesktopLayoutModule } from "./desktop-layout.module";
@Component({
selector: "app-user-layout",
standalone: true,
templateUrl: "user-layout.component.html",
imports: [CommonModule, RouterModule, JslibModule, DesktopLayoutModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserLayoutComponent {
protected readonly logo = PasswordManagerLogo;
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { SendsComponent } from "./sends.component";
describe("SendsComponent", () => {
let component: SendsComponent;
let fixture: ComponentFixture<SendsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SendsComponent],
}).compileComponents();
fixture = TestBed.createComponent(SendsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,10 @@
import { Component, ChangeDetectionStrategy } from "@angular/core";
@Component({
selector: "app-sends-v2",
standalone: true,
imports: [],
template: "<p>Sends V2 Component</p>",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SendsComponent {}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { VaultComponent } from "./vault.component";
describe("VaultComponent", () => {
let component: VaultComponent;
let fixture: ComponentFixture<VaultComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VaultComponent],
}).compileComponents();
fixture = TestBed.createComponent(VaultComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("creates component", () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,10 @@
import { ChangeDetectionStrategy, Component } from "@angular/core";
@Component({
selector: "app-vault-v3",
standalone: true,
imports: [],
template: "<p>Vault V3 Component</p>",
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class VaultComponent {}

View File

@@ -72,6 +72,9 @@ export enum FeatureFlag {
/* Innovation */
PM19148_InnovationArchive = "pm-19148-innovation-archive",
/* Desktop */
DesktopUiMigrationMilestone1 = "desktop-ui-migration-milestone-1",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -150,6 +153,9 @@ export const DefaultFeatureFlagValue = {
/* Innovation */
[FeatureFlag.PM19148_InnovationArchive]: FALSE,
/* Desktop */
[FeatureFlag.DesktopUiMigrationMilestone1]: FALSE,
} satisfies Record<FeatureFlag, AllowedFeatureFlagTypes>;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;