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
Expand Up @@ -131,6 +131,7 @@ describe('MapComponent', () => {
getCenter: jasmine.createSpy('getCenter'),
setView: jasmine.createSpy('setView'),
fitBounds: jasmine.createSpy('fitBounds'),
closePopup: jasmine.createSpy('closePopup'),
controls: { bottomleft: { addTo: jasmine.createSpy('addTo') } },
},
layerIds: ['a', 'b'],
Expand All @@ -147,6 +148,7 @@ describe('MapComponent', () => {
(L as any).markerClusterGroup = () => ({
addLayer: jasmine.createSpy('addLayer'),
clearLayers: jasmine.createSpy('clearLayers'),
getLayers: jasmine.createSpy('getLayers').and.returnValue([]),
});
spyOn(L.Control.prototype, 'addTo').and.callFake(function (this: any) {
return this;
Expand Down Expand Up @@ -557,11 +559,13 @@ describe('MapComponent', () => {
mockMap = {
addLayer: jasmine.createSpy(),
removeLayer: jasmine.createSpy(),
closePopup: jasmine.createSpy(),
};
mapServiceMock.getSMKInstance.and.returnValue({ $viewer: { map: mockMap } });
(component as any).markersClusterGroup = {
clearLayers: jasmine.createSpy(),
addLayer: jasmine.createSpy(),
getLayers: jasmine.createSpy().and.returnValue([]),
};
spyOn(console, 'log');
spyOn(console, 'error');
Expand Down Expand Up @@ -657,6 +661,7 @@ describe('MapComponent', () => {
addLayer: jasmine.createSpy('addLayer'),
removeLayer: jasmine.createSpy('removeLayer'),
hasLayer: jasmine.createSpy('hasLayer').and.returnValue(false),
closePopup: jasmine.createSpy('closePopup')
};
// make SMK return our mock map
mapServiceMock.getSMKInstance.and.returnValue({ $viewer: { map: mockMap } });
Expand All @@ -665,6 +670,8 @@ describe('MapComponent', () => {
(component as any).markersClusterGroup = {
clearLayers: jasmine.createSpy('clearLayers'),
addLayer: jasmine.createSpy('addLayer'),
closePopup: jasmine.createSpy('closePopup'),
getLayers: jasmine.createSpy('getLayers').and.returnValue([]),
};


Expand All @@ -676,7 +683,7 @@ describe('MapComponent', () => {
it('creates project & activity layers with unique projectGuids and adds them to map', () => {
const locs = [
{ projectGuid: 'a', latitude: 1, longitude: 2 },
{ projectGuid: 'a', latitude: 3, longitude: 4 },
{ projectGuid: 'a', latitude: 3, longitude: 4 },
{ projectGuid: 'b', latitude: 5, longitude: 6 },
{ projectGuid: undefined, latitude: 7, longitude: 8 }, // invalid -> filtered out
];
Expand Down Expand Up @@ -749,5 +756,130 @@ describe('MapComponent', () => {
});
});

describe('teardownActiveUI()', () => {
let mapMock: any;

beforeEach(() => {
mapMock = { closePopup: jasmine.createSpy('closePopup') };
mapServiceMock.getSMKInstance.and.returnValue({ $viewer: { map: mapMock } });
});

it('closes global popup and removes active marker via cluster group when present', () => {
const activeMarkerSpy = jasmine.createSpyObj<L.Marker>('Marker', [
'unbindPopup', 'remove'
]);
Object.setPrototypeOf(activeMarkerSpy, L.Marker.prototype);

const clusterMock = {
addLayer: jasmine.createSpy('addLayer'),
clearLayers: jasmine.createSpy('clearLayers'),
removeLayer: jasmine.createSpy('removeLayer'),
hasLayer: jasmine.createSpy('hasLayer').and.returnValue(true),
getLayers: jasmine.createSpy('getLayers').and.returnValue([]),
};

(component as any).markersClusterGroup = clusterMock as any;
(component as any).activeMarker = activeMarkerSpy;

(component as any).teardownActiveUI();

expect(mapMock.closePopup).toHaveBeenCalled();
expect(activeMarkerSpy.unbindPopup).toHaveBeenCalled();
expect(clusterMock.removeLayer).toHaveBeenCalledWith(activeMarkerSpy);
expect((component as any).activeMarker).toBeNull();
});

it('closes global popup and removes active marker directly when not in cluster', () => {
const activeMarkerSpy = jasmine.createSpyObj<L.Marker>('Marker', [
'unbindPopup', 'remove'
]);
Object.setPrototypeOf(activeMarkerSpy, L.Marker.prototype);

const clusterMock = {
addLayer: jasmine.createSpy('addLayer'),
clearLayers: jasmine.createSpy('clearLayers'),
removeLayer: jasmine.createSpy('removeLayer'),
hasLayer: jasmine.createSpy('hasLayer').and.returnValue(false),
getLayers: jasmine.createSpy('getLayers').and.returnValue([]),
};

(component as any).markersClusterGroup = clusterMock as any;
(component as any).activeMarker = activeMarkerSpy;

(component as any).teardownActiveUI();

expect(mapMock.closePopup).toHaveBeenCalled();
expect(activeMarkerSpy.unbindPopup).toHaveBeenCalled();
expect(activeMarkerSpy.remove).toHaveBeenCalled();
expect(clusterMock.removeLayer).not.toHaveBeenCalled();
expect((component as any).activeMarker).toBeNull();
});
});

describe('updateProjectMarkersFromLocations() teardown before clear', () => {
it('unbinds/closes popups and removes listeners for each marker before clearing cluster', () => {
const mapMock = { addLayer: () => { }, removeLayer: () => { }, closePopup: () => { } };
mapServiceMock.getSMKInstance.and.returnValue({ $viewer: { map: mapMock } });

const m1 = jasmine.createSpyObj<L.Marker>('Marker', ['unbindPopup', 'closePopup', 'off']);
const m2 = jasmine.createSpyObj<L.Marker>('Marker', ['unbindPopup', 'closePopup', 'off']);
Object.setPrototypeOf(m1, L.Marker.prototype);
Object.setPrototypeOf(m2, L.Marker.prototype);

(component as any).markersClusterGroup = {
addLayer: jasmine.createSpy('addLayer'),
clearLayers: jasmine.createSpy('clearLayers'),
removeLayer: jasmine.createSpy('removeLayer'),
hasLayer: jasmine.createSpy('hasLayer').and.returnValue(false),
getLayers: jasmine.createSpy('getLayers').and.returnValue([m1, m2]),
} as any;

(component as any).updateProjectMarkersFromLocations([]);

expect(m1.unbindPopup).toHaveBeenCalled();
expect(m1.closePopup).toHaveBeenCalled();
expect(m1.off).toHaveBeenCalled();

expect(m2.unbindPopup).toHaveBeenCalled();
expect(m2.closePopup).toHaveBeenCalled();
expect(m2.off).toHaveBeenCalled();

expect((component as any).markersClusterGroup.clearLayers).toHaveBeenCalled();
});
});

describe('filtering removes selected/active project', () => {
it('tears down active marker and closes map popup when selected project is no longer in results', () => {
const mapMock = {
addLayer: jasmine.createSpy('addLayer'),
removeLayer: jasmine.createSpy('removeLayer'),
closePopup: jasmine.createSpy('closePopup'),
};
mapServiceMock.getSMKInstance.and.returnValue({ $viewer: { map: mapMock } });

const activeMarkerSpy = jasmine.createSpyObj<L.Marker>('Marker', ['unbindPopup', 'remove']);
Object.setPrototypeOf(activeMarkerSpy, L.Marker.prototype);

const clusterMock = {
addLayer: jasmine.createSpy('addLayer'),
clearLayers: jasmine.createSpy('clearLayers'),
removeLayer: jasmine.createSpy('removeLayer'),
hasLayer: jasmine.createSpy('hasLayer').and.returnValue(true),
getLayers: jasmine.createSpy('getLayers').and.returnValue([]),
};

(component as any).markersClusterGroup = clusterMock as any;
(component as any).activeMarker = activeMarkerSpy;
(component as any).selectedProject = { projectGuid: 'to-remove' };

(component as any).updateProjectMarkersFromLocations([
{ projectGuid: 'test-guid', latitude: 1, longitude: 2 },
]);

expect(mapMock.closePopup).toHaveBeenCalled();
expect(clusterMock.removeLayer).toHaveBeenCalledWith(activeMarkerSpy);
expect((component as any).activeMarker).toBeNull();
});
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ export class MapComponent implements AfterViewInit, OnDestroy {
}

private updateProjectMarkersFromLocations(locations: ProjectLocation[]): void {
this.teardownActiveUI();

const smk = this.mapService.getSMKInstance();
const map = smk?.$viewer?.map;

Expand All @@ -275,7 +277,16 @@ export class MapComponent implements AfterViewInit, OnDestroy {
return;
}

this.markersClusterGroup.clearLayers();
if (this.markersClusterGroup) {
this.markersClusterGroup.getLayers().forEach(l => {
if (l instanceof L.Marker) {
l.closePopup();
l.unbindPopup();
l.off();
}
});
this.markersClusterGroup.clearLayers();
}
this.projectMarkerMap.clear();
this.activeMarker = null;

Expand Down Expand Up @@ -429,4 +440,28 @@ export class MapComponent implements AfterViewInit, OnDestroy {
});
}

// Close any open popup and fully remove the active marker so no DOM is orphaned
private teardownActiveUI(): void {
const smk = this.mapService.getSMKInstance();
const map: L.Map | undefined = smk?.$viewer?.map;

map?.closePopup();

// Remove the active marker and unbind popup to drop DOM from panes
try {
if (this.activeMarker) {
this.activeMarker.unbindPopup();
if (this.markersClusterGroup?.hasLayer(this.activeMarker)) {
this.markersClusterGroup.removeLayer(this.activeMarker);
} else {
this.activeMarker.remove();
}
}
} catch (err) {
console.error('Error during teardown:', err);
} finally {
this.activeMarker = null;
}
}

}
Loading