mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 04:03:29 +00:00
Merge branch 'main' into beeep/dev-container
This commit is contained in:
@@ -74,9 +74,11 @@
|
||||
<button type="button" bitMenuItem (click)="edit(cipher)">
|
||||
{{ "edit" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem (click)="clone(cipher)">
|
||||
{{ "clone" | i18n }}
|
||||
</button>
|
||||
@if (userHasPremium$ | async) {
|
||||
<button type="button" bitMenuItem (click)="clone(cipher)">
|
||||
{{ "clone" | i18n }}
|
||||
</button>
|
||||
}
|
||||
@if (canAssignCollections$ | async) {
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { By } from "@angular/platform-browser";
|
||||
import { provideNoopAnimations } from "@angular/platform-browser/animations";
|
||||
import { Router } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
@@ -7,10 +9,13 @@ import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { PopupRouterCacheService } from "@bitwarden/browser/platform/popup/view-cache/popup-router-cache.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { LogService } from "@bitwarden/logging";
|
||||
@@ -25,40 +30,78 @@ jest.mock("qrcode-parser", () => {});
|
||||
|
||||
describe("ArchiveComponent", () => {
|
||||
let component: ArchiveComponent;
|
||||
let fixture: ComponentFixture<ArchiveComponent>;
|
||||
|
||||
let hasOrganizations: jest.Mock;
|
||||
let decryptedCollections$: jest.Mock;
|
||||
let navigate: jest.Mock;
|
||||
let showPasswordPrompt: jest.Mock;
|
||||
let userHasPremium$: jest.Mock;
|
||||
let archivedCiphers$: jest.Mock;
|
||||
|
||||
beforeAll(async () => {
|
||||
beforeEach(async () => {
|
||||
navigate = jest.fn();
|
||||
showPasswordPrompt = jest.fn().mockResolvedValue(true);
|
||||
hasOrganizations = jest.fn();
|
||||
decryptedCollections$ = jest.fn();
|
||||
hasOrganizations = jest.fn().mockReturnValue(of(false));
|
||||
decryptedCollections$ = jest.fn().mockReturnValue(of([]));
|
||||
userHasPremium$ = jest.fn().mockReturnValue(of(false));
|
||||
archivedCiphers$ = jest.fn().mockReturnValue(of([{ id: "cipher-1" }]));
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ArchiveComponent],
|
||||
providers: [
|
||||
provideNoopAnimations(),
|
||||
{ provide: Router, useValue: { navigate } },
|
||||
{
|
||||
provide: AccountService,
|
||||
useValue: { activeAccount$: new BehaviorSubject({ id: "user-id" }) },
|
||||
},
|
||||
{ provide: PasswordRepromptService, useValue: { showPasswordPrompt } },
|
||||
{ provide: OrganizationService, useValue: { hasOrganizations } },
|
||||
{
|
||||
provide: OrganizationService,
|
||||
useValue: { hasOrganizations, organizations$: () => of([]) },
|
||||
},
|
||||
{ provide: CollectionService, useValue: { decryptedCollections$ } },
|
||||
{ provide: DialogService, useValue: mock<DialogService>() },
|
||||
{ provide: CipherService, useValue: mock<CipherService>() },
|
||||
{ provide: CipherArchiveService, useValue: mock<CipherArchiveService>() },
|
||||
{
|
||||
provide: CipherArchiveService,
|
||||
useValue: {
|
||||
userHasPremium$,
|
||||
archivedCiphers$,
|
||||
userCanArchive$: jest.fn().mockReturnValue(of(true)),
|
||||
showSubscriptionEndedMessaging$: jest.fn().mockReturnValue(of(false)),
|
||||
},
|
||||
},
|
||||
{ provide: ToastService, useValue: mock<ToastService>() },
|
||||
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() },
|
||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||
{ provide: LogService, useValue: mock<LogService>() },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: {
|
||||
environment$: of({
|
||||
getIconsUrl: () => "https://icons.example.com",
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainSettingsService,
|
||||
useValue: {
|
||||
showFavicons$: of(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CipherAuthorizationService,
|
||||
useValue: {
|
||||
canDeleteCipher$: jest.fn().mockReturnValue(of(true)),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(ArchiveComponent);
|
||||
fixture = TestBed.createComponent(ArchiveComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
@@ -137,4 +180,54 @@ describe("ArchiveComponent", () => {
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("clone menu option", () => {
|
||||
const getBitMenuPanel = () => document.querySelector(".bit-menu-panel");
|
||||
|
||||
it("is shown when user has premium", async () => {
|
||||
userHasPremium$.mockReturnValue(of(true));
|
||||
|
||||
const testFixture = TestBed.createComponent(ArchiveComponent);
|
||||
testFixture.detectChanges();
|
||||
await testFixture.whenStable();
|
||||
|
||||
const menuTrigger = testFixture.debugElement.query(By.css('button[aria-haspopup="menu"]'));
|
||||
expect(menuTrigger).toBeTruthy();
|
||||
(menuTrigger.nativeElement as HTMLButtonElement).click();
|
||||
testFixture.detectChanges();
|
||||
|
||||
const menuPanel = getBitMenuPanel();
|
||||
expect(menuPanel).toBeTruthy();
|
||||
|
||||
const menuButtons = menuPanel?.querySelectorAll("button[bitMenuItem]");
|
||||
const cloneButtonFound = Array.from(menuButtons || []).some(
|
||||
(btn) => btn.textContent?.trim() === "clone",
|
||||
);
|
||||
|
||||
expect(cloneButtonFound).toBe(true);
|
||||
});
|
||||
|
||||
it("is not shown when user does not have premium", async () => {
|
||||
userHasPremium$.mockReturnValue(of(false));
|
||||
|
||||
const testFixture = TestBed.createComponent(ArchiveComponent);
|
||||
testFixture.detectChanges();
|
||||
await testFixture.whenStable();
|
||||
|
||||
const menuTrigger = testFixture.debugElement.query(By.css('button[aria-haspopup="menu"]'));
|
||||
expect(menuTrigger).toBeTruthy();
|
||||
(menuTrigger.nativeElement as HTMLButtonElement).click();
|
||||
testFixture.detectChanges();
|
||||
|
||||
const menuPanel = getBitMenuPanel();
|
||||
expect(menuPanel).toBeTruthy();
|
||||
|
||||
const menuButtons = menuPanel?.querySelectorAll("button[bitMenuItem]");
|
||||
const cloneButtonFound = Array.from(menuButtons || []).some(
|
||||
(btn) => btn.textContent?.trim() === "clone",
|
||||
);
|
||||
|
||||
expect(cloneButtonFound).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,6 +135,10 @@ export class ArchiveComponent {
|
||||
switchMap((userId) => this.cipherArchiveService.showSubscriptionEndedMessaging$(userId)),
|
||||
);
|
||||
|
||||
protected userHasPremium$ = this.userId$.pipe(
|
||||
switchMap((userId) => this.cipherArchiveService.userHasPremium$(userId)),
|
||||
);
|
||||
|
||||
async navigateToPremium() {
|
||||
await this.router.navigate(["/premium"]);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
"browser-hrtime": "1.1.8",
|
||||
"chalk": "4.1.2",
|
||||
"commander": "14.0.0",
|
||||
"core-js": "3.47.0",
|
||||
"core-js": "3.48.0",
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
"accessIntelligence": {
|
||||
"message": "Access Intelligence"
|
||||
},
|
||||
"noApplicationsMatchTheseFilters": {
|
||||
"message": "No applications match these filters"
|
||||
},
|
||||
"passwordRisk": {
|
||||
"message": "Password Risk"
|
||||
},
|
||||
|
||||
@@ -43,5 +43,11 @@
|
||||
[checkboxChange]="onCheckboxChange"
|
||||
[showAppAtRiskMembers]="showAppAtRiskMembers"
|
||||
></app-table-row-scrollable-m11>
|
||||
|
||||
@if (emptyTableExplanation()) {
|
||||
<div class="tw-flex tw-mt-10 tw-justify-center">
|
||||
<span bitTypography="body2">{{ emptyTableExplanation() }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ export class ApplicationsComponent implements OnInit {
|
||||
icon: " ",
|
||||
},
|
||||
]);
|
||||
protected readonly emptyTableExplanation = signal("");
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
@@ -164,6 +165,12 @@ export class ApplicationsComponent implements OnInit {
|
||||
this.dataSource.filter = (app) =>
|
||||
filterFunction(app) &&
|
||||
app.applicationName.toLowerCase().includes(searchText.toLowerCase());
|
||||
|
||||
if (this.dataSource?.filteredData?.length === 0) {
|
||||
this.emptyTableExplanation.set(this.i18nService.t("noApplicationsMatchTheseFilters"));
|
||||
} else {
|
||||
this.emptyTableExplanation.set("");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -38,7 +38,7 @@
|
||||
"bufferutil": "4.1.0",
|
||||
"chalk": "4.1.2",
|
||||
"commander": "14.0.0",
|
||||
"core-js": "3.47.0",
|
||||
"core-js": "3.48.0",
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
@@ -205,7 +205,7 @@
|
||||
"browser-hrtime": "1.1.8",
|
||||
"chalk": "4.1.2",
|
||||
"commander": "14.0.0",
|
||||
"core-js": "3.47.0",
|
||||
"core-js": "3.48.0",
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
@@ -20879,9 +20879,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.47.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
|
||||
"integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
|
||||
"version": "3.48.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz",
|
||||
"integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
"bufferutil": "4.1.0",
|
||||
"chalk": "4.1.2",
|
||||
"commander": "14.0.0",
|
||||
"core-js": "3.47.0",
|
||||
"core-js": "3.48.0",
|
||||
"form-data": "4.0.4",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
|
||||
Reference in New Issue
Block a user