Skip to content

Commit

Permalink
Merge pull request #1838 from fecgov/feature/354
Browse files Browse the repository at this point in the history
Feature/354
  • Loading branch information
mjtravers authored Apr 23, 2024
2 parents 5695de8 + 1a6bef3 commit a3f4c46
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,33 +1,38 @@
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { EventEmitter } from '@angular/core';
import { of } from 'rxjs';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { provideMockStore } from '@ngrx/store/testing';
import { SharedModule } from 'app/shared/shared.module';
import { ContactService } from 'app/shared/services/contact.service';
import { testContact, testMockStore } from 'app/shared/utils/unit-test.utils';
import { DropdownModule } from 'primeng/dropdown';
import { AutoCompleteModule } from 'primeng/autocomplete';
import { DialogModule } from 'primeng/dialog';
import { ContactLookupComponent } from './contact-lookup.component';
import { LabelPipe } from '../../pipes/label.pipe';
import { LabelUtils } from 'app/shared/utils/label.utils';
import { Candidate } from 'app/shared/models/candidate.model';
import {
CandidateLookupResponse,
CandidateOfficeTypes,
CommitteeLookupResponse,
Contact,
ContactTypeLabels,
ContactTypes,
FecApiCandidateLookupData,
FecApiCommitteeLookupData,
FecfileCandidateLookupData,
FecfileCommitteeLookupData,
FecfileIndividualLookupData,
FecfileOrganizationLookupData,
IndividualLookupResponse,
OrganizationLookupResponse,
OrganizationLookupResponse
} from 'app/shared/models/contact.model';
import { ContactService } from 'app/shared/services/contact.service';
import { FecApiService } from 'app/shared/services/fec-api.service';
import { SharedModule } from 'app/shared/shared.module';
import { LabelUtils } from 'app/shared/utils/label.utils';
import { testContact, testMockStore } from 'app/shared/utils/unit-test.utils';
import { SelectItem } from 'primeng/api';
import { AutoCompleteModule } from 'primeng/autocomplete';
import { DialogModule } from 'primeng/dialog';
import { DropdownModule } from 'primeng/dropdown';
import { of } from 'rxjs';
import { LabelPipe } from '../../pipes/label.pipe';
import { ContactLookupComponent } from './contact-lookup.component';

