Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed47d66
Add asterisks near the required fields
mazursasha1990 Feb 18, 2025
cd95ee1
Add error messages and error styles to the required form fields
mazursasha1990 Feb 18, 2025
d60c17c
Move getSelectStyles function to the util.js and create isRequiredFie…
mazursasha1990 Feb 18, 2025
26e08d4
Use getSelectStyles and isRequiredFieldValid functions from util.js i…
mazursasha1990 Feb 18, 2025
d45c17a
Use isRequiredFieldValid to check the validation for name and address…
mazursasha1990 Feb 18, 2025
d25417f
Fix the Ambiguous spacing before next element span warning
mazursasha1990 Feb 18, 2025
65a9141
Merge branch 'main' into OSDEV-1678-the-request-body-is-invalid-appea…
mazursasha1990 Feb 18, 2025
0460e42
Add tests for ProductionLocationInfo component
mazursasha1990 Feb 18, 2025
4ac4ad7
Merge branch 'main' into OSDEV-1678-the-request-body-is-invalid-appea…
mazursasha1990 Feb 19, 2025
4b16cb2
Add release notes
mazursasha1990 Feb 19, 2025
3752c94
Fix linter issue in the ProductionLocationInfo.test.js
mazursasha1990 Feb 19, 2025
bb9d78b
Merge branch 'main' into OSDEV-1678-the-request-body-is-invalid-appea…
mazursasha1990 Feb 21, 2025
3a5a484
Added tests for isRequiredFieldValid and getSelectStyles from util.js
mazursasha1990 Feb 21, 2025
ffeb266
Merge branch 'main' into OSDEV-1678-the-request-body-is-invalid-appea…
mazursasha1990 Feb 21, 2025
598d6e7
Add minHeight value for control field of Select
mazursasha1990 Feb 21, 2025
f905a2b
Add check for valid number of workes to the isFormValid constant
mazursasha1990 Feb 21, 2025
a6566fe
Add additional test cases to the ProductionLocationInfo component
mazursasha1990 Feb 21, 2025
76f60c0
Fix an error in the tests
mazursasha1990 Feb 21, 2025
23976ab
Empty-Commit
mazursasha1990 Feb 21, 2025
4aa9c62
Use RequiredAsterisk component in the SearchByNameAndAddressTab
mazursasha1990 Feb 24, 2025
a06db4f
Use RequiredAsterisk component in the SearchByNameAndAddressTab
mazursasha1990 Feb 24, 2025
92a4840
Change place of RequiredAsterisk import
mazursasha1990 Feb 24, 2025
97095ca
Merge branch 'main' into OSDEV-1678-the-request-body-is-invalid-appea…
mazursasha1990 Feb 25, 2025
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
1 change: 1 addition & 0 deletions doc/release/RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html

