Aam-Digital/ndb-core

View on GitHub
src/app/core/entity/entity-actions/entity-actions.service.spec.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import { fakeAsync, TestBed, tick } from "@angular/core/testing";
import { EntityActionsService } from "./entity-actions.service";
import { EntityMapperService } from "../entity-mapper/entity-mapper.service";
import {
  MatSnackBar,
  MatSnackBarRef,
  TextOnlySnackBar,
} from "@angular/material/snack-bar";
import { ConfirmationDialogService } from "../../common-components/confirmation-dialog/confirmation-dialog.service";
import { Entity } from "../model/entity";
import { NEVER, of, Subject } from "rxjs";
import { Router } from "@angular/router";
import { CoreTestingModule } from "../../../utils/core-testing.module";
import { EntityDeleteService } from "./entity-delete.service";
import { EntityAnonymizeService } from "./entity-anonymize.service";
import { CascadingActionResult } from "./cascading-entity-action";

describe("EntityActionsService", () => {
  let service: EntityActionsService;
  let mockedEntityMapper: jasmine.SpyObj<EntityMapperService>;
  let snackBarSpy: jasmine.SpyObj<MatSnackBar>;
  let mockSnackBarRef: jasmine.SpyObj<MatSnackBarRef<TextOnlySnackBar>>;
  let mockConfirmationDialog: jasmine.SpyObj<ConfirmationDialogService>;
  let mockRouter;
  let mockedEntityDeleteService: jasmine.SpyObj<EntityDeleteService>;
  let mockedEntityAnonymizeService: jasmine.SpyObj<EntityAnonymizeService>;

  let singleTestEntity: Entity;
  let severalTestEntities: Entity[] = [];

  beforeEach(() => {
    singleTestEntity = new Entity();
    severalTestEntities[0] = new Entity();
    severalTestEntities[1] = new Entity();
    severalTestEntities[2] = new Entity();

    mockedEntityDeleteService = jasmine.createSpyObj(["deleteEntity"]);
    mockedEntityDeleteService.deleteEntity.and.resolveTo(
      new CascadingActionResult([singleTestEntity]),
    );
    mockedEntityAnonymizeService = jasmine.createSpyObj(["anonymizeEntity"]);
    mockedEntityAnonymizeService.anonymizeEntity.and.resolveTo(
      new CascadingActionResult([singleTestEntity]),
    );
    mockedEntityMapper = jasmine.createSpyObj(["save", "saveAll"]);

    snackBarSpy = jasmine.createSpyObj(["open"]);
    mockSnackBarRef = jasmine.createSpyObj(["onAction", "afterDismissed"]);
    mockSnackBarRef.onAction.and.returnValue(of());
    snackBarSpy.open.and.returnValue(mockSnackBarRef);

    mockConfirmationDialog = jasmine.createSpyObj([
      "getConfirmation",
      "showProgressDialog",
    ]);
    mockConfirmationDialog.getConfirmation.and.resolveTo(true);
    mockConfirmationDialog.showProgressDialog.and.returnValue(
      jasmine.createSpyObj(["close"]),
    );

    TestBed.configureTestingModule({
      imports: [CoreTestingModule],
      providers: [
        EntityActionsService,
        { provide: EntityDeleteService, useValue: mockedEntityDeleteService },
        {
          provide: EntityAnonymizeService,
          useValue: mockedEntityAnonymizeService,
        },
        { provide: EntityMapperService, useValue: mockedEntityMapper },
        { provide: MatSnackBar, useValue: snackBarSpy },
        Router,
        {
          provide: ConfirmationDialogService,
          useValue: mockConfirmationDialog,
        },
      ],
    });
    mockRouter = TestBed.inject(Router);
    spyOn(mockRouter, "navigate");

    service = TestBed.inject(EntityActionsService);
  });

  it("should return false when user cancels confirmation", async () => {
    mockConfirmationDialog.getConfirmation.and.resolveTo(false);

    const result = await service.delete(new Entity());

    expect(result).toBe(false);
    expect(snackBarSpy.open).not.toHaveBeenCalled();
    expect(mockedEntityDeleteService.deleteEntity).not.toHaveBeenCalled();
  });

  it("should delete a single entity, show snackbar confirmation and navigate back", async () => {
    // onAction is never called
    mockSnackBarRef.onAction.and.returnValues(NEVER);
    mockSnackBarRef.afterDismissed.and.returnValue(of(undefined));

    const result = await service.delete(singleTestEntity, true);

    expect(result).toBe(true);
    expect(snackBarSpy.open).toHaveBeenCalled();
    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalledWith(
      singleTestEntity,
      true,
    );
    expect(mockRouter.navigate).toHaveBeenCalled();
  });

  it("should delete several entities and show snackbar confirmation", async () => {
    // onAction is never called
    mockSnackBarRef.onAction.and.returnValues(NEVER);
    mockSnackBarRef.afterDismissed.and.returnValue(of(undefined));

    const result = await service.delete(severalTestEntities);

    expect(result).toBe(true);
    expect(snackBarSpy.open).toHaveBeenCalled();
    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalledTimes(3);
    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalledWith(
      severalTestEntities[0],
      true,
    );
    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalledWith(
      severalTestEntities[1],
      true,
    );
    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalledWith(
      severalTestEntities[2],
      true,
    );
  });

  it("should undo the deletion of several entities", fakeAsync(async () => {
    const otherAffectedEntities = [new Entity(), new Entity()];
    mockedEntityDeleteService.deleteEntity.and.resolveTo(
      new CascadingActionResult([
        ...severalTestEntities,
        ...otherAffectedEntities,
      ]),
    );

    // Mock a snackbar where 'undo' is pressed
    const onSnackbarAction = new Subject<void>();
    mockSnackBarRef.onAction.and.returnValue(onSnackbarAction.asObservable());

    mockedEntityMapper.save.and.resolveTo();

    service.delete(severalTestEntities, true);
    tick();

    mockRouter.navigate.calls.reset();
    onSnackbarAction.next();
    onSnackbarAction.complete();
    tick();

    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalled();
    expect(mockedEntityMapper.saveAll).toHaveBeenCalledWith(
      [...severalTestEntities, ...otherAffectedEntities],
      true,
    );
    expect(mockRouter.navigate).toHaveBeenCalled();
  }));

  it("should re-save all affected entities and navigate back to entity on undo", fakeAsync(() => {
    const anotherAffectedEntity = new Entity();
    mockedEntityDeleteService.deleteEntity.and.resolveTo(
      new CascadingActionResult([singleTestEntity, anotherAffectedEntity]),
    );

    // Mock a snackbar where 'undo' is pressed
    const onSnackbarAction = new Subject<void>();
    mockSnackBarRef.onAction.and.returnValue(onSnackbarAction.asObservable());

    mockedEntityMapper.save.and.resolveTo();

    service.delete(singleTestEntity, true);
    tick();

    mockRouter.navigate.calls.reset();
    onSnackbarAction.next();
    onSnackbarAction.complete();
    tick();

    expect(mockedEntityDeleteService.deleteEntity).toHaveBeenCalled();
    expect(mockedEntityMapper.saveAll).toHaveBeenCalledWith(
      [singleTestEntity, anotherAffectedEntity],
      true,
    );
    expect(mockRouter.navigate).toHaveBeenCalled();
  }));

  it("should anonymize and save a single entity", async () => {
    // onAction is never called
    mockSnackBarRef.onAction.and.returnValues(NEVER);
    mockSnackBarRef.afterDismissed.and.returnValue(of(undefined));

    const result = await service.anonymize(singleTestEntity);

    expect(result).toBe(true);
    expect(snackBarSpy.open).toHaveBeenCalled();
    expect(mockedEntityAnonymizeService.anonymizeEntity).toHaveBeenCalledWith(
      singleTestEntity,
    );
  });

  it("should anonymize and save several entities and show snackbar confirmation", async () => {
    // onAction is never called
    mockSnackBarRef.onAction.and.returnValues(NEVER);
    mockSnackBarRef.afterDismissed.and.returnValue(of(undefined));

    const result = await service.anonymize(severalTestEntities);

    expect(result).toBe(true);
    expect(snackBarSpy.open).toHaveBeenCalled();
    expect(mockedEntityAnonymizeService.anonymizeEntity).toHaveBeenCalledTimes(
      3,
    );
    expect(mockedEntityAnonymizeService.anonymizeEntity).toHaveBeenCalledWith(
      severalTestEntities[0],
    );
    expect(mockedEntityAnonymizeService.anonymizeEntity).toHaveBeenCalledWith(
      severalTestEntities[1],
    );
    expect(mockedEntityAnonymizeService.anonymizeEntity).toHaveBeenCalledWith(
      severalTestEntities[2],
    );
  });

  it("should undo the anonymization of several entities", fakeAsync(async () => {
    const otherAffectedEntities = [new Entity(), new Entity()];
    mockedEntityAnonymizeService.anonymizeEntity.and.resolveTo(
      new CascadingActionResult([
        ...severalTestEntities,
        ...otherAffectedEntities,
      ]),
    );

    // Mock a snackbar where 'undo' is pressed
    const onSnackbarAction = new Subject<void>();
    mockSnackBarRef.onAction.and.returnValue(onSnackbarAction.asObservable());

    mockedEntityMapper.save.and.resolveTo();

    service.anonymize(severalTestEntities);
    tick();

    onSnackbarAction.next();
    onSnackbarAction.complete();
    tick();

    expect(mockedEntityAnonymizeService.anonymizeEntity).toHaveBeenCalled();
    expect(mockedEntityMapper.saveAll).toHaveBeenCalledWith(
      [...severalTestEntities, ...otherAffectedEntities],
      true,
    );
  }));

  it("should archive and save a single entity and show snackbar confirmation", async () => {
    // onAction is never called
    mockSnackBarRef.onAction.and.returnValues(NEVER);
    mockSnackBarRef.afterDismissed.and.returnValue(of(undefined));

    let expectedSavedEntity = singleTestEntity.copy();
    expectedSavedEntity.inactive = true;

    const result = await service.archive(singleTestEntity);
    expect(result).toBe(true);
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(expectedSavedEntity);
    expect(snackBarSpy.open).toHaveBeenCalled();
  });

  it("should archive and save several entities and show snackbar confirmation", async () => {
    // onAction is never called
    mockSnackBarRef.onAction.and.returnValues(NEVER);
    mockSnackBarRef.afterDismissed.and.returnValue(of(undefined));

    let expectedSavedEntities = severalTestEntities.map((e) => e.copy());
    expectedSavedEntities.forEach((e) => (e.inactive = true));

    const result = await service.archive(severalTestEntities);
    expect(result).toBe(true);
    expect(mockedEntityMapper.save).toHaveBeenCalledTimes(3);
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      expectedSavedEntities[0],
    );
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      expectedSavedEntities[1],
    );
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      expectedSavedEntities[2],
    );
    expect(snackBarSpy.open).toHaveBeenCalled();
  });

  it("should archiveUndo and save a single entity", async () => {
    let expectedSavedEntity = singleTestEntity.copy();
    expectedSavedEntity.inactive = true;

    await service.archive(singleTestEntity);
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(expectedSavedEntity);
    mockedEntityMapper.save.calls.reset();

    await service.undoArchive(singleTestEntity);
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(singleTestEntity);
  });

  it("should archiveUndo and save several entities", async () => {
    let expectedSavedEntities = severalTestEntities.map((e) => e.copy());
    expectedSavedEntities.forEach((e) => (e.inactive = true));

    await service.archive(severalTestEntities);
    expect(mockedEntityMapper.save).toHaveBeenCalledTimes(3);
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      expectedSavedEntities[0],
    );
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      expectedSavedEntities[1],
    );
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      expectedSavedEntities[2],
    );
    mockedEntityMapper.save.calls.reset();

    await service.undoArchive(severalTestEntities);
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      severalTestEntities[0],
    );
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      severalTestEntities[1],
    );
    expect(mockedEntityMapper.save).toHaveBeenCalledWith(
      severalTestEntities[2],
    );
  });
});