diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/Context.tsx b/editor.planx.uk/src/@planx/components/MapAndLabel/Public/Context.tsx index a26e98c991..2cfa03302b 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/Context.tsx +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/Public/Context.tsx @@ -45,11 +45,11 @@ interface MapAndLabelContextValue { type MapAndLabelProviderProps = PropsWithChildren; const MapAndLabelContext = createContext( - undefined, + undefined ); export const MapAndLabelProvider: React.FC = ( - props, + props ) => { const { schema, children, handleSubmit, previouslySubmittedData, fn } = props; const { formikConfig, initialValues } = useSchema({ @@ -62,7 +62,7 @@ export const MapAndLabelProvider: React.FC = ( fn ] as FeatureCollection; const previousFormData = previousGeojson?.features.map( - (feature) => feature.properties, + (feature) => feature.properties ) as SchemaUserResponse[]; const _previousMapData = previousGeojson?.features; @@ -83,6 +83,7 @@ export const MapAndLabelProvider: React.FC = ( const mergedProperties = { ...feature.properties, ...values.schemaData[i], + label: `${i + 1}`, }; feature["properties"] = mergedProperties; geojson.features.push(feature); @@ -99,11 +100,13 @@ export const MapAndLabelProvider: React.FC = ( }); const [activeIndex, setActiveIndex] = useState( - previousFormData?.length - 1 || -1, + previousFormData ? previousFormData?.length - 1 : 0 ); const [minError, setMinError] = useState(false); const [maxError, setMaxError] = useState(false); + const [loadPreviousValuesOnMap, setLoadPreviousValuesOnMap] = + useState(Boolean(previouslySubmittedData)); const handleGeoJSONChange = (event: GeoJSONChangeEvent) => { // If the user clicks 'reset' on the map, geojson will be empty object @@ -147,7 +150,9 @@ export const MapAndLabelProvider: React.FC = ( formik.setErrors({}); }; - const removeAllFeaturesFromMap = () => setFeatures(undefined); + const removeAllFeaturesFromMap = () => { + setFeatures(undefined); + }; const removeAllFeaturesFromForm = () => { formik.setFieldValue("schemaData", []); @@ -179,8 +184,11 @@ export const MapAndLabelProvider: React.FC = ( setActiveIndex(newFeatures.length - 1); }; - const addInitialFeaturesToMap = (features: Feature[]) => { - setFeatures(features); + const addInitialFeaturesToMap = (initialFeatures: Feature[]) => { + if (loadPreviousValuesOnMap) { + setFeatures(initialFeatures); + setLoadPreviousValuesOnMap(false); + } }; const addFeatureToForm = () => { @@ -198,13 +206,16 @@ export const MapAndLabelProvider: React.FC = ( 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) ); }; @@ -212,7 +223,7 @@ export const MapAndLabelProvider: React.FC = ( // 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 @@ -236,7 +247,6 @@ export const MapAndLabelProvider: React.FC = ( // 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 ( { 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; diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx b/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx index f582ad3ec2..4cbdf8bb67 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.tsx @@ -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); } diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/GenericValues.ts b/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/GenericValues.ts index 16c3737b3c..1ee9ab0758 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/GenericValues.ts +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/GenericValues.ts @@ -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; }; diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/Trees.ts b/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/Trees.ts index db502de886..abebddd4c4 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/Trees.ts +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/Trees.ts @@ -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, @@ -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, +}; diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/mockPayload.tsx b/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/mockPayload.tsx index 6395d5453b..036e7bc719 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/mockPayload.tsx +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/test/mocks/mockPayload.tsx @@ -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: { @@ -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, +}; diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/test/public.backNavigation.test.tsx b/editor.planx.uk/src/@planx/components/MapAndLabel/test/public.backNavigation.test.tsx new file mode 100644 index 0000000000..f84e90468a --- /dev/null +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/test/public.backNavigation.test.tsx @@ -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( + + ); + 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( + + ); + 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( + + ); + + 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"); + }); +}); diff --git a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.test.tsx b/editor.planx.uk/src/@planx/components/MapAndLabel/test/public.test.tsx similarity index 98% rename from editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.test.tsx rename to editor.planx.uk/src/@planx/components/MapAndLabel/test/public.test.tsx index 8b363f01b1..afd7ad8603 100644 --- a/editor.planx.uk/src/@planx/components/MapAndLabel/Public/index.test.tsx +++ b/editor.planx.uk/src/@planx/components/MapAndLabel/test/public.test.tsx @@ -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, @@ -23,7 +18,7 @@ import { fillOutFirstHalfOfForm, fillOutForm, fillOutSecondHalfOfForm, -} from "../test/utils"; +} from "./utils"; beforeAll(() => { if (!window.customElements.get("my-map")) {