### Bugfix
* [OSDEV-1745](https://opensupplyhub.atlassian.net/browse/OSDEV-1745) - The `Search by Name and Address` tab was defined as default on the Production Location Search page.
* [OSDEV-1678](https://opensupplyhub.atlassian.net/browse/OSDEV-1678) - Added asterisks next to each required form field (Name, Address, and Country) on the "Production Location Information" page. Highlighted an empty field and displayed an error message if it loses focus.

### What's new
* *Describe what's new here. The changes that can impact user experience should be listed in this section.*
Expand Down
200 changes: 200 additions & 0 deletions src/react/src/__tests__/components/ProductionLocationInfo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import React from "react";
import { fireEvent } from "@testing-library/react";
import { BrowserRouter as Router } from "react-router-dom";

import ProductionLocationInfo from "../../components/Contribute/ProductionLocationInfo";
import renderWithProviders from "../../util/testUtils/renderWithProviders";

jest.mock("../../components/Filters/StyledSelect", () => (props) => {
const { options = [], value, onChange, onBlur, placeholder } = props;
return (
<select
data-testid="mocked-select"
value={value ? value.value : ""}
onChange={(e) => {
const selectedOption = options.find(
(opt) => opt.value === e.target.value,
);
onChange(selectedOption);
}}
onBlur={onBlur}
>
<option value="">{placeholder}</option>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
);
});

describe("ProductionLocationInfo component", () => {
const defaultState = {
filterOptions: {
countries: {
data: [{ value: "US", label: "United States" }],
error: null,
fetching: false,
},
facilityProcessingType: {
data: [],
error: null,
fetching: false,
},
},
};

const defaultProps = {
submitMethod: "POST",
};

const renderComponent = (props = {}) =>
renderWithProviders(
<Router>
<ProductionLocationInfo {...defaultProps} {...props} />
</Router>,
{ preloadedState: defaultState },
)

test("renders the production location form", () => {
const { getByText, getByPlaceholderText, getAllByText, getByTestId } = renderComponent();

expect(getByText("Production Location Information")).toBeInTheDocument();
expect(getByText("Use the form below to edit the name, address, and country for your production location.")).toBeInTheDocument();
expect(getByText("Location Name")).toBeInTheDocument();
expect(getByText("Enter the name of the production location that you are uploading.")).toBeInTheDocument();
expect(getByText("Address")).toBeInTheDocument();
expect(getByText("Enter the address of the production location. We will use this to plot the location on a map.")).toBeInTheDocument();
expect(getAllByText("Country")).toHaveLength(2);
expect(getByText("Select the country where the production site is located.")).toBeInTheDocument();
expect(getByText("Additional information")).toBeInTheDocument();
expect(
getByText("Expand this section to add more data about your production location, including product types, number of workers, parent company and more."),
).toBeInTheDocument();

const nameInput = getByPlaceholderText("Enter the name");
expect(nameInput).toBeInTheDocument();
expect(nameInput).toHaveValue("");

const addressInput = getByPlaceholderText("Enter the full address");
expect(addressInput).toBeInTheDocument();
expect(addressInput).toHaveValue("");

const countrySelect = getByTestId("mocked-select");
expect(countrySelect).toBeInTheDocument();
expect(countrySelect).toHaveValue("");

const iconButton = getByTestId("toggle-additional-info");
expect(iconButton).toBeInTheDocument();
});

test("displays error (and disables submit) when required fields are empty after blur", () => {
const { getByRole, getByPlaceholderText, getAllByText, getByTestId } = renderComponent();

const submitButton = getByRole("button", { name: /Submit/i });
expect(submitButton).toBeDisabled();

const nameInput = getByPlaceholderText("Enter the name");
const addressInput = getByPlaceholderText("Enter the full address");
const countrySelect = getByTestId("mocked-select");

fireEvent.blur(nameInput);
fireEvent.blur(addressInput);
fireEvent.blur(countrySelect);

const nameError = getAllByText("This field is required.");
expect(nameError).toHaveLength(3);

expect(getByPlaceholderText("Enter the name")).toHaveAttribute("aria-invalid", "true");
expect(getByPlaceholderText("Enter the full address")).toHaveAttribute("aria-invalid", "true");
});

test("enables the submit button when required fields are filled", () => {
const { getByRole, getByPlaceholderText, getByTestId } = renderComponent();

const submitButton = getByRole("button", { name: /Submit/i });
expect(submitButton).toBeDisabled();

const nameInput = getByPlaceholderText("Enter the name");
const addressInput = getByPlaceholderText("Enter the full address");
const countrySelect = getByTestId("mocked-select");

fireEvent.change(nameInput, { target: { value: "Test Name" } });
fireEvent.change(addressInput, { target: { value: "Test Address" } });
fireEvent.change(countrySelect, { target: { value: "US" } });

expect(countrySelect.value).toBe("US");
expect(submitButton).toBeEnabled();
});

test("displays additional information form when icon button is clicked", () => {
const { getByTestId, getByText, queryByText } = renderComponent();

const iconButton = getByTestId("toggle-additional-info");
fireEvent.click(iconButton);

expect(getByText("Sector(s)")).toBeInTheDocument();
expect(getByText("Select the sector(s) that this location operates in. For example: Apparel, Electronics, Renewable Energy.")).toBeInTheDocument();
expect(getByText("Product Type(s)")).toBeInTheDocument();
expect(getByText("Enter the type of products produced at this location. For example: Shirts, Laptops, Solar Panels.")).toBeInTheDocument();
expect(getByText("Location Type(s)")).toBeInTheDocument();
expect(getByText("Select the location type(s) for this production location. For example: Final Product Assembly, Raw Materials Production or Processing, Office/HQ.")).toBeInTheDocument();
expect(getByText("Processing Type(s)")).toBeInTheDocument();
expect(getByText("Select the type of processing activities that take place at this location. For example: Printing, Tooling, Assembly.")).toBeInTheDocument();
expect(getByText("Number of Workers")).toBeInTheDocument();
expect(getByText("Enter a number or a range for the number of people employed at the location. For example: 100, 100-150.")).toBeInTheDocument();
expect(getByText("Parent Company")).toBeInTheDocument();
expect(getByText("Enter the company that holds majority ownership for this production.")).toBeInTheDocument();

fireEvent.click(iconButton);

expect(queryByText("Sector(s)")).not.toBeInTheDocument();
expect(queryByText("Product Type(s)")).not.toBeInTheDocument();
expect(queryByText("Location Type(s)")).not.toBeInTheDocument();
expect(queryByText("Processing Type(s)")).not.toBeInTheDocument();
expect(queryByText("Number of Workers")).not.toBeInTheDocument();
expect(queryByText("Parent Company")).not.toBeInTheDocument();
});

test("displays error when number of workers is not a valid number and disable submit button", () => {
const { getByRole, getByPlaceholderText, getByTestId } = renderComponent();

const submitButton = getByRole("button", { name: /Submit/i });
expect(submitButton).toBeDisabled();

const nameInput = getByPlaceholderText("Enter the name");
const addressInput = getByPlaceholderText("Enter the full address");
const countrySelect = getByTestId("mocked-select");

fireEvent.change(nameInput, { target: { value: "Test Name" } });
fireEvent.change(addressInput, { target: { value: "Test Address" } });
fireEvent.change(countrySelect, { target: { value: "US" } });

expect(submitButton).toBeEnabled();

const iconButton = getByTestId("toggle-additional-info");
fireEvent.click(iconButton);

const numberInput = getByPlaceholderText("Enter the number of workers as a number or range");
fireEvent.change(numberInput, { target: { value: "Test" } });

expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "true");
expect(submitButton).toBeDisabled();

fireEvent.change(numberInput, { target: { value: "100" } });

expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "false");
expect(submitButton).toBeEnabled();

fireEvent.change(numberInput, { target: { value: "100-150" } });

expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "false");
expect(submitButton).toBeEnabled();

fireEvent.change(numberInput, { target: { value: "200-100" } });

expect(getByPlaceholderText("Enter the number of workers as a number or range")).toHaveAttribute("aria-invalid", "true");
expect(submitButton).toBeDisabled
});
});
75 changes: 75 additions & 0 deletions src/react/src/__tests__/utils.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ const {
getLastPathParameter,
generateRangeField,
parseContribData,
isRequiredFieldValid,
getSelectStyles,
} = require('../util/util');

