Skip to content

Commit

Permalink
Merge branch 'try/state-in-context-use-hook' of https://github.com/re…
Browse files Browse the repository at this point in the history
…akit/reakit into try/state-in-context-use-hook
  • Loading branch information
EricRibeiro committed Apr 17, 2021
2 parents 4c44464 + 3937cf9 commit 068f5a9
Show file tree
Hide file tree
Showing 15 changed files with 592 additions and 2 deletions.
5 changes: 5 additions & 0 deletions packages/reakit-playground/src/__deps/reakit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ export default {
"reakit/Rover/RoverState": require("reakit/Rover/RoverState"),
"reakit/Separator": require("reakit/Separator"),
"reakit/Separator/Separator": require("reakit/Separator/Separator"),
"reakit/StateContext": require("reakit/StateContext"),
"reakit/StateContext/createStateContext": require("reakit/StateContext/createStateContext"),
"reakit/StateContext/useStateContext": require("reakit/StateContext/useStateContext"),
"reakit/StateContext/useStateContextConsumer": require("reakit/StateContext/useStateContextConsumer"),
"reakit/StateContext/useStateContextProvider": require("reakit/StateContext/useStateContextProvider"),
"reakit/Tab": require("reakit/Tab"),
"reakit/Tab/Tab": require("reakit/Tab/Tab"),
"reakit/Tab/TabList": require("reakit/Tab/TabList"),
Expand Down
1 change: 1 addition & 0 deletions packages/reakit/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
/Role
/Rover
/Separator
/StateContext
/Tab
/Tabbable
/Toolbar
Expand Down
19 changes: 18 additions & 1 deletion packages/reakit/src/Combobox/ComboboxOption.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createComponent } from "reakit-system/createComponent";
import { createHook } from "reakit-system/createHook";
import { useStateContextConsumer } from "reakit/StateContext";
import {
CompositeItemOptions,
CompositeItemHTMLProps,
Expand All @@ -11,13 +12,29 @@ import {
unstable_ComboboxItemHTMLProps as ComboboxItemHTMLProps,
unstable_useComboboxItem as useComboboxItem,
} from "./ComboboxItem";
import { StateContext } from "./ComboboxPopover";

export const unstable_useComboboxOption = createHook<
unstable_ComboboxOptionOptions,
unstable_ComboboxOptionHTMLProps
>({
name: "ComboboxOption",
compose: [useComboboxItem, useCompositeItem],
compose: [
useStateContextConsumer({
context: StateContext,
shouldUpdate: (id, state, nextState) => {
return (
state.currentId === null ||
nextState.currentId === null ||
id === state.currentId ||
id === nextState.currentId
);
},
updateDependencies: (state) => [state?.currentId],
}),
useComboboxItem,
useCompositeItem,
],
keys: COMBOBOX_OPTION_KEYS,

useProps(_, htmlProps) {
Expand Down
13 changes: 12 additions & 1 deletion packages/reakit/src/Combobox/ComboboxPopover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { createComponent } from "reakit-system/createComponent";
import { createHook } from "reakit-system/createHook";
import { useWarning } from "reakit-warning";
import { useCreateElement } from "reakit-system/useCreateElement";
import {
createStateContext,
useStateContextProvider,
} from "reakit/StateContext";
import {
PopoverOptions,
PopoverHTMLProps,
Expand All @@ -14,13 +18,18 @@ import {
unstable_useComboboxList as useComboboxList,
} from "./ComboboxList";
import { ComboboxPopoverStateReturn } from "./__utils/ComboboxPopoverState";
import { unstable_ComboboxState } from "./ComboboxState";

export const StateContext = createStateContext<
Partial<unstable_ComboboxState>
>();

export const unstable_useComboboxPopover = createHook<
unstable_ComboboxPopoverOptions,
unstable_ComboboxPopoverHTMLProps
>({
name: "ComboboxPopover",
compose: [useComboboxList, usePopover],
compose: [useStateContextProvider(StateContext), useComboboxList, usePopover],
keys: COMBOBOX_POPOVER_KEYS,

useOptions(options) {
Expand All @@ -33,8 +42,10 @@ export const unstable_useComboboxPopover = createHook<
},

useComposeProps(options, { tabIndex, ...htmlProps }) {
htmlProps = useStateContextProvider(StateContext)(options, htmlProps, true);
htmlProps = useComboboxList(options, htmlProps, true);
htmlProps = usePopover(options, htmlProps, true);

return {
...htmlProps,
tabIndex: tabIndex ?? undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import * as React from "react";
import { render, press, click, type, screen } from "reakit-test-utils";
import AccessibleCombobox from "..";

test("open combobox popover on click", () => {
render(<AccessibleCombobox />);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruits")).toBeVisible();
expect(screen.getByText("Apple")).not.toHaveFocus();
});

test("open combobox popover on arrow down", () => {
render(<AccessibleCombobox />);
press.Tab();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowDown();
expect(screen.getByLabelText("Fruits")).toBeVisible();
expect(screen.getByText("Apple")).not.toHaveFocus();
});

test("open combobox popover on arrow up", () => {
render(<AccessibleCombobox />);
press.Tab();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowUp();
expect(screen.getByLabelText("Fruits")).toBeVisible();
expect(screen.getByText("Banana")).not.toHaveFocus();
});

test("open combobox popover by typing on the combobox", () => {
render(<AccessibleCombobox />);
press.Tab();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
type("a");
expect(screen.getByLabelText("Fruits")).toBeVisible();
expect(screen.getByText("Apple")).not.toHaveFocus();
});

test("do not open combobox popover on arrow right/left", () => {
render(<AccessibleCombobox />);
press.Tab();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowLeft();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowRight();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
});

test("do not open combobox popover on backspace on empty input", () => {
render(<AccessibleCombobox />);
press.Tab();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
type("\b");
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
});

test("close combobox popover by clicking outside", () => {
const { baseElement } = render(<AccessibleCombobox />);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruits")).toBeVisible();
click(baseElement);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
});

test("close combobox popover by tabbing out", () => {
render(
<>
<AccessibleCombobox />
<button>button</button>
</>
);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.ArrowDown();
press.Tab();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
expect(screen.getByText("button")).toHaveFocus();
});

test("close combobox popover by pressing esc", () => {
render(<AccessibleCombobox />);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
});

test("open combobox popover after pressing esc", () => {
render(<AccessibleCombobox />);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowDown();
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowUp();
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
type("a");
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowDown();
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowDown();
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
type("\b");
expect(screen.getByLabelText("Fruits")).toBeVisible();
});

test("open combobox popover after pressing esc twice", () => {
render(<AccessibleCombobox />);
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruits")).toBeVisible();
press.Escape();
press.Escape();
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
press.ArrowDown();
expect(screen.getByLabelText("Fruits")).toBeVisible();
});

test("move through combobox options with keyboard", () => {
render(<AccessibleCombobox />);
click(screen.getByLabelText("Fruit"));
press.ArrowDown();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByText("Apple")).toHaveFocus();
press.ArrowDown();
expect(screen.getByText("Orange")).toHaveFocus();
press.ArrowDown();
expect(screen.getByText("Banana")).toHaveFocus();
press.ArrowDown();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByText("Apple")).not.toHaveFocus();
expect(screen.getByText("Orange")).not.toHaveFocus();
expect(screen.getByText("Banana")).not.toHaveFocus();
press.ArrowUp();
expect(screen.getByText("Banana")).toHaveFocus();
press.ArrowUp();
expect(screen.getByText("Orange")).toHaveFocus();
press.ArrowUp();
expect(screen.getByText("Apple")).toHaveFocus();
press.ArrowUp();
expect(screen.getByLabelText("Fruit")).toHaveFocus();
expect(screen.getByText("Apple")).not.toHaveFocus();
expect(screen.getByText("Orange")).not.toHaveFocus();
expect(screen.getByText("Banana")).not.toHaveFocus();
press.ArrowUp();
expect(screen.getByText("Banana")).toHaveFocus();
press.ArrowRight();
expect(screen.getByText("Banana")).toHaveFocus();
press.ArrowLeft();
expect(screen.getByText("Banana")).toHaveFocus();
press.Home();
expect(screen.getByText("Banana")).toHaveFocus();
press.End();
expect(screen.getByText("Banana")).toHaveFocus();
});

test("select combobox option by clicking on it", () => {
render(<AccessibleCombobox />);
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruit")).toHaveValue("");
click(screen.getByText("Orange"));
expect(screen.getByLabelText("Fruit")).toHaveValue("Orange");
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
type("\b\b\b\b\b\ba");
expect(screen.getByLabelText("Fruit")).toHaveValue("a");
click(screen.getByText("Apple"));
expect(screen.getByLabelText("Fruit")).toHaveValue("Apple");
});

test("select combobox option by pressing enter on it", () => {
render(<AccessibleCombobox />);
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruit")).toHaveValue("");
press.ArrowUp();
press.Enter();
expect(screen.getByLabelText("Fruit")).toHaveValue("Banana");
expect(screen.getByLabelText("Fruits")).not.toBeVisible();
type("\b\b\b\b\b\ba");
expect(screen.getByLabelText("Fruit")).toHaveValue("a");
press.ArrowDown();
press.Enter();
expect(screen.getByLabelText("Fruit")).toHaveValue("Apple");
});

test("do not select combobox option by pressing space on it", () => {
render(<AccessibleCombobox />);
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruit")).toHaveValue("");
press.ArrowDown();
press.Space();
expect(screen.getByLabelText("Fruit")).toHaveValue("");
expect(screen.getByLabelText("Fruits")).toBeVisible();
});

test("unselect combobox option when typing on the combobox", () => {
render(<AccessibleCombobox />);
click(screen.getByLabelText("Fruit"));
expect(screen.getByLabelText("Fruit")).toHaveValue("");
press.ArrowDown();
expect(screen.getByText("Apple")).toHaveFocus();
type("a");
expect(screen.getByText("Apple")).not.toHaveFocus();
press.ArrowDown();
expect(screen.getByText("Apple")).toHaveFocus();
});

test("clicking on combobox input unselects combobox option", () => {
render(<AccessibleCombobox />);
click(screen.getByLabelText("Fruit"));
press.ArrowDown();
expect(screen.getByText("Apple")).toHaveFocus();
click(screen.getByLabelText("Fruit"));
expect(screen.getByText("Apple")).not.toHaveFocus();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react";
import {
unstable_useComboboxState as useComboboxState,
unstable_Combobox as Combobox,
unstable_ComboboxPopover as ComboboxPopover,
unstable_ComboboxOption as ComboboxOption,
} from "reakit/Combobox";

import "./style.css";

export default function AccessibleComboboxContext() {
const combobox = useComboboxState({ gutter: 8 });
return (
<>
<Combobox {...combobox} aria-label="Fruit" />
<ComboboxPopover {...combobox} aria-label="Fruits">
<ComboboxOption value="Apple" />
<ComboboxOption value="Orange" />
<ComboboxOption value="Banana" />
</ComboboxPopover>
</>
);
}
Loading

0 comments on commit 068f5a9

Please sign in to comment.