Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Map and Label breaking on back action #3825

Merged
merged 4 commits into from
Oct 25, 2024
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 @@ -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);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, see above comment - not following this change in particular !

};

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}`,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label was being assigned based on the object you were copying from rather than the object we were copying to

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch but I don't think this is quite working in all scenarios correctly yet!

For example:

  • I add two trees on the map
  • I fill out tree two tab
  • On tab 1, I "copy from" tree 2
  • I "continue" & my labels are correct
  • I come "back" and "remove tree" from tab 1 - this shifts the map & tab labels for previous "tree 2" to 1 now
  • I "continue" again and my passport data has a single feature with label: 2 when it should be label: 1 to reflect map/tab changes after a point was removed !

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for catching this, I'll add some adjustments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing added for this and hope I have solved it. I couldn't find another bug through manual testing...

});
};

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,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per your question in the PR description, mocking previouslySubmittedData is how I'd expect to see "back" navigation tested at the component level 👍 This is consistent with other @planx/components tests

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);
jessicamcinchak marked this conversation as resolved.
Show resolved Hide resolved
});

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]);
Copy link
Member

@jessicamcinchak jessicamcinchak Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: aren't point1 and point2 already on the map? Am we actually mocking adding "multiple" features here or just one? I understand the mock data needs to reflect the full feature collection, but I wonder if there's a way we can make this more clear in helper function naming, a code comment etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, it should just be adding one feature at a time. I'll adjust and switch the functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found that this was correct and just adding a single point deletes the others from the map, so I need them added like this. I've added a comment above each line explaining this need


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
Loading