Skip to content

Commit

Permalink
fix: Map and Label breaking on back action (#3825)
Browse files Browse the repository at this point in the history
  • Loading branch information
RODO94 authored Oct 25, 2024
1 parent 565613f commit 8ef61d3
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ interface MapAndLabelContextValue {
type MapAndLabelProviderProps = PropsWithChildren<PresentationalProps>;

const MapAndLabelContext = createContext<MapAndLabelContextValue | undefined>(
undefined,
undefined
);

export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
props,
props
) => {
const { schema, children, handleSubmit, previouslySubmittedData, fn } = props;
const { formikConfig, initialValues } = useSchema({
Expand All @@ -62,7 +62,7 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
fn
] as FeatureCollection;
const previousFormData = previousGeojson?.features.map(
(feature) => feature.properties,
(feature) => feature.properties
) as SchemaUserResponse[];
const _previousMapData = previousGeojson?.features;

Expand All @@ -83,6 +83,7 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
const mergedProperties = {
...feature.properties,
...values.schemaData[i],
label: `${i + 1}`,
};
feature["properties"] = mergedProperties;
geojson.features.push(feature);
Expand All @@ -99,11 +100,13 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
});

const [activeIndex, setActiveIndex] = useState<number>(
previousFormData?.length - 1 || -1,
previousFormData ? previousFormData?.length - 1 : 0
);

const [minError, setMinError] = useState<boolean>(false);
const [maxError, setMaxError] = useState<boolean>(false);
const [loadPreviousValuesOnMap, setLoadPreviousValuesOnMap] =
useState<boolean>(Boolean(previouslySubmittedData));

const handleGeoJSONChange = (event: GeoJSONChangeEvent) => {
// If the user clicks 'reset' on the map, geojson will be empty object
Expand Down Expand Up @@ -147,7 +150,9 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
formik.setErrors({});
};

const removeAllFeaturesFromMap = () => setFeatures(undefined);
const removeAllFeaturesFromMap = () => {
setFeatures(undefined);
};

const removeAllFeaturesFromForm = () => {
formik.setFieldValue("schemaData", []);
Expand Down Expand Up @@ -179,8 +184,11 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
setActiveIndex(newFeatures.length - 1);
};

const addInitialFeaturesToMap = (features: Feature[]) => {
setFeatures(features);
const addInitialFeaturesToMap = (initialFeatures: Feature[]) => {
if (loadPreviousValuesOnMap) {
setFeatures(initialFeatures);
setLoadPreviousValuesOnMap(false);
}
};

const addFeatureToForm = () => {
Expand All @@ -198,21 +206,24 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (

const copyFeature = (sourceIndex: number, destinationIndex: number) => {
const sourceFeature = formik.values.schemaData[sourceIndex];
formik.setFieldValue(`schemaData[${destinationIndex}]`, sourceFeature);
formik.setFieldValue(`schemaData[${destinationIndex}]`, {
...sourceFeature,
label: `${destinationIndex + 1}`,
});
};

const removeFeatureFromForm = (index: number) => {
formik.setFieldValue(
"schemaData",
formik.values.schemaData.filter((_, i) => i !== index),
formik.values.schemaData.filter((_, i) => i !== index)
);
};

const removeFeatureFromMap = (index: number) => {
// Order of features can vary by change/modification, filter on label not array position
const label = `${index + 1}`;
const filteredFeatures = features?.filter(
(f) => f.properties?.label !== label,
(f) => f.properties?.label !== label
);

// Shift any feature labels that are larger than the removed feature label so they remain incremental
Expand All @@ -236,7 +247,6 @@ export const MapAndLabelProvider: React.FC<MapAndLabelProviderProps> = (
// Set active index as highest tab after removal, so that when you "add" a new feature the tabs increment correctly
setActiveIndex((features && features.length - 2) || 0);
};

return (
<MapAndLabelContext.Provider
value={{
Expand Down Expand Up @@ -267,7 +277,7 @@ export const useMapAndLabelContext = (): MapAndLabelContextValue => {
const context = useContext(MapAndLabelContext);
if (!context) {
throw new Error(
"useMapAndLabelContext must be used within a MapAndLabelProvider",
"useMapAndLabelContext must be used within a MapAndLabelProvider"
);
}
return context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ const Root = () => {
const passport = useStore((state) => state.computePassport().data);

// If coming "back" or "changing", load initial features & tabs onto the map
// Pre-populating form fields within tabs is handled via formik.initialValues in Context.tsx
// Pre-populating form fields within tabs is handled via formik.initialValues in Context.tsx
if (previouslySubmittedData?.data?.[fn]?.features?.length > 0) {
addInitialFeaturesToMap(previouslySubmittedData?.data?.[fn]?.features);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type TreeData = {
species: string;
work: string;
justification: string;
urgency: "low" | "moderate" | "high" | "urgenct";
urgency: "low" | "moderate" | "high" | "urgent";
label: string;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { Schema } from "@planx/components/shared/Schema/model";

import { PresentationalProps } from "../../Public";
import { Trees } from "../../schemas/Trees";
import {
previouslySubmittedDoubleFeature,
previouslySubmittedSingleFeature,
} from "./mockPayload";

const mockTreeSchema: Schema = {
...Trees,
Expand All @@ -20,3 +24,13 @@ export const props: PresentationalProps = {
longitude: -0.1629784,
latitude: 51.5230919,
};

export const previouslySubmittedSingleFeatureProps: PresentationalProps = {
...props,
previouslySubmittedData: previouslySubmittedSingleFeature,
};

export const previouslySubmittedDoubleFeatureProps: PresentationalProps = {
...props,
previouslySubmittedData: previouslySubmittedDoubleFeature,
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { FeatureCollection } from "geojson";

import { mockTreeData } from "./GenericValues";
export type MockPayload = { data: { MockFn: FeatureCollection } };
export interface MockPayload {
data: { MockFn: FeatureCollection };
}

interface PreviousData extends MockPayload {
auto: boolean;
}

export const mockSingleFeaturePayload: MockPayload = {
data: {
Expand All @@ -11,11 +18,47 @@ export const mockSingleFeaturePayload: MockPayload = {
coordinates: [-3.685929607119201, 57.15301433687542],
type: "Point",
},
properties: { mockTreeData },
properties: { mockTreeData, label: "1" },
type: "Feature",
},
],
type: "FeatureCollection",
},
},
};

export const mockDoubleFeaturePayload: MockPayload = {
data: {
MockFn: {
features: [
{
geometry: {
coordinates: [-3.685929607119201, 57.15301433687542],
type: "Point",
},
properties: { mockTreeData, label: "1" },
type: "Feature",
},
{
type: "Feature",
properties: { ...mockTreeData, label: "2" },
geometry: {
type: "Point",
coordinates: [-3.686529607119201, 57.15310433687542],
},
},
],
type: "FeatureCollection",
},
},
};

export const previouslySubmittedSingleFeature: PreviousData = {
auto: false,
...mockSingleFeaturePayload,
};

export const previouslySubmittedDoubleFeature: PreviousData = {
auto: false,
...mockDoubleFeaturePayload,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { MyMap } from "@opensystemslab/map";
import React from "react";
import { setup } from "testUtils";
import { it, vi } from "vitest";

import { Presentational as MapAndLabel } from "../Public";
import { point1, point2, point3 } from "./mocks/geojson";
import {
previouslySubmittedDoubleFeatureProps,
previouslySubmittedSingleFeatureProps,
} from "./mocks/Trees";
import { addMultipleFeatures, clickContinue } from "./utils";

beforeAll(() => {
if (!window.customElements.get("my-map")) {
window.customElements.define("my-map", MyMap);
}

const ResizeObserverMock = vi.fn(() => ({
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}));

vi.stubGlobal("ResizeObserver", ResizeObserverMock);
});

describe("navigating back after adding single feature", () => {
it("shows previously submitted features", async () => {
const { getByTestId, queryByRole } = setup(
<MapAndLabel {...previouslySubmittedSingleFeatureProps} />
);
const map = getByTestId("map-and-label-map");

expect(map).toBeInTheDocument();

const firstTab = queryByRole("tab", { name: /Tree 1/ });
expect(firstTab).toBeVisible();

const firstTabPanel = getByTestId("vertical-tabpanel-0");
expect(firstTabPanel).toBeVisible();

// To properly add the features to the map, you need to pass all previous features as well
addMultipleFeatures([point1, point2]);

const secondTab = queryByRole("tab", { name: /Tree 2/ });
expect(secondTab).toBeVisible();

const secondTabPanel = getByTestId("vertical-tabpanel-1");
expect(secondTabPanel).toBeVisible();
});
});

describe("navigating back after adding two features", () => {
it("shows previously submitted features", async () => {
const { getByTestId, queryByRole } = setup(
<MapAndLabel {...previouslySubmittedDoubleFeatureProps} />
);
const map = getByTestId("map-and-label-map");

expect(map).toBeInTheDocument();

const firstTab = queryByRole("tab", { name: /Tree 1/ });
const secondTab = queryByRole("tab", { name: /Tree 2/ });
expect(firstTab).toBeInTheDocument();
expect(secondTab).toBeInTheDocument();

const secondTabPanel = getByTestId("vertical-tabpanel-1");
expect(secondTabPanel).toBeVisible();

// To properly add the features to the map, you need to pass all previous features as well
addMultipleFeatures([point1, point2, point3]);

const thirdTab = queryByRole("tab", { name: /Tree 3/ });
expect(thirdTab).toBeVisible();

const thirdTabPanel = getByTestId("vertical-tabpanel-2");
expect(thirdTabPanel).toBeVisible();
});
it("should maintain labelling when removing a feature", async () => {
const handleSubmit = vi.fn();
const { getByRole, user } = setup(
<MapAndLabel
{...previouslySubmittedDoubleFeatureProps}
handleSubmit={handleSubmit}
/>
);

const firstTab = getByRole("tab", { name: /Tree 1/ });

await user.click(firstTab);

const removeButton = getByRole("button", { name: /remove/i });

await user.click(removeButton);

await clickContinue(user);

const output =
handleSubmit.mock.calls[0][0].data.MockFn.features[0].properties.label;

expect(output).toEqual("1");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ import { setup } from "testUtils";
import { vi } from "vitest";
import { axe } from "vitest-axe";

import { mockTreeData } from "../test/mocks/GenericValues";
import {
mockFeaturePointObj,
point1,
point2,
point3,
} from "../test/mocks/geojson";
import { props } from "../test/mocks/Trees";
import { mockTreeData } from "./mocks/GenericValues";
import { mockFeaturePointObj, point1, point2, point3 } from "./mocks/geojson";
import { props } from "./mocks/Trees";
import {
addFeaturesToMap,
addMultipleFeatures,
Expand All @@ -23,7 +18,7 @@ import {
fillOutFirstHalfOfForm,
fillOutForm,
fillOutSecondHalfOfForm,
} from "../test/utils";
} from "./utils";

beforeAll(() => {
if (!window.customElements.get("my-map")) {
Expand Down

0 comments on commit 8ef61d3

Please sign in to comment.