Skip to content

Commit 1d7f944

Browse files
committed
e2e(geospatial): support api key assets, implement geospatial fixtures and test example
replace silos protocol to support geospatial interface key
1 parent 5459e13 commit 1d7f944

File tree

11 files changed

+469
-14
lines changed

11 files changed

+469
-14
lines changed

lib/interviewer/Interfaces/Geospatial/Geospatial.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import {
22
entityPrimaryKeyProperty,
33
type VariableValue,
44
} from '@codaco/shared-consts';
5-
import { type ExtendedMapOptions } from '~/lib/interviewer/Interfaces/Geospatial/useMapbox';
65
import { type Action } from '@reduxjs/toolkit';
76
import { LocateFixed, ZoomIn, ZoomOut } from 'lucide-react';
7+
import { type ExtendedMapOptions } from '~/lib/interviewer/Interfaces/Geospatial/useMapbox';
88
// import 'mapbox-gl/dist/mapbox-gl.css';
99
import { AnimatePresence, motion, type Variants } from 'motion/react';
1010
import dynamic from 'next/dynamic';
@@ -20,8 +20,8 @@ import { updateNode as updateNodeAction } from '~/lib/interviewer/ducks/modules/
2020
import useBeforeNext from '~/lib/interviewer/hooks/useBeforeNext';
2121
import usePropSelector from '~/lib/interviewer/hooks/usePropSelector';
2222
import useReadyForNextStage from '~/lib/interviewer/hooks/useReadyForNextStage';
23-
import CollapsablePrompts from '~/lib/interviewer/Interfaces/Sociogram/CollapsablePrompts';
2423
import { useMapbox } from '~/lib/interviewer/Interfaces/Geospatial/useMapbox';
24+
import CollapsablePrompts from '~/lib/interviewer/Interfaces/Sociogram/CollapsablePrompts';
2525
import { getNetworkNodesForType } from '~/lib/interviewer/selectors/session';
2626
import { type RootState } from '~/lib/interviewer/store';
2727
import { type Direction, type StageProps } from '~/lib/interviewer/types';
@@ -130,6 +130,8 @@ export default function GeospatialInterface({
130130
mapContainerRef,
131131
mapRef,
132132
accessToken,
133+
isMapLoaded,
134+
zoomLevel,
133135
handleResetMapZoom,
134136
handleZoomIn,
135137
handleZoomOut,
@@ -230,14 +232,19 @@ export default function GeospatialInterface({
230232
return (
231233
<div className="relative flex size-full flex-col" ref={dragSafeRef}>
232234
<motion.div
233-
id="map-container"
234235
className="size-full"
235236
ref={mapContainerRef}
236237
variants={fadeVariants}
238+
data-testid="map-container"
239+
data-map-loaded={isMapLoaded}
240+
data-zoom-level={zoomLevel}
237241
>
238242
{/* if outside-selectable-areas, add an overlay */}
239243
{initialSelectionValue === 'outside-selectable-areas' && (
240-
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center">
244+
<div
245+
className="absolute inset-0 z-10 flex flex-col items-center justify-center"
246+
data-testid="outside-selectable-overlay"
247+
>
241248
<div className="bg-background absolute inset-0 opacity-75" />
242249
<div className="relative z-20 flex w-1/3 flex-col items-center gap-6 text-center">
243250
<h2>
@@ -250,6 +257,7 @@ export default function GeospatialInterface({
250257
setLocationValue(null);
251258
}}
252259
color="primary"
260+
data-testid="deselect-outside-area-button"
253261
>
254262
Deselect
255263
</Button>
@@ -274,6 +282,7 @@ export default function GeospatialInterface({
274282
spacing="none"
275283
elevation="none"
276284
className="bg-surface/60 absolute right-4 bottom-4 z-5 flex flex-col gap-2 rounded-xl p-2 shadow-2xl backdrop-blur-md"
285+
data-testid="map-toolbar"
277286
variants={{
278287
initial: {
279288
opacity: 0,
@@ -291,20 +300,23 @@ export default function GeospatialInterface({
291300
icon={<ZoomIn />}
292301
aria-label="Zoom In"
293302
color="dynamic"
303+
data-testid="map-zoom-in"
294304
/>
295305
<IconButton
296306
size="lg"
297307
onClick={handleZoomOut}
298308
icon={<ZoomOut />}
299309
aria-label="Zoom Out"
300310
color="dynamic"
311+
data-testid="map-zoom-out"
301312
/>
302313
<IconButton
303314
size="lg"
304315
onClick={handleResetMapZoom}
305316
icon={<LocateFixed />}
306317
aria-label="Recenter Map"
307318
color="dynamic"
319+
data-testid="map-recenter"
308320
/>
309321
</MotionSurface>
310322

@@ -333,6 +345,7 @@ export default function GeospatialInterface({
333345
color="primary"
334346
onClick={handleOutsideSelectableAreas}
335347
disabled={initialSelectionValue === 'outside-selectable-areas'}
348+
data-testid="outside-selectable-areas-button"
336349
>
337350
Outside Selectable Areas
338351
</Button>

lib/interviewer/Interfaces/Geospatial/GeospatialSearch.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export default function GeospatialSearch({
244244
value={query}
245245
onChange={handleQueryChange}
246246
onKeyDown={handleInputKeyDown}
247+
data-testid="geospatial-search-input"
247248
// ARIA combobox attributes
248249
role="combobox"
249250
aria-expanded={showSuggestions}
@@ -258,6 +259,7 @@ export default function GeospatialSearch({
258259
size="sm"
259260
onClick={handleClear}
260261
aria-label="Clear search"
262+
data-testid="geospatial-search-clear"
261263
tabIndex={-1}
262264
/>
263265
) : undefined

lib/interviewer/Interfaces/Geospatial/useMapbox.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ export const useMapbox = ({
102102
initialSelectionValue,
103103
onSelectionChange,
104104
}: UseMapboxProps) => {
105-
const mapContainerRef = useRef<HTMLDivElement>(null);
106-
const mapRef = useRef<mapboxgl.Map | null>(null);
107-
const [isMapLoaded, setIsMapLoaded] = useState(false);
108-
109105
const {
110106
center,
111107
initialZoom,
@@ -117,6 +113,11 @@ export const useMapbox = ({
117113
showTransit,
118114
} = mapOptions;
119115

116+
const mapContainerRef = useRef<HTMLDivElement>(null);
117+
const mapRef = useRef<mapboxgl.Map | null>(null);
118+
const [isMapLoaded, setIsMapLoaded] = useState(false);
119+
const [zoomLevel, setZoomLevel] = useState<number>(initialZoom);
120+
120121
// get token value from asset manifest, using id
121122
const getApiAssetKeyValue = useSelector(makeGetApiKeyAssetValue);
122123
const accessToken = useMemo(
@@ -309,6 +310,14 @@ export const useMapbox = ({
309310

310311
mapRef.current.on('load', handleMapStyleLoad);
311312

313+
// Track zoom level changes for testing
314+
mapRef.current.on('zoomend', () => {
315+
const zoom = mapRef.current?.getZoom();
316+
if (zoom !== undefined) {
317+
setZoomLevel(zoom);
318+
}
319+
});
320+
312321
return () => {
313322
mapRef.current?.remove();
314323
};
@@ -436,6 +445,8 @@ export const useMapbox = ({
436445
mapContainerRef,
437446
mapRef,
438447
accessToken,
448+
isMapLoaded,
449+
zoomLevel,
439450
handleResetMapZoom,
440451
handleZoomIn,
441452
handleZoomOut,

tests/e2e/data/silos.netcanvas

-11.6 KB
Binary file not shown.

tests/e2e/fixtures/protocol-fixture.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,25 @@ export class ProtocolFixture {
176176

177177
if (protocol.assetManifest) {
178178
for (const [assetId, asset] of Object.entries(protocol.assetManifest)) {
179-
if (asset.type === 'apikey') continue;
179+
// Handle API key assets (e.g., Mapbox token)
180+
if (asset.type === 'apikey') {
181+
if ('value' in asset && asset.value) {
182+
await this.prisma.asset.create({
183+
data: {
184+
key: createId(),
185+
assetId,
186+
name: asset.name,
187+
type: asset.type,
188+
url: '', // API keys don't have URLs
189+
size: 0,
190+
value: asset.value,
191+
protocols: { connect: { id: protocolId } },
192+
},
193+
});
194+
}
195+
continue;
196+
}
197+
180198
if (!('source' in asset)) continue;
181199

182200
await this.prisma.asset.create({

0 commit comments

Comments
 (0)