Skip to content

Commit

Permalink
feat(Select): Added new Select component DEV-53
Browse files Browse the repository at this point in the history
  • Loading branch information
giubatt committed May 19, 2021
1 parent 18eb3f0 commit 6fe6409
Show file tree
Hide file tree
Showing 11 changed files with 1,080 additions and 0 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module.exports = {
rules: {
"unicorn/no-nested-ternary": "off",
"unicorn/no-null": "off",
"unicorn/prefer-spread": "off",
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react/self-closing-comp": ["error"],
Expand Down
152 changes: 152 additions & 0 deletions src/components/Select/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useState } from "react"
import { Meta, Story } from "@storybook/react/types-6-0"

import Select, { SelectProps, SelectedItems } from "./Select"
import { mockedOptions } from "./__mocks__/options"

const mockedChildrenOptions = mockedOptions.map(({ label, value }) => (
<Select.Option key={value} value={value}>
{label}
</Select.Option>
))

export default {
title: "Design System/Select",
component: Select,
} as Meta

const Template: Story<SelectProps> = (props: SelectProps) => {
const [value, setValue] = useState<SelectProps["value"]>(
props.value || props.defaultValue || ""
)

return (
<Select
{...props}
value={value}
onChange={(newValue) => {
props.onChange?.(newValue)
setValue(newValue)
}}
/>
)
}

/** Multi selector controlled to make sure the selected items become persistent through state changes */
const MultiTemplate: Story<SelectProps> = (props: SelectProps) => {
const [selectedItems, setSelectedItems] = useState<SelectedItems>(
props.defaultSelectedItems || props.selectedItems || []
)

return (
<Select
{...props}
selectedItems={selectedItems}
onSelectedItemsChange={(newSelectedItems) => {
setSelectedItems(newSelectedItems)
}}
>
{mockedOptions.map(({ label, value }) => (
<Select.Option key={value} value={value}>
{label}
</Select.Option>
))}
</Select>
)
}

export const Default = Template.bind({})
Default.args = {
children: mockedChildrenOptions.slice(0, 5),
id: "default",
}

export const WithLabel = Template.bind({})
WithLabel.args = {
children: mockedChildrenOptions,
label: "Select an element: ",
}

export const WithDefaultValue = Template.bind({})
WithDefaultValue.args = {
children: [
<Select.Option key={200} value={200}>
<span
style={{
display: "flex",
alignItems: "center",
}}
>
<span
style={{
marginRight: "8px",
borderRadius: "50%",
background: "#30D158",
width: "8px",
height: "8px",
}}
/>
Active
</span>
</Select.Option>,
...mockedChildrenOptions,
],
defaultValue: 200,
}

export const MultipleSelection = MultiTemplate.bind({})
MultipleSelection.args = {
children: mockedChildrenOptions,
label: "Select one or multiple elements: ",
multi: true,
}

export const WithDefaultSelectedItems = MultiTemplate.bind({})
WithDefaultSelectedItems.args = {
children: mockedChildrenOptions,
label: "Select one or multiple elements: ",
multi: true,
defaultSelectedItems: [1, 5],
}

export const Disabled = Template.bind({})
Disabled.args = {
children: mockedChildrenOptions,
label: "Select an element: ",
disabled: true,
}

export const WithoutPlaceholder = Template.bind({})
WithoutPlaceholder.args = {
children: mockedChildrenOptions.slice(0, 5),
placeholder: " ",
fullWidth: true,
}

const TemplateObjectValues: Story<SelectProps> = (props: SelectProps) => {
const [value, setValue] = useState<SelectProps["value"]>(
props.value || props.defaultValue || ""
)

return (
<Select
{...props}
value={value}
onChange={(newValue) => {
props.onChange?.(newValue)
setValue(newValue)
}}
/>
)
}
export const WithObjectValues = TemplateObjectValues.bind({})
WithObjectValues.args = {
children: [
<Select.Option
key={JSON.stringify({ from: "10/10/10", to: "12/12/12" })}
value={{ from: "10/10/10", to: "12/12/12" }}
>
Today
</Select.Option>,
],
}
131 changes: 131 additions & 0 deletions src/components/Select/Select.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { useState } from "react"
import { fireEvent, render } from "../../tests/utils"

import { mockedOptions } from "./__mocks__/options"
import Select, { SelectProps } from "./Select"
const children = mockedOptions.map(({ label, value }) => (
<Select.Option key={value} label={label} value={value} />
))

const ControlledMultiSelectTemplate = (props: SelectProps) => {
const [selectedItems, setSelectedItems] = useState<
SelectProps["selectedItems"]
>([])

return (
<Select
selectedItems={selectedItems}
multi={true}
{...props}
onSelectedItemsChange={(selectedItems) => {
setSelectedItems(selectedItems)
props.onSelectedItemsChange?.(selectedItems)
}}
/>
)
}

describe("Select", () => {
it("renders successfully", () => {
const { getByRole } = render(<Select>{children}</Select>)

/**
* Check if the element exists
*/
expect(
getByRole("button", { name: "Select an option" })
).toBeInTheDocument()
})

it("renders the label succesfully", () => {
const { getByText } = render(
<Select label="Select an element:">{children}</Select>
)

expect(getByText("Select an element:")).toBeInTheDocument()
})

it("renders all the options and allows to select", () => {
const onChangeMock = jest.fn()
const { getByText, queryByTitle, getByRole } = render(
<Select onChange={onChangeMock}>{children}</Select>
)

/** Act: enable the options by clicking on the button */
const button = getByRole("button")
fireEvent.click(button)

/**
* Check if the options are rendered
*/
mockedOptions.forEach((option) => {
expect(getByText(option.label)).toBeInTheDocument()
})

/**
* Check for an invalid option
*/
expect(queryByTitle("Random")).not.toBeInTheDocument()

/** Try to select an option */
const option = getByText(mockedOptions[1].label)
fireEvent.click(option)

expect(onChangeMock).toHaveBeenCalled()
})

describe("onSelectedItemsChange", () => {
it("allows to add and remove a multi select item", () => {
const onSelectedItemsChangeMock = jest.fn()

const { getByText, getByRole } = render(
<ControlledMultiSelectTemplate
onSelectedItemsChange={onSelectedItemsChangeMock}
>
{children}
</ControlledMultiSelectTemplate>
)

/** Act: enable the options by clicking on the button */
const button = getByRole("button")
fireEvent.click(button)

/** Try to select an option */
const option = getByText(mockedOptions[1].label)

fireEvent.click(option)

/** Select again to remove it */
fireEvent.click(option)

expect(onSelectedItemsChangeMock).toHaveBeenCalledTimes(2)
})
})

describe("handleBulkAction", () => {
it("allows to user to perform bulk actions to select and deselect items", () => {
const onSelectedItemsChangeMock = jest.fn()

const { getByText, getByRole } = render(
<ControlledMultiSelectTemplate
onSelectedItemsChange={onSelectedItemsChangeMock}
>
{children}
</ControlledMultiSelectTemplate>
)

/** Act: enable the options by clicking on the button */
const button = getByRole("button")
fireEvent.click(button)

/** Trigger bulk actions */
const selectAllOption = getByText("Select all")
fireEvent.click(selectAllOption)

const deselectAllOptions = getByText("Deselect all")
fireEvent.click(deselectAllOptions)

expect(onSelectedItemsChangeMock).toHaveBeenCalledTimes(2)
})
})
})
Loading

0 comments on commit 6fe6409

Please sign in to comment.