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 2 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 @@ -99,11 +99,14 @@ 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 [loadPreviousValues, setLoadPreviousValues] = useState<boolean>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went round the blocks with this trying to find an elegant solution, this felt like the best option to ensure previous values only loaded once

Copy link
Member

Choose a reason for hiding this comment

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

I don't really understand the addition of these local state variables - why can't we simply ensure addInitialFeaturesToMap is only called if when previouslySubmittedData is present? What's a scenario where we're trying to load initial values more than once?

If we do indeed need this, can we rename it to something like loadPreviousValuesOnMap because it's not actually handling the form tab data (formik.initialValues is, right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I could understand, previouslySubmittedData is always present once you navigate backwards, so when you add a new point the code will run again and it will add the new point to features then hit the if() statement then reassign features to be previouslySubmittedData.data...

I initially tried to place it in a useEffect() to only run the if statement on initial mount then whenever previouslySubmittedData changed, but I couldn't get it to work so easily, and it felt like a larger change than the state variable.

Have I missed a simpler solution?

Copy link
Member

@jessicamcinchak jessicamcinchak Oct 25, 2024

Choose a reason for hiding this comment

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

Thanks for explanation - happy to keep this approach then, it seems to be achieving what we want !

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 (loadPreviousValues) {
setFeatures(initialFeatures);
setLoadPreviousValues(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,7 +206,10 @@ 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) => {
Expand Down Expand Up @@ -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
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,6 +2,10 @@ import { Schema } from "@planx/components/shared/Schema/model";

import { PresentationalProps } from "../../Public";
import { Trees } from "../../schemas/Trees";
import {
previouslySubmittedDoubleData,
previouslySubmittedSingleData,
} from "./mockPayload";

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

export const previousSingleDataProps: PresentationalProps = {
...props,
previouslySubmittedData: previouslySubmittedSingleData,
RODO94 marked this conversation as resolved.
Show resolved Hide resolved
};

export const previousDoubleDataProps: PresentationalProps = {
...props,
previouslySubmittedData: previouslySubmittedDoubleData,
};
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 previousMockDoubleFeatureData: 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 previouslySubmittedSingleData: PreviousData = {
auto: false,
...mockSingleFeaturePayload,
};

export const previouslySubmittedDoubleData: PreviousData = {
auto: false,
...previousMockDoubleFeatureData,
};
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,73 @@
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 {
previousDoubleDataProps,
previousSingleDataProps,
} from "./mocks/Trees";
import { addMultipleFeatures } 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 {...previousSingleDataProps} />,
);
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();

// point1 here needs to be reflected in the props you pass in at the start so labels match
addMultipleFeatures([point1, point2]);

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

describe("navigating back after adding two features", () => {
it("shows previously submitted features", async () => {
const { getByTestId, queryByRole } = setup(
<MapAndLabel {...previousDoubleDataProps} />,
);
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();

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();
});
});
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 file was becoming quite long, so it felt right to move the back navigation tests to separate file, and relocate these files to the test folder on the Map and Label component

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