
tag would be rendered without its "src" attribute, which is not a good practice and should be avoided.
+ src={props.imgURL || props.anonImgURL}
alt={props.name}
onError={hideNonExistantImages}
/>
diff --git a/packages/app-webdir-ui/src/ProfileCard/index.test.js b/packages/app-webdir-ui/src/ProfileCard/index.test.js
new file mode 100644
index 0000000000..7ad0c4c1ca
--- /dev/null
+++ b/packages/app-webdir-ui/src/ProfileCard/index.test.js
@@ -0,0 +1,58 @@
+import React from "react";
+
+import { fireEvent, render, screen } from "@testing-library/react";
+
+import { ProfileCard } from "./index";
+
+const IMG_URL = "https://example.com/photo.jpg";
+const ANON_IMG_URL = "https://example.com/anon.png";
+
+describe("ProfileCard", () => {
+ it("uses anonImgURL as a fallback when imgURL is empty", () => {
+ render(
+
+ );
+
+ expect(screen.getByAltText("Morgan Denke")).toHaveAttribute(
+ "src",
+ ANON_IMG_URL
+ );
+ });
+
+ it("swaps to anonImgURL when the profile image fails to load", () => {
+ render(
+
+ );
+
+ const img = screen.getByAltText("Morgan Denke");
+ expect(img).toHaveAttribute("src", IMG_URL);
+
+ fireEvent.error(img);
+
+ expect(img).toHaveAttribute("src", ANON_IMG_URL);
+ expect(img).toBeVisible();
+ });
+
+ it("hides the image instead of looping when the anon image itself fails to load", () => {
+ render(
+
+ );
+
+ const img = screen.getByAltText("Morgan Denke");
+ fireEvent.error(img);
+ expect(img).toHaveAttribute("src", ANON_IMG_URL);
+
+ fireEvent.error(img);
+
+ expect(img).toHaveAttribute("src", ANON_IMG_URL);
+ expect(img).not.toBeVisible();
+ });
+});
diff --git a/packages/app-webdir-ui/src/SearchPage/index.stories.js b/packages/app-webdir-ui/src/SearchPage/index.stories.js
index f20a2306d6..f11bababa5 100644
--- a/packages/app-webdir-ui/src/SearchPage/index.stories.js
+++ b/packages/app-webdir-ui/src/SearchPage/index.stories.js
@@ -4,6 +4,9 @@ import { SearchPage } from "./index";
import { FullLayout } from "@asu/shared";
+const API_URL = import.meta.env.VITE_API_URL;
+const searchApiVersion = import.meta.env.VITE_SEARCH_API_VERSION;
+
export default {
title: "Organisms/Search Page/Templates",
decorators: [story =>
{story()}],
@@ -12,8 +15,8 @@ export default {
export const searchPageExample = () => (
diff --git a/packages/app-webdir-ui/src/WebDirectoryComponent/index.stories.js b/packages/app-webdir-ui/src/WebDirectoryComponent/index.stories.js
index 4786c236bd..43e38d550e 100644
--- a/packages/app-webdir-ui/src/WebDirectoryComponent/index.stories.js
+++ b/packages/app-webdir-ui/src/WebDirectoryComponent/index.stories.js
@@ -4,6 +4,9 @@ import { WebDirectory } from "./index";
import { FullLayout } from "@asu/shared";
+const API_URL = import.meta.env.VITE_API_URL;
+const searchApiVersion = import.meta.env.VITE_SEARCH_API_VERSION;
+
export default {
title: "Organisms/Web Directory/Templates",
argTypes: {
@@ -50,8 +53,8 @@ export const webDirectoryExampleDepartments = args => {
{
);
};
+webDirectoryExampleDepartments.args = {
+ alphaFilter: "true",
+};
export const webDirectoryExamplePeople = args => {
return (
@@ -71,8 +77,8 @@ export const webDirectoryExamplePeople = args => {
{
{
);
};
+webDirectoryExampleDepartmentsAndPeople.args = {
+ alphaFilter: "true",
+};
diff --git a/packages/app-webdir-ui/src/helpers/Filter/index.test.js b/packages/app-webdir-ui/src/helpers/Filter/index.test.js
new file mode 100644
index 0000000000..a4bab990b8
--- /dev/null
+++ b/packages/app-webdir-ui/src/helpers/Filter/index.test.js
@@ -0,0 +1,97 @@
+import React from "react";
+
+import { fireEvent, render, screen } from "@testing-library/react";
+
+import { FilterComponent } from "./index";
+
+const CHOICES = ["A", "B", "C", "D", "E", "F", "G"];
+
+/**
+ * jsdom never actually lays out content, so `scrollWidth`/`offsetWidth`
+ * default to 0. Mock them on the rendered `.choices-container` and fire the
+ * events `FilterComponent` listens for, mirroring what a real overflowing
+ * browser layout would report.
+ */
+const mockScrollableContainer = ({ scrollWidth, offsetWidth }) => {
+ const container = screen.getByRole("radiogroup");
+ Object.defineProperty(container, "scrollWidth", {
+ configurable: true,
+ value: scrollWidth,
+ });
+ Object.defineProperty(container, "offsetWidth", {
+ configurable: true,
+ value: offsetWidth,
+ });
+ Object.defineProperty(container, "clientWidth", {
+ configurable: true,
+ value: offsetWidth,
+ });
+ container.scrollTo = jest.fn();
+ fireEvent(container, new Event("resize"));
+ return container;
+};
+
+describe("FilterComponent nav controls", () => {
+ it("renders the prev/next controls with the classes their CSS depends on", () => {
+ render(
+