describe('ContactLookupComponent', () => {
let component: ContactLookupComponent;
Expand All @@ -47,7 +52,14 @@ describe('ContactLookupComponent', () => {
AutoCompleteModule,
SharedModule,
],
providers: [FormBuilder, ContactService, EventEmitter, provideMockStore(testMockStore)],
providers: [
FormBuilder,
ContactService,
FecApiService,
EventEmitter,
provideMockStore(testMockStore),
HttpClient,
],
}).compileComponents();

testContactService = TestBed.inject(ContactService);
Expand Down Expand Up @@ -121,7 +133,7 @@ describe('ContactLookupComponent', () => {
tick(500);
expect(
JSON.stringify(component.contactLookupList) ===
JSON.stringify(testCandidateLookupResponse.toSelectItemGroups(true)),
JSON.stringify(testCandidateLookupResponse.toSelectItemGroups(true)),
).toBeTrue();
expect(
JSON.stringify([
Expand Down Expand Up @@ -183,7 +195,7 @@ describe('ContactLookupComponent', () => {
component.onDropdownSearch(testEvent);
expect(
JSON.stringify(component.contactLookupList) ===
JSON.stringify(testCommitteeLookupResponse.toSelectItemGroups(true)),
JSON.stringify(testCommitteeLookupResponse.toSelectItemGroups(true)),
).toBeTrue();
expect(
JSON.stringify([
Expand Down Expand Up @@ -257,7 +269,7 @@ describe('ContactLookupComponent', () => {
tick(500);
expect(
JSON.stringify(component.contactLookupList) ===
JSON.stringify(testOrganizationLookupResponse.toSelectItemGroups()),
JSON.stringify(testOrganizationLookupResponse.toSelectItemGroups()),
).toBeTrue();
expect(
JSON.stringify([
Expand All @@ -282,6 +294,100 @@ describe('ContactLookupComponent', () => {
expect(eventEmitterEmitSpy).toHaveBeenCalledOnceWith(testContact);
}));

it('#onFecApiCandidateLookupDataSelect candidate name only', fakeAsync(() => {
const testCandidate = Candidate.fromJSON({
candidate_id: 'P80000722',
name: 'BIDEN, JOSEPH R JR',
});
const eventEmitterEmitSpy = spyOn(component.contactLookupSelect, 'emit');
const getCandidateDetailsSpy = spyOn(component.fecApiService,
'getCandidateDetails').and.returnValue(of(testCandidate));
const testFecApiCandidateLookupData: FecApiCandidateLookupData = {
candidate_id: 'P80000722',
office: 'P',
name: 'BIDEN, JOSEPH R JR',
toSelectItem(): SelectItem<FecApiCandidateLookupData> {
return {
label: `${this.name} (${this.candidate_id})`,
value: this,
};
}
}
component.onFecApiCandidateLookupDataSelect(testFecApiCandidateLookupData);
tick(500);
expect(getCandidateDetailsSpy).toHaveBeenCalledOnceWith(
testFecApiCandidateLookupData.candidate_id!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
expect(eventEmitterEmitSpy).toHaveBeenCalledOnceWith(
Contact.fromJSON({
type: ContactTypes.CANDIDATE,
candidate_id: 'P80000722',
last_name: 'BIDEN',
first_name: 'JOSEPH R JR',
middle_name: undefined,
prefix: undefined,
suffix: undefined,
street_1: undefined,
street_2: undefined,
city: undefined,
state: undefined,
zip: undefined,
employer: '',
occupation: '',
candidate_office: undefined,
candidate_state: undefined,
candidate_district: undefined,
}));
}));

it('#onFecApiCandidateLookupDataSelect candidate last_name and first_name', fakeAsync(() => {
const testCandidate = Candidate.fromJSON({
candidate_id: 'P80000722',
candidate_first_name: 'test_candidate_first_name',
candidate_last_name: 'test_candidate_last_name',
candidate_middle_name: 'test_candidate_middle_name',
candidate_prefix: 'test_candidate_prefix',
candidate_suffix: 'test_candidate_suffix',
});
const eventEmitterEmitSpy = spyOn(component.contactLookupSelect, 'emit');
const getCandidateDetailsSpy = spyOn(component.fecApiService,
'getCandidateDetails').and.returnValue(of(testCandidate));
const testFecApiCandidateLookupData: FecApiCandidateLookupData = {
candidate_id: 'P80000722',
office: 'P',
name: 'BIDEN, JOSEPH R JR',
toSelectItem(): SelectItem<FecApiCandidateLookupData> {
return {
label: `${this.name} (${this.candidate_id})`,
value: this,
};
}
}
component.onFecApiCandidateLookupDataSelect(testFecApiCandidateLookupData);
tick(500);
expect(getCandidateDetailsSpy).toHaveBeenCalledOnceWith(
testFecApiCandidateLookupData.candidate_id!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
expect(eventEmitterEmitSpy).toHaveBeenCalledOnceWith(
Contact.fromJSON({
type: ContactTypes.CANDIDATE,
candidate_id: 'P80000722',
last_name: 'test_candidate_last_name',
first_name: 'test_candidate_first_name',
middle_name: 'test_candidate_middle_name',
prefix: 'test_candidate_prefix',
suffix: 'test_candidate_suffix',
street_1: undefined,
street_2: undefined,
city: undefined,
state: undefined,
zip: undefined,
employer: '',
occupation: '',
candidate_office: undefined,
candidate_state: undefined,
candidate_district: undefined,
}));
}));

it('#onCreateNewContactSelect Contact happy path', fakeAsync(() => {
const eventEmitterEmitSpy = spyOn(component.createNewContactSelect, 'emit');
component.onCreateNewContactSelect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class ContactLookupComponent extends DestroyerComponent implements OnInit

constructor(
private contactService: ContactService,
private fecApiService: FecApiService,
public fecApiService: FecApiService,
) {
super();
}
Expand Down Expand Up @@ -165,17 +165,18 @@ export class ContactLookupComponent extends DestroyerComponent implements OnInit
onFecApiCandidateLookupDataSelect(data: FecApiCandidateLookupData) {
if (data.candidate_id) {
this.fecApiService.getCandidateDetails(data.candidate_id).subscribe((candidate) => {
// TODO: fix once we get info from api and set all names below properly
const nameSplit = candidate.name?.split(', ');
this.contactLookupSelect.emit(
Contact.fromJSON({
type: ContactTypes.CANDIDATE,
candidate_id: candidate.candidate_id,
last_name: nameSplit?.[0],
first_name: nameSplit?.[1],
middle_name: '',
prefix: '',
suffix: '',
last_name: (candidate.candidate_first_name && candidate.candidate_last_name ?
candidate.candidate_last_name : nameSplit?.[0]), // namesplit to account for paper filers
first_name: (candidate.candidate_first_name && candidate.candidate_last_name ?
candidate.candidate_first_name : nameSplit?.[1]), // namesplit to account for paper filers
middle_name: candidate.candidate_middle_name,
prefix: candidate.candidate_prefix,
suffix: candidate.candidate_suffix,
street_1: candidate.address_street_1,
street_2: candidate.address_street_2,
city: candidate.address_city,
Expand Down
5 changes: 5 additions & 0 deletions front-end/src/app/shared/models/candidate.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ export class Candidate extends BaseModel {
address_street_1: string | undefined;
address_street_2: string | undefined;
address_zip: string | undefined;
candidate_first_name: string | undefined;
candidate_id: string | undefined;
candidate_inactive: boolean | undefined;
candidate_last_name: string | undefined;
candidate_middle_name: string | undefined;
candidate_prefix: string | undefined;
candidate_status: string | undefined;
candidate_suffix: string | undefined;
cycles: number[] = [];
district: string | undefined;
district_number: number | undefined;
Expand Down
14 changes: 2 additions & 12 deletions front-end/src/app/shared/services/fec-api.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { TestBed } from '@angular/core/testing';
import { provideMockStore } from '@ngrx/store/testing';
import { CommitteeAccount } from 'app/shared/models/committee-account.model';
import { FecApiPaginatedResponse } from 'app/shared/models/fec-api.model';
import { Candidate } from '../models/candidate.model';
import { FecFiling } from '../models/fec-filing.model';
import { testMockStore } from '../utils/unit-test.utils';
import { FecApiService } from './fec-api.service';
import { Candidate } from '../models/candidate.model';

describe('FecApiService', () => {
let httpTestingController: HttpTestingController;
Expand All @@ -28,16 +28,6 @@ describe('FecApiService', () => {
describe('#getCandidateDetails()', () => {
it('should return candidate details', () => {
const candidate: Candidate = new Candidate();
const response: FecApiPaginatedResponse = {
api_version: '1.0',
pagination: {
page: 1,
per_page: 20,
count: 1,
pages: 1,
},
results: [candidate],
};

service.getCandidateDetails('P12345678').subscribe((candidateData) => {
expect(candidateData).toEqual(candidate);
Expand All @@ -48,7 +38,7 @@ describe('FecApiService', () => {
);

expect(req.request.method).toEqual('GET');
req.flush(response);
req.flush(candidate);
});
});

Expand Down
10 changes: 3 additions & 7 deletions front-end/src/app/shared/services/fec-api.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { firstValueFrom, map, Observable } from 'rxjs';
import { Candidate } from '../models/candidate.model';
import { CommitteeAccount } from '../models/committee-account.model';
import { FecApiPaginatedResponse } from '../models/fec-api.model';
import { Candidate } from '../models/candidate.model';
import { FecFiling } from '../models/fec-filing.model';
import { ApiService } from './api.service';

Expand All @@ -16,7 +16,7 @@ export class FecApiService {
constructor(
private http: HttpClient,
private apiService: ApiService,
) {}
) { }

getHeaders() {
return {
Expand All @@ -33,11 +33,7 @@ export class FecApiService {
if (!candidate_id) {
throw new Error('Fecfile: No Candidate Id provided in getCandidateDetails()');
}
return this.apiService
.get<FecApiPaginatedResponse>('/contacts/candidate/', {
candidate_id,
})
.pipe(map((response: FecApiPaginatedResponse) => (response.results[0] as Candidate) || undefined));
return this.apiService.get<Candidate>('/contacts/candidate/', { candidate_id });
}

/**
Expand Down

0 comments on commit a3f4c46

Please sign in to comment.