Skip to content
Merged
81 changes: 77 additions & 4 deletions src/molecules/accessible-list/accessible-list.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,80 @@
// import React from "react";
// import { render } from "@testing-library/react";
// import { AccessibleList } from "./accessible-list";
import React, { Component } from "react";
import faker from "faker";
import { render, fireEvent, screen } from "@testing-library/react";
import { AccessibleList } from "./accessible-list";
import { KeyboardKeys } from "../../constants/keyboard-keys";

describe("AccessibleList", () => {
test.skip("TODO - https://github.com/AndcultureCode/AndcultureCode.JavaScript.React.Components/issues/19", () => {});
test("when default props, renders items", () => {
// Arrange
const expected = faker.random.word();

// Act
const { getByText } = render(
<AccessibleList focusFirstItem={false}>
<button>{expected}</button>
</AccessibleList>
);

// Assert
expect(getByText(expected)).not.toBeNil();
});

test("when onEsc set, calls handler upon escape pressed", () => {
// Arrange
let isChecked = false;
const buttonText = faker.random.word();
const onEscHandler = () => {
isChecked = true;
};

// Act
const { getByText } = render(
<AccessibleList focusFirstItem={true} onEsc={onEscHandler}>
<button>{buttonText}</button>
</AccessibleList>
);
fireEvent.keyDown(getByText(buttonText), { key: KeyboardKeys.Escape });

// Assert
expect(isChecked).toBeTrue();
});

test("when invalid react element, does not render in list", () => {
// Arrange
const buttonText = [faker.random.word(), faker.random.word()];

// Act
const { container } = render(
<AccessibleList focusFirstItem={true}>
<button>{buttonText[0]}</button>
{null}
{undefined}
<button>{buttonText[1]}</button>
</AccessibleList>
);

// Assert
expect(container.childNodes).toHaveLength(2);
});

it("when onClick set, calls handler upon click", async () => {
// Arrange
let isChecked = false;
const handleClick = () => {
isChecked = true;
};
const buttonText = faker.random.word();

// Act
const { getByText } = render(
<AccessibleList focusFirstItem={true}>
<button onClick={handleClick}>{buttonText}</button>
</AccessibleList>
);
fireEvent.click(getByText(buttonText));

// Assert
expect(isChecked).toBeTrue();
});
});
104 changes: 60 additions & 44 deletions src/molecules/accessible-list/accessible-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,47 +37,46 @@ const AccessibleList: React.FunctionComponent<AccessibleListProps> = (
}, [refArray, current, props.focusFirstItem]);

const handleKeyDown = (e: KeyboardEvent) => {
if (
e.key === KeyboardKeys.DownArrow &&
current === refArray.length - 1
) {
e.preventDefault();
setCurrent(0);
return;
switch (e.key) {
case KeyboardKeys.UpArrow: {
handleUpArrowPress(e);
break;
}
case KeyboardKeys.DownArrow: {
handleDownArrowPress(e);
break;
}
case KeyboardKeys.Escape: {
handleEscapePress(e);
break;
}
default: {
return;
}
}
};

if (e.key === KeyboardKeys.UpArrow && current === 0) {
e.preventDefault();
setCurrent(refArray.length - 1);
return;
}
const handleDownArrowPress = (e: KeyboardEvent) => {
const isLastElementFocused = current === refArray.length - 1;
const indexToFocus = isLastElementFocused ? 0 : current + 1;

if (
e.key === KeyboardKeys.DownArrow &&
current !== refArray.length - 1
) {
e.preventDefault();
setCurrentAndPreventDefault(e, indexToFocus);
};

setCurrent(current + 1);
return;
}

if (e.key === KeyboardKeys.UpArrow && current !== 0) {
e.preventDefault();

setCurrent(current - 1);
return;
const handleEscapePress = (e: KeyboardEvent) => {
setCurrentAndPreventDefault(e, 0);
if (props.onEsc != null) {
props.onEsc();
}
};

if (e.key === KeyboardKeys.Escape) {
e.preventDefault();
setCurrent(0);
const handleUpArrowPress = (e: KeyboardEvent) => {
const isFirstElementFocused = current === 0;
const indexToFocus = isFirstElementFocused
? refArray.length - 1
: current - 1;

if (props.onEsc != null) {
props.onEsc();
}
return;
}
setCurrentAndPreventDefault(e, indexToFocus);
};

const renderChildren = () => {
Expand All @@ -87,19 +86,36 @@ const AccessibleList: React.FunctionComponent<AccessibleListProps> = (
return child;
}

return React.cloneElement(child, {
...child.props,
onClick: () => {
if (child.props.onClick != null) {
child.props.onClick();
}
},
onKeyDown: handleKeyDown,
ref: (el: HTMLElement) => (refArray[validElementIndex++] = el),
});
const renderedChild = renderChild(child, validElementIndex);
validElementIndex++;
return renderedChild;
});
};

const renderChild = (child: React.ReactElement, index: number) => {
return React.cloneElement(child, {
...child.props,
onClick: () => {
if (child.props.onClick != null) {
child.props.onClick();
}
},
onKeyDown: handleKeyDown,
ref: (el: HTMLElement) => (refArray[index] = el),
});
};

const setCurrentAndPreventDefault = (
e: KeyboardEvent,
indexToFocus: number
) => {
if (indexToFocus < 0) {
return;
}
e.preventDefault();
setCurrent(indexToFocus);
};

return <React.Fragment>{renderChildren()}</React.Fragment>;
};

Expand Down