Skip to content

Commit

Permalink
feat: add select search
Browse files Browse the repository at this point in the history
  • Loading branch information
abelflopes committed Jul 4, 2024
1 parent 75c4350 commit bdfc124
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 94 deletions.
1 change: 1 addition & 0 deletions packages/components/select/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"devDependencies": {
"@react-ck/babel-config": "^1.0.0",
"@react-ck/jest-config": "^1.0.0",
"@react-ck/manager": "^1.2.6",
"@react-ck/typescript-config": "^1.0.0",
"@react-ck/webpack-config": "^1.0.0",
"@types/react": "^18.2.33"
Expand Down
57 changes: 42 additions & 15 deletions packages/components/select/specs/index.unit.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
import React from "react";
import { Select } from "../src/index";
import { render, screen, fireEvent } from "@testing-library/react";
import { render, screen, fireEvent, act } from "@testing-library/react";
import "@testing-library/jest-dom";
import { mockResizeObserver } from "./mocks";
import { Manager } from "@react-ck/manager";

/**
* This test is mostly aimed to ensure that the keeps a native-like behaviour
*/

describe("unit Select", () => {
beforeAll(() => {
mockResizeObserver();

// make RAF synchronous so that position engine renders immediately
Object.defineProperty(window, "requestAnimationFrame", {
writable: true,
value: (cb: () => void) => {
cb();
},
});
});

it("renders correctly", async () => {
render(<Select data-testid="select" />);
const find = await screen.findByTestId("select");
Expand All @@ -22,16 +40,18 @@ describe("unit Select", () => {
expect(find.value).toBe(value);
});

it("should get set", () => {
it("should get set value", () => {
const value = "123";
render(
<Select data-testid="select" value={value}>
<Select.Option>000</Select.Option>
<Select.Option selected>{value}</Select.Option>
</Select>,
);
const find = screen.getByDisplayValue<HTMLSelectElement>(value);
expect(find.value).toBe(value);
const find = screen.queryByDisplayValue<HTMLSelectElement>(value);

expect(find).toBeInTheDocument();
expect(find?.value).toBe(value);
});

it("should not fire onchange by default", () => {
Expand All @@ -55,20 +75,27 @@ describe("unit Select", () => {
const value = "123";
const handleChange = jest.fn();

render(
<Select data-testid="select" onChange={handleChange}>
<Select.Option>{initialValue}</Select.Option>
<Select.Option>{value}</Select.Option>
</Select>,
const r = render(
<Manager>
<Select data-testid="select" value={initialValue} onChange={handleChange}>
<Select.Option>{initialValue}</Select.Option>
<Select.Option>{value}</Select.Option>
</Select>
</Manager>,
);
const find = screen.getByDisplayValue<HTMLSelectElement>(initialValue);
const option1 = find.getElementsByTagName("option").item(0);
const option2 = find.getElementsByTagName("option").item(1);

fireEvent.change(find, { target: { value } });
const root = screen.getByTestId("select");

// Open select
fireEvent.focus(root);

const [, option2] = r.baseElement.querySelectorAll("li");

if (option2) fireEvent.click(option2);

const nativeElement = screen.getByDisplayValue<HTMLSelectElement>(value);

expect(handleChange).toHaveBeenCalledTimes(1);
expect(option1?.selected).toBe(false);
expect(option2?.selected).toBe(true);
expect(nativeElement.value).toBe(value);
});
});
22 changes: 22 additions & 0 deletions packages/components/select/specs/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
type MockFetch<T extends object> = () => Promise<{ json: () => T }>;

function mockResizeObserverImplementation<T extends object>(data: T | Error): MockFetch<T> {
return jest.fn().mockImplementation(
async (): ReturnType<MockFetch<T>> =>
data instanceof Error
? Promise.reject(data)
: Promise.resolve({
json: () => data,
}),
);
}

export const mockResizeObserver = (): void => {
Object.defineProperty(window, "ResizeObserver", {
writable: true,
value: class {
public observe = (): void => undefined;
public disconnect = (): void => undefined;
},
});
};
45 changes: 40 additions & 5 deletions packages/components/select/src/SelectOption.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
import React from "react";
import { Menu } from "@react-ck/provisional";
import React, { useContext, useMemo } from "react";
import { SelectContext } from "./context";
import { componentToText } from "@react-ck/react-utils";
import { type SelectOptionProps } from "./types";

export type SelectOptionProps = React.OptionHTMLAttributes<HTMLOptionElement>;
export const SelectOption = ({
value,
disabled,
children,
...props
}: Readonly<SelectOptionProps>): React.ReactElement => {
const context = useContext(SelectContext);

export const SelectOption = ({ ...props }: Readonly<SelectOptionProps>): React.ReactElement => (
<option {...props} />
);
const childrenText = useMemo(() => componentToText(children), [children]);

const computedValue = useMemo(() => {
const v = value ?? childrenText;

if (!v || v.length === 0) throw new Error("Select option has no value");

return v;
}, [childrenText, value]);

const displayChildren = useMemo(() => children ?? value, [children, value]);

const isCurrentlySelected = context?.selectedValues.includes(computedValue);

return (
<Menu.Item
{...props}
disabled={disabled}
skin={isCurrentlySelected ? "primary" : "default"}
onClick={() => {
if (disabled) return;

context?.handleChange(computedValue, isCurrentlySelected ? "deselect" : "select");
}}>
{displayChildren}
</Menu.Item>
);
};
9 changes: 9 additions & 0 deletions packages/components/select/src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from "react";
import { type ChangeHandler, type SelectedValues } from "./types";

export interface SelectContextProps {
selectedValues: SelectedValues;
handleChange: ChangeHandler;
}

export const SelectContext = createContext<SelectContextProps | undefined>(undefined);
Loading

0 comments on commit bdfc124

Please sign in to comment.