Skip to content

Instantly share code, notes, and snippets.

@jjrasche
Last active June 25, 2019 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jjrasche/1ba44469b07899b8b51c7c7da1639df3 to your computer and use it in GitHub Desktop.
Save jjrasche/1ba44469b07899b8b51c7c7da1639df3 to your computer and use it in GitHub Desktop.
// Angular
import { Component, ViewChild } from "@angular/core";
import { fakeAsync, TestModuleMetadata } from "@angular/core/testing";
// Third-Party
import { of } from "rxjs";
// Modules
import { ProposalHeaderModule } from "@vms/proposal/detail/proposal/proposal-header.module";
// Services
import { BidTypeLookupService } from "@vms/services/bid-type/bid-type-lookup.service";
import { GeorgeManageProposalLookupService } from "@vms/services/george/george-manage-proposal-lookup.service";
import { PaymentTypeLookupService } from "@vms/services/proposal/payment-type-lookup.service";
import { ProposalCertificationStatusLookupService } from "@vms/services/proposal/proposal-certification-status-lookup.service";
import { ProposalDetailOwnersService } from "@vms/services/proposal/proposal-detail-owners.service";
import { ProposalFiscalYearService } from "@vms/services/proposal/proposal-fiscal-year.service";
import { ProposalStatusService } from "@vms/services/proposal/proposal-status.service";
// Components / Directives
import { ProposalHeaderComponent } from "./proposal-header.component";
// Models
import { BidType } from "@vms/shared/objects/bid-type";
import { ApiResult } from "@vms/shared/objects/data-request";
import { GeorgeOfficeDto } from "@vms/shared/objects/admin/george";
import { HttpComponentTest } from "@vms/test/front-end-testing/http-component-test";
import { PaymentType } from "@vms/shared/objects/payment-type";
import { Proposal } from "@vms/proposal/objects/proposal";
import {
getProposal,
getBidTypes,
getProposalManageGeorges,
getProposalOwners,
getFiscalYears,
getPaymentTypes,
getProposalStatuses,
getProposalCertStatuses,
getFailedSaveResponse,
getResponseOwners,
getChangeGeorgeResponse,
} from "@vms/proposal/detail/proposal/header/proposal-header.component.spec.data";
import { ProposalStatusCodeEnum } from "@vms/shared/objects/proposal-status";
import { TestInputType, InputTest } from "@vms/test/front-end-testing/test-objects";
import { User } from "@vms/shared/objects/admin/user";
// tslint:disable: max-line-length
// Host Component
@Component({
selector: `test-host-component`,
template: `
<vms-proposal-header [proposal]="proposal">
</vms-proposal-header>
`,
})
class HostComponent {
// create access to the component under test for use in test cases
@ViewChild(ProposalHeaderComponent)
public componentUnderTest: ProposalHeaderComponent;
public proposal: Proposal;
// get access to our components services that control access to global data in order to mock later
constructor(
public bidTypeService: BidTypeLookupService,
public georgeService: GeorgeManageProposalLookupService,
public ownerService: ProposalDetailOwnersService,
public fiscalYearService: ProposalFiscalYearService,
public paymentTypeService: PaymentTypeLookupService,
public proposalCertificationStatusService: ProposalCertificationStatusLookupService,
public proposalStatusService: ProposalStatusService
) {}
// allow test cases to set own data
public setupTestData(proposal: Proposal) {
this.proposal = proposal;
}
}
// this typing forces the implementation of inputs to list all values in the Input enum
export type FormInputKeys = { [key in keyof typeof Input]: any };
export enum Input {
George = "George",
Owner = "Owner",
FiscalYear = "FiscalYear",
BidType = "BidType",
PaymentMethod = "PaymentMethod",
SaleName = "SaleName",
SaleNumber = "SaleNumber",
}
let specModule = ProposalHeaderModule.config() as TestModuleMetadata;
fdescribe("ProposalHeaderComponent", () => {
// testing abstraction used to simplify setup and access to helper methods
let base = new HttpComponentTest<ProposalHeaderComponent, FormInputKeys>(specModule, ProposalHeaderComponent, HostComponent);
base.settings = {
verifyAllCalls: false
};
// header button interaction
it("When not in edit state in header edit button is cliked, then form is in edit state", fakeAsync(() => {
setupTest(getProposal(), false);
base.clickEditButton();
expect(base.comp.editing).toEqual(true);
}));
it("When in edit state and header cancel button clicked, then form is not in edit state", fakeAsync(() => {
setupTest();
base.clickCancelButton();
expect(base.comp.editing).toEqual(false);
}));
it("When in edit state and form pristine, then header save button is disabled", fakeAsync(() => {
setupTest();
base.expectDisabled(base.getFormSaveButton());
}));
// Form Interactions
it("When form loads in read-mode, then the proposal object sets the form values", fakeAsync(() => {
let proposal = getProposal();
setupTest(proposal, false, getBidTypes(), getProposalManageGeorges(), getProposalOwners(), getFiscalYears());
base.compareReadOnlyFormValues(["18957", "Draft", "VMS 4.0", "Baraga Office", "Heym, Doug (HEYMDxxx)", proposal.fiscal_year.toString(), "Oral Auction", "Lump Sum", "Not Set", "Not Set", "Not Set", "Doug's second test sale", "11-999-18", "1", "Not Set", "Not Set", "Not Set"]);
}));
it("When form loads in edit-mode, then proposal object sets the form values", fakeAsync(() => {
let proposal = getProposal();
proposal.is_fiscal_year_editable = true;
const fiscalYears = getFiscalYears();
fiscalYears.data.push("2020");
setupTest(proposal, true, getBidTypes(), getProposalManageGeorges(), getProposalOwners(), fiscalYears);
expect(base.getActualEditableFormValue()).toEqual(["Baraga Office", "Heym, Doug (HEYMDxxx)", proposal.fiscal_year.toString(), "Oral Auction", "Lump Sum", "Doug's second test sale", "999-18"]);
}));
it("When more than 1 fiscal year is available, then fiscal year field is editable", fakeAsync(() => {
let proposal = getProposal();
proposal.is_fiscal_year_editable = true;
const fiscalYears = getFiscalYears();
fiscalYears.data.push("2020");
setupTest(proposal, true, getBidTypes(), getProposalManageGeorges(), getProposalOwners(), fiscalYears);
base.expectInputEditable(Input.FiscalYear);
}));
it("When 1 fiscal year is available, then fiscal year field is not editable", fakeAsync(() => {
setupTest();
base.expectInputReadonly(Input.FiscalYear);
}));
it("When the prposal's fiscal year is not included in the drop down, then the fiscal year field is read only", fakeAsync(() => {
let proposal = getProposal();
proposal.fiscal_year = 2018; // 2018 is not in the getFiscalYears data set
setupTest(proposal);
base.expectInputReadonly(Input.FiscalYear);
}));
it("When george is changed, then a request is made to update owners and sale number and owners and sale number are updated", fakeAsync(() => {
setupTest();
base.flushAllRequests();
base.setFormElement(Input.George, "380");
base.expectDisabled(base.getFormElement(Input.SaleName));
base.expectSingleRequest(base.verifyRequest(`/proposal/${base.hostComp.proposal.proposal_id}/get-sale-number-and-owners-after-george-change/380`, "GET"), getChangeGeorgeResponse());
expect(getSaleNumberPrefix()).toEqual("12 -"); // for input prefix
expect(base.comp.formComponent.data.sale_num).toEqual("12-999-18"); // for input view value
// Saving state set correctly after response. Able to change inputs after save.
base.expectNotDisabled(base.getFormElement(Input.SaleName));
expect(base.getInputFormControl(Input.Owner).value).toEqual(base.hostComp.proposal.owner);
const expectedOwnerOptions = getResponseOwners().filter(owner => owner.user_id !== 0).map(owner => owner.user_name);
const actual = base.getSelectOptionValues(base.getFormElement(Input.Owner), false);
expect(actual).toEqual(expectedOwnerOptions);
}));
// Validations
it("When editing sale number and saving, server side validation enforcing uniqueness shows validation error", fakeAsync(() => {
const proposal = getProposal();
setupTest();
// action
base.setFormElement(Input.SaleNumber, "12345");
base.flushAllRequests();
base.clickSaveButton();
base.expectSingleRequest(base.verifyRequest(`/proposal/${proposal.proposal_id}`, "PUT"), getFailedSaveResponse());
// expect
base.expectValidation(Input.SaleNumber);
base.fixForTimerInQueueError();
}));
it("When users edits sale number, then unable to enter anything other than format xxx - xx", fakeAsync(() => {
setupTest(getProposal());
// invalid input
base.setFormElement(Input.SaleNumber, "tester");
expect(base.getFormElementValue(Input.SaleNumber)).toEqual(``);
// valid input, expect formatting
base.setFormElement(Input.SaleNumber, "12345");
expect(base.getFormElementValue(Input.SaleNumber)).toEqual(`123-45`);
}));
it("When editing sale number, users are able to enter all 5 numbers or no numbers at all", fakeAsync(() => {
let proposal = { proposal_status_code: ProposalStatusCodeEnum.Draft, george_id: 280, sale_num_first_2: 11 } as Proposal;
setupTest(proposal);
// valid entry, 5 numbers
base.setAndValidateFormElement(Input.SaleNumber, "12345", "123-45");
// invalid entry, validtion check should fail
base.setAndValidateFormElement(Input.SaleNumber, "123", "123-", true);
// valid entry, empty
base.setAndValidateFormElement(Input.SaleNumber, null, "");
}));
// Permissions
it("When proposal is in Draft status and user has PROPOSAL_MANAGE permission, then can edit all fields", fakeAsync(() => {
base.giveUserGeorgePermissions(["PROPOSAL_MANAGE"]);
let proposal = { proposal_status_code: ProposalStatusCodeEnum.Draft, george_id: 280, sale_num_first_2: 11 } as Proposal;
setupTest(proposal);
base.expectFieldsToBeEditable([Input.George, Input.Owner, Input.BidType, Input.PaymentMethod, Input.SaleName, Input.SaleNumber]);
}));
it("When proposal is in Draft status and user has PROPOSAL_REVIEW permission, then can edit all fields", fakeAsync(() => {
base.giveUserGeorgePermissions(["PROPOSAL_REVIEW"]);
let proposal = { proposal_status_code: ProposalStatusCodeEnum.Draft, george_id: 280, sale_num_first_2: 11 } as Proposal;
setupTest(proposal);
base.expectFieldsToBeEditable([Input.George, Input.Owner, Input.BidType, Input.PaymentMethod, Input.SaleName, Input.SaleNumber]);
}));
it("When proposal status is between Draft and Reviewing and user has PROPOSAL_REVIEW permission, then can edit some fields", fakeAsync(() => {
base.giveUserGeorgePermissions(["PROPOSAL_REVIEW"]);
// spyOn((base.comp as any).proposalStatusService, "canEditProposal").and.returnValue(true);
let proposal = { proposal_status_code: ProposalStatusCodeEnum.Reviewing, george_id: 280, sale_num_first_2: 11 } as Proposal;
setupTest(proposal);
base.expectFieldsToBeEditable([Input.BidType, Input.PaymentMethod, Input.SaleName]);
}));
it("When proposal status is between Draft and Reviewing and user has PROPOSAL_MANAGE permission, then cannot edit anything", fakeAsync(() => {
base.giveUserGeorgePermissions(["PROPOSAL_MANAGE"]);
// spyOn((base.comp as any).proposalStatusService, "canEditProposal").and.returnValue(true);
let proposal = { proposal_status_code: ProposalStatusCodeEnum.Reviewing, george_id: 280, sale_num_first_2: 11 } as Proposal;
setupTest(proposal);
base.expectFieldsToBeEditable([]);
}));
function setupTest(
proposal: Proposal = getProposal(),
editing: boolean = true,
bidTypes: ApiResult<BidType[]> = getBidTypes(),
georges: ApiResult<GeorgeOfficeDto[]> = getProposalManageGeorges(),
proposalOwners: ApiResult<User[]> = getProposalOwners(),
fiscalYears: ApiResult<string[]> = getFiscalYears(),
paymentTypes: ApiResult<PaymentType[]> = getPaymentTypes(),
proposalStatuses: any = getProposalStatuses(),
proposalCertStatuses: any = getProposalCertStatuses()) {
// component setup
base.comp.isPristine = true;
base.comp.isValid = true;
base.comp.editing = editing;
base.hostComp.setupTestData(proposal);
// setup loadable data services
spyOn(base.hostComp.bidTypeService, "loadFunction").and.returnValue(of(bidTypes));
spyOn(base.hostComp.georgeService, "loadFunction").and.returnValue(of(georges));
spyOn(base.hostComp.ownerService, "loadFunction").and.returnValue(of(proposalOwners));
spyOn(base.hostComp.fiscalYearService, "loadFunction").and.returnValue(of(fiscalYears));
spyOn(base.hostComp.paymentTypeService, "loadFunction").and.returnValue(of(paymentTypes));
spyOn(base.hostComp.proposalStatusService, "loadFunction").and.returnValue(of(proposalStatuses));
spyOn(base.hostComp.proposalCertificationStatusService, "loadFunction").and.returnValue(of(proposalCertStatuses));
// initialize inputs
base.formInputs = {
George: { type: TestInputType.Select, id: "george", controlName: "george_id" } as InputTest,
Owner: { type: TestInputType.Select, id: "owner", controlName: "owner" } as InputTest,
FiscalYear: { type: TestInputType.Select, id: "fiscal_year", controlName: "fiscal_year" } as InputTest,
BidType: { type: TestInputType.Select, id: "bid_type", controlName: "bid_type_code" } as InputTest,
PaymentMethod: { type: TestInputType.Select, id: "payment_method", controlName: "payment_type_code" } as InputTest,
SaleName: { type: TestInputType.Input, id: "sale_name", controlName: "sale_name" } as InputTest,
SaleNumber: { type: TestInputType.Input, id: "sale_number", controlName: "sale_num" } as InputTest,
};
base.wait();
base.formGroup = base.comp.formComponent.formGroup;
}
function getSaleNumberPrefix(): string {
const saleNumberInput = base.getFormElement(Input.SaleNumber);
const saleNumberAddOn = saleNumberInput.previousElementSibling;
return saleNumberAddOn.textContent;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment