Skip to content
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
@@ -0,0 +1,11 @@
<pr-dialog-frame
heading="Choose GPS Coordinates"
[confirmDisabled]="!isValid()"
(cancelled)="cancel()"
(confirmed)="save()"
>
<pr-coordinate-map-input
[(coordinates)]="coordinates"
(validityChange)="onValidityChange($event)"
/>
</pr-dialog-frame>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:host {
display: block;
width: 640px;
max-width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
import { GoogleMapsModule } from '@angular/google-maps';
import { CoordinateMapInputComponent } from '@shared/components/coordinate-map-input/coordinate-map-input.component';
import {
CoordinatePickerComponent,
CoordinatePickerData,
} from './coordinate-picker.component';

const LISBON = { latitude: 38.70786, longitude: -9.400139 };

describe('CoordinatePickerComponent', () => {
let fixture: ComponentFixture<CoordinatePickerComponent>;
let component: CoordinatePickerComponent;
let dialogRef: jasmine.SpyObj<DialogRef>;

const setUp = async (dialogData: CoordinatePickerData): Promise<void> => {
dialogRef = jasmine.createSpyObj('DialogRef', ['close']);

await TestBed.configureTestingModule({
imports: [CoordinatePickerComponent],
providers: [
{ provide: DIALOG_DATA, useValue: dialogData },
{ provide: DialogRef, useValue: dialogRef },
],
})
.overrideComponent(CoordinateMapInputComponent, {
remove: { imports: [GoogleMapsModule] },
add: { schemas: [CUSTOM_ELEMENTS_SCHEMA] },
})
.compileComponents();

fixture = TestBed.createComponent(CoordinatePickerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
};

const query = <T extends HTMLElement>(selector: string): T =>
fixture.nativeElement.querySelector(selector);

describe('with nothing to start from', () => {
beforeEach(async () => {
await setUp({});
});

it('should render a titled dialog', () => {
expect(query('.pr-dialog-header h2').textContent.trim()).toBe(
'Choose GPS Coordinates',
);
});

it('should hand the map input nothing to start from', () => {
expect(component.coordinates()).toBeNull();
});
});

describe('when the location already has coordinates', () => {
beforeEach(async () => {
await setUp({ location: { ...LISBON, city: 'Lisbon' } });
});

it('should hand the stored pair to the map input', () => {
expect(component.coordinates()).toEqual(LISBON);
});

it('should keep the address it was given when saving', () => {
component.save();

expect(dialogRef.close).toHaveBeenCalledWith({
location: { ...LISBON, city: 'Lisbon' },
});
});

it('should clear the stored pair when the map input reports none', () => {
component.coordinates.set(null);
component.save();

expect(dialogRef.close).toHaveBeenCalledWith({
location: { latitude: null, longitude: null, city: 'Lisbon' },
});
});
});

describe('when the location has an address but no coordinates', () => {
beforeEach(async () => {
await setUp({ location: { city: 'Lisbon' } });
});

it('should still carry the address through a save', () => {
component.save();

expect(dialogRef.close).toHaveBeenCalledWith({
location: { city: 'Lisbon', latitude: null, longitude: null },
});
});
});

describe('when the map input reports unreadable text', () => {
beforeEach(async () => {
await setUp({ location: { ...LISBON } });
});

it('should disable the confirm button', () => {
component.onValidityChange(false);
fixture.detectChanges();

expect(query<HTMLButtonElement>('.pr-btn-confirm').disabled).toBeTrue();
});

it('should refuse to save the pair the field is no longer showing', () => {
component.onValidityChange(false);
component.save();

expect(dialogRef.close).not.toHaveBeenCalled();
});
});

describe('leaving the dialog', () => {
beforeEach(async () => {
await setUp({});
});

it('should close with nothing when cancelled', () => {
query<HTMLButtonElement>('.pr-btn-cancel').click();

expect(dialogRef.close).toHaveBeenCalledWith();
});

it('should close with nothing when dismissed from the header', () => {
query<HTMLButtonElement>('.pr-close-button').click();

expect(dialogRef.close).toHaveBeenCalledWith();
});

it('should close with the pair when saved', () => {
component.coordinates.set(LISBON);
query<HTMLButtonElement>('.pr-btn-confirm').click();

expect(dialogRef.close).toHaveBeenCalledWith({ location: LISBON });
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Component, Inject, OnInit, Optional, signal } from '@angular/core';
import { DIALOG_DATA, DialogRef } from '@angular/cdk/dialog';
import { LocnVOData } from '@models';
import { CoordinateMapInputComponent } from '@shared/components/coordinate-map-input/coordinate-map-input.component';
import { DialogFrameComponent } from '@shared/components/dialog-frame/dialog-frame.component';
import {
Coordinates,
coordinatesFromLocation,
} from '@shared/utilities/coordinates';

export interface CoordinatePickerData {
location?: LocnVOData;
}

export interface CoordinatePickerResult {
location: LocnVOData;
}

@Component({
selector: 'pr-coordinate-picker',
standalone: true,
imports: [CoordinateMapInputComponent, DialogFrameComponent],
templateUrl: './coordinate-picker.component.html',
styleUrls: ['./coordinate-picker.component.scss'],
})
export class CoordinatePickerComponent implements OnInit {
coordinates = signal<Coordinates | null>(null);
isValid = signal(true);

private locationBeingEdited: LocnVOData = {};

constructor(
@Optional()
@Inject(DIALOG_DATA)
public dialogData?: CoordinatePickerData,
@Optional() private dialogRef?: DialogRef<CoordinatePickerResult>,
) {}

ngOnInit(): void {
const existing = this.dialogData?.location;
if (!existing) {
return;
}
this.locationBeingEdited = { ...existing };
this.coordinates.set(coordinatesFromLocation(existing));
}

public onValidityChange(isValid: boolean): void {
this.isValid.set(isValid);
}

public cancel(): void {
this.dialogRef?.close();
}

public save(): void {
Comment thread
aasandei-vsp marked this conversation as resolved.
if (!this.isValid()) {
return;
}
const coordinates = this.coordinates();
this.dialogRef?.close({
location: {
...this.locationBeingEdited,
latitude: coordinates?.latitude ?? null,
longitude: coordinates?.longitude ?? null,
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import { MessageService } from '@shared/services/message/message.service';
import { EditService } from '@core/services/edit/edit.service';
import { ProfileItemVOData } from '@models/profile-item-vo';
import { ProfileService } from '@shared/services/profile/profile.service';

const DEFAULT_ZOOM = 12;
const DEFAULT_CENTER: google.maps.LatLngLiteral = {
lat: 39.8333333,
lng: -98.585522,
};
import {
CONTINENTAL_US_CENTER,
LOCATED_ZOOM,
WHOLE_COUNTRY_ZOOM,
} from '@shared/utilities/map-view';

@Component({
selector: 'pr-location-picker',
Expand All @@ -41,15 +40,15 @@ export class LocationPickerComponent implements OnInit, AfterViewInit {
@Input() archive: ArchiveVO;

mapOptions: google.maps.MapOptions = {
zoom: DEFAULT_ZOOM,
zoom: LOCATED_ZOOM,
streetViewControl: false,
fullscreenControl: false,
mapTypeControl: false,
clickableIcons: false,
};

zoom = 4;
center: google.maps.LatLng = new google.maps.LatLng(DEFAULT_CENTER);
zoom = WHOLE_COUNTRY_ZOOM;
center: google.maps.LatLng = new google.maps.LatLng(CONTINENTAL_US_CENTER);

height: string;
width: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<google-map
[height]="height()"
width="100%"
[options]="mapOptions"
(mapClick)="onMapClick($event)"
>
@if (markerPosition(); as position) {
<map-marker [position]="position" [options]="markerOptions" />
}
</google-map>

<div class="pr-coordinate-field">
<pr-icon-text-input
Comment thread
aasandei-vsp marked this conversation as resolved.
[icon]="coordinateIcon"
[label]="label()"
[value]="coordinateText()"
[invalid]="!isValid()"
(valueChange)="onCoordinateTextChange($event)"
/>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import 'colors';

:host {
display: block;
position: relative;
}

.pr-coordinate-field {
position: absolute;
top: 16px;
left: 16px;
right: 16px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba($PR-brand-black, 0.16);
}
Loading
Loading