();
- this.dataSource.filteredData?.forEach((row) => {
- if (this.selectedUrls().has(row.applicationName)) {
- filteredUrls.add(row.applicationName);
- }
- });
- this.selectedUrls.set(filteredUrls);
});
}
@@ -218,12 +234,13 @@ export class ApplicationsComponent implements OnInit {
this.selectedFilter.set(value);
}
- markAppsAsCritical = async () => {
+ async markAppsAsCritical() {
this.updatingCriticalApps.set(true);
- const count = this.selectedUrls().size;
+ const visibleSelected = this.visibleSelectedApps();
+ const count = visibleSelected.size;
this.dataService
- .saveCriticalApplications(Array.from(this.selectedUrls()))
+ .saveCriticalApplications(Array.from(visibleSelected))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (response) => {
@@ -246,11 +263,11 @@ export class ApplicationsComponent implements OnInit {
});
},
});
- };
+ }
- unmarkAppsAsCritical = async () => {
+ async unmarkAppsAsCritical() {
this.updatingCriticalApps.set(true);
- const appsToUnmark = this.selectedUrls();
+ const appsToUnmark = this.visibleSelectedApps();
this.dataService
.removeCriticalApplications(appsToUnmark)
@@ -278,7 +295,7 @@ export class ApplicationsComponent implements OnInit {
});
},
});
- };
+ }
async requestPasswordChange() {
const orgId = this.organizationId();
@@ -310,24 +327,38 @@ export class ApplicationsComponent implements OnInit {
}
}
- showAppAtRiskMembers = async (applicationName: string) => {
+ async showAppAtRiskMembers(applicationName: string) {
await this.dataService.setDrawerForAppAtRiskMembers(applicationName);
- };
+ }
- onCheckboxChange = (applicationName: string, event: Event) => {
- const isChecked = (event.target as HTMLInputElement).checked;
+ onCheckboxChange({ applicationName, checked }: { applicationName: string; checked: boolean }) {
this.selectedUrls.update((selectedUrls) => {
const nextSelected = new Set(selectedUrls);
- if (isChecked) {
+ if (checked) {
nextSelected.add(applicationName);
} else {
nextSelected.delete(applicationName);
}
return nextSelected;
});
- };
+ }
- downloadApplicationsCSV = () => {
+ onSelectAllChange(checked: boolean) {
+ const filteredData = this.filteredTableData();
+ if (!filteredData) {
+ return;
+ }
+
+ this.selectedUrls.update((selectedUrls) => {
+ const nextSelected = new Set(selectedUrls);
+ filteredData.forEach((row) =>
+ checked ? nextSelected.add(row.applicationName) : nextSelected.delete(row.applicationName),
+ );
+ return nextSelected;
+ });
+ }
+
+ downloadApplicationsCSV() {
try {
const data = this.dataSource.filteredData;
if (!data || data.length === 0) {
@@ -360,5 +391,5 @@ export class ApplicationsComponent implements OnInit {
} catch (error) {
this.logService.error("Failed to download applications CSV", error);
}
- };
+ }
}
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html
index 05dec048328..ddbc977fc13 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.html
@@ -33,7 +33,7 @@
bitCheckbox
type="checkbox"
[checked]="selectedUrls().has(row.applicationName)"
- (change)="checkboxChange()(row.applicationName, $event)"
+ (change)="checkboxChanged($event.target, row.applicationName)"
/>
| {
selectAllCheckboxEl = fixture.debugElement.query(By.css('[data-testid="selectAll"]'));
});
- it("should check all rows in table when checked", () => {
+ it("should emit selectAllChange event with true when checked", () => {
// arrange
const selectedUrls = new Set();
const dataSource = new TableDataSource();
@@ -121,18 +121,19 @@ describe("AppTableRowScrollableM11Component", () => {
fixture.componentRef.setInput("dataSource", dataSource);
fixture.detectChanges();
+ const selectAllChangeSpy = jest.fn();
+ fixture.componentInstance.selectAllChange.subscribe(selectAllChangeSpy);
+
// act
selectAllCheckboxEl.nativeElement.click();
fixture.detectChanges();
// assert
- expect(selectedUrls.has("google.com")).toBe(true);
- expect(selectedUrls.has("facebook.com")).toBe(true);
- expect(selectedUrls.has("twitter.com")).toBe(true);
- expect(selectedUrls.size).toBe(3);
+ expect(selectAllChangeSpy).toHaveBeenCalledWith(true);
+ expect(selectAllChangeSpy).toHaveBeenCalledTimes(1);
});
- it("should uncheck all rows in table when unchecked", () => {
+ it("should emit selectAllChange event with false when unchecked", () => {
// arrange
const selectedUrls = new Set(["google.com", "facebook.com", "twitter.com"]);
const dataSource = new TableDataSource();
@@ -142,12 +143,16 @@ describe("AppTableRowScrollableM11Component", () => {
fixture.componentRef.setInput("dataSource", dataSource);
fixture.detectChanges();
+ const selectAllChangeSpy = jest.fn();
+ fixture.componentInstance.selectAllChange.subscribe(selectAllChangeSpy);
+
// act
selectAllCheckboxEl.nativeElement.click();
fixture.detectChanges();
// assert
- expect(selectedUrls.size).toBe(0);
+ expect(selectAllChangeSpy).toHaveBeenCalledWith(false);
+ expect(selectAllChangeSpy).toHaveBeenCalledTimes(1);
});
it("should become checked when all rows in table are checked", () => {
@@ -178,4 +183,59 @@ describe("AppTableRowScrollableM11Component", () => {
expect(selectAllCheckboxEl.nativeElement.checked).toBe(false);
});
});
+
+ describe("individual row checkbox", () => {
+ it("should emit checkboxChange event with correct parameters when checkboxChanged is called", () => {
+ // arrange
+ const checkboxChangeSpy = jest.fn();
+ fixture.componentInstance.checkboxChange.subscribe(checkboxChangeSpy);
+
+ const mockTarget = { checked: true } as HTMLInputElement;
+
+ // act
+ fixture.componentInstance.checkboxChanged(mockTarget, "google.com");
+
+ // assert
+ expect(checkboxChangeSpy).toHaveBeenCalledWith({
+ applicationName: "google.com",
+ checked: true,
+ });
+ expect(checkboxChangeSpy).toHaveBeenCalledTimes(1);
+ });
+
+ it("should emit checkboxChange with checked=false when checkbox is unchecked", () => {
+ // arrange
+ const checkboxChangeSpy = jest.fn();
+ fixture.componentInstance.checkboxChange.subscribe(checkboxChangeSpy);
+
+ const mockTarget = { checked: false } as HTMLInputElement;
+
+ // act
+ fixture.componentInstance.checkboxChanged(mockTarget, "google.com");
+
+ // assert
+ expect(checkboxChangeSpy).toHaveBeenCalledWith({
+ applicationName: "google.com",
+ checked: false,
+ });
+ expect(checkboxChangeSpy).toHaveBeenCalledTimes(1);
+ });
+
+ it("should emit checkboxChange with correct applicationName for different applications", () => {
+ // arrange
+ const checkboxChangeSpy = jest.fn();
+ fixture.componentInstance.checkboxChange.subscribe(checkboxChangeSpy);
+
+ const mockTarget = { checked: true } as HTMLInputElement;
+
+ // act
+ fixture.componentInstance.checkboxChanged(mockTarget, "facebook.com");
+
+ // assert
+ expect(checkboxChangeSpy).toHaveBeenCalledWith({
+ applicationName: "facebook.com",
+ checked: true,
+ });
+ });
+ });
});
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts
index a23d1855ba5..65cfb8d092e 100644
--- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts
+++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable-m11.component.ts
@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
-import { ChangeDetectionStrategy, Component, input } from "@angular/core";
+import { ChangeDetectionStrategy, Component, input, output } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { MenuModule, TableDataSource, TableModule, TooltipDirective } from "@bitwarden/components";
@@ -30,7 +30,8 @@ export class AppTableRowScrollableM11Component {
readonly selectedUrls = input>();
readonly openApplication = input("");
readonly showAppAtRiskMembers = input<(applicationName: string) => void>();
- readonly checkboxChange = input<(applicationName: string, $event: Event) => void>();
+ readonly checkboxChange = output<{ applicationName: string; checked: boolean }>();
+ readonly selectAllChange = output();
allAppsSelected(): boolean {
const tableData = this.dataSource()?.filteredData;
@@ -43,20 +44,13 @@ export class AppTableRowScrollableM11Component {
return tableData.length > 0 && tableData.every((row) => selectedUrls.has(row.applicationName));
}
+ checkboxChanged(target: HTMLInputElement, applicationName: string) {
+ const checked = target.checked;
+ this.checkboxChange.emit({ applicationName, checked });
+ }
+
selectAllChanged(target: HTMLInputElement) {
const checked = target.checked;
-
- const tableData = this.dataSource()?.filteredData;
- const selectedUrls = this.selectedUrls();
-
- if (!tableData || !selectedUrls) {
- return false;
- }
-
- if (checked) {
- tableData.forEach((row) => selectedUrls.add(row.applicationName));
- } else {
- selectedUrls.clear();
- }
+ this.selectAllChange.emit(checked);
}
}
|