Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/354 #1838

Merged
merged 8 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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