const {
Expand All @@ -99,6 +101,8 @@ const {
componentsWithErrorMessage,
} = require('../util/constants');

const COLOURS = require('../util/COLOURS').default;

it('gets correct error message component', () => {
const correctListName = 'New & Test Name - Location, [Ltd].';
const emptyListName = '';
Expand Down Expand Up @@ -2166,3 +2170,74 @@ it('should convert incoming object with camelCase keys into snake_case to confor

expect(parseContribData(input)).toEqual(expectedOutput);
});

describe('isRequiredFieldValid', () => {
it('should return true if the field has a value', () => {
expect(isRequiredFieldValid('test')).toBe(true);
expect(isRequiredFieldValid(' test ')).toBe(true);
expect(isRequiredFieldValid('test test')).toBe(true);
});

it('should return false if the field has no value', () => {
expect(isRequiredFieldValid('')).toBe(false);
expect(isRequiredFieldValid(' ')).toBe(false);
expect(isRequiredFieldValid(null)).toBe(false);
expect(isRequiredFieldValid(undefined)).toBe(false);
});
});

describe('getSelectStyles', () => {
const provided = {
borderColor: 'grey',
boxShadow: 'none',
color: 'blue',
};

const stateFocused = { isFocused: true };
const stateNotFocused = { isFocused: false };

it('returns an object with control and placeholder functions', () => {
const styles = getSelectStyles();
expect(typeof styles.control).toBe('function');
expect(typeof styles.placeholder).toBe('function');
});

it('applies PURPLE border and inset box shadow when focused and no error', () => {
const styles = getSelectStyles();
const controlStyles = styles.control(provided, stateFocused);
expect(controlStyles.borderColor).toBe(COLOURS.PURPLE);
expect(controlStyles.boxShadow).toBe(`inset 0 0 0 1px ${COLOURS.PURPLE}`);
});

it('applies RED border when error state is true', () => {
const styles = getSelectStyles(true);
const controlStyles = styles.control(provided, stateFocused);
expect(controlStyles.borderColor).toBe(COLOURS.RED);
});

it('applies correct placeholder style when error state is true', () => {
const styles = getSelectStyles(true);
const placeholderStyles = styles.placeholder(provided);
expect(placeholderStyles.opacity).toBe(0.7);
expect(placeholderStyles.color).toBe(COLOURS.RED);
});

it('uses the provided color for placeholder when error state is false', () => {
const styles = getSelectStyles();
const placeholderStyles = styles.placeholder(provided);
expect(placeholderStyles.opacity).toBe(0.7);
expect(placeholderStyles.color).toBe(provided.color);
});

it('sets hover borderColor to "black" when not focused and no error', () => {
const styles = getSelectStyles();
const controlStyles = styles.control(provided, stateNotFocused);
expect(controlStyles['&:hover']).toEqual({ borderColor: 'black' });
});

it('sets hover borderColor to false when error state is true', () => {
const styles = getSelectStyles(true);
const controlStyles = styles.control(provided, stateNotFocused);
expect(controlStyles['&:hover']).toEqual({ borderColor: false });
});
});
Loading
Loading