@@ -66,3 +66,4 @@ | |||||
# Others | # Others | ||||
- [X] Add `select-none` to input labels, etc. | - [X] Add `select-none` to input labels, etc. | ||||
- [ ] Test all components! |
@@ -3,7 +3,10 @@ | |||||
"rules": { | "rules": { | ||||
"quote-props": "off", | "quote-props": "off", | ||||
"react/jsx-props-no-spreading": "off", | "react/jsx-props-no-spreading": "off", | ||||
"react/button-has-type": "off" | |||||
"react/button-has-type": "off", | |||||
"@typescript-eslint/no-unsafe-call": "off", | |||||
"@typescript-eslint/no-unsafe-member-access": "off", | |||||
"import/no-extraneous-dependencies": "off" | |||||
}, | }, | ||||
"extends": [ | "extends": [ | ||||
"lxsmnsyc/typescript/react" | "lxsmnsyc/typescript/react" | ||||
@@ -18,6 +18,7 @@ | |||||
"@testing-library/user-event": "^14.4.3", | "@testing-library/user-event": "^14.4.3", | ||||
"@types/node": "^18.14.1", | "@types/node": "^18.14.1", | ||||
"@types/react": "^18.0.27", | "@types/react": "^18.0.27", | ||||
"@types/testing-library__jest-dom": "^5.14.7", | |||||
"eslint": "^8.35.0", | "eslint": "^8.35.0", | ||||
"eslint-config-lxsmnsyc": "^0.5.0", | "eslint-config-lxsmnsyc": "^0.5.0", | ||||
"jsdom": "^21.1.0", | "jsdom": "^21.1.0", | ||||
@@ -1,4 +0,0 @@ | |||||
import matchers from '@testing-library/jest-dom/matchers'; | |||||
import { expect } from 'vitest'; | |||||
expect.extend(matchers); |
@@ -12,13 +12,12 @@ import { | |||||
it, | it, | ||||
expect, afterEach, | expect, afterEach, | ||||
} from 'vitest'; | } from 'vitest'; | ||||
import matchers from '@testing-library/jest-dom/matchers'; | |||||
import { | import { | ||||
ActionButton, | ActionButton, | ||||
} from '.'; | } from '.'; | ||||
import matchers from '@testing-library/jest-dom/matchers'; | |||||
expect.extend(matchers); | |||||
vi.mock('@tesseract-design/web-base-button'); | |||||
expect.extend(matchers); | |||||
describe('ActionButton', () => { | describe('ActionButton', () => { | ||||
afterEach(() => { | afterEach(() => { | ||||
@@ -65,7 +64,7 @@ describe('ActionButton', () => { | |||||
}); | }); | ||||
it('handles click events', async () => { | it('handles click events', async () => { | ||||
const onClick = vi.fn().mockImplementationOnce((e) => { e.preventDefault() }); | |||||
const onClick = vi.fn().mockImplementationOnce((e) => { e.preventDefault(); }); | |||||
render( | render( | ||||
<ActionButton | <ActionButton | ||||
onClick={onClick} | onClick={onClick} | ||||
@@ -94,7 +93,7 @@ describe('ActionButton', () => { | |||||
${'small'} | ${'h-10'} | ${'small'} | ${'h-10'} | ||||
${'medium'} | ${'h-12'} | ${'medium'} | ${'h-12'} | ||||
${'large'} | ${'h-16'} | ${'large'} | ${'h-16'} | ||||
`('on %s size', ({ | |||||
`('on $size size', ({ | |||||
size, | size, | ||||
className, | className, | ||||
}: { size: Button.Size, className: string }) => { | }: { size: Button.Size, className: string }) => { | ||||
@@ -127,7 +126,7 @@ describe('ActionButton', () => { | |||||
${'bare'} | ${'bg-negative'} | ${'bare'} | ${'bg-negative'} | ||||
${'outline'} | ${'border-2'} | ${'outline'} | ${'border-2'} | ||||
${'filled'} | ${'bg-primary'} | ${'filled'} | ${'bg-primary'} | ||||
`('renders a button with variant %s', ({ | |||||
`('renders a button with $variant variant', ({ | |||||
variant, | variant, | ||||
className, | className, | ||||
}: { variant: Button.Variant, className: string }) => { | }: { variant: Button.Variant, className: string }) => { | ||||
@@ -157,7 +156,7 @@ describe('ActionButton', () => { | |||||
render( | render( | ||||
<ActionButton> | <ActionButton> | ||||
Foo | Foo | ||||
</ActionButton> | |||||
</ActionButton>, | |||||
); | ); | ||||
const children: HTMLElement = screen.getByTestId('children'); | const children: HTMLElement = screen.getByTestId('children'); | ||||
@@ -168,7 +167,7 @@ describe('ActionButton', () => { | |||||
render( | render( | ||||
<ActionButton | <ActionButton | ||||
type={buttonType} | type={buttonType} | ||||
/> | |||||
/>, | |||||
); | ); | ||||
const button: HTMLButtonElement = screen.getByRole('button'); | const button: HTMLButtonElement = screen.getByRole('button'); | ||||
expect(button).toHaveProperty('type', buttonType); | expect(button).toHaveProperty('type', buttonType); | ||||
@@ -178,7 +177,7 @@ describe('ActionButton', () => { | |||||
render( | render( | ||||
<ActionButton | <ActionButton | ||||
disabled | disabled | ||||
/> | |||||
/>, | |||||
); | ); | ||||
const button: HTMLButtonElement = screen.getByTestId('button'); | const button: HTMLButtonElement = screen.getByTestId('button'); | ||||
expect(button).toBeDisabled(); | expect(button).toBeDisabled(); | ||||
@@ -15,8 +15,10 @@ | |||||
"devDependencies": { | "devDependencies": { | ||||
"@testing-library/jest-dom": "^5.16.5", | "@testing-library/jest-dom": "^5.16.5", | ||||
"@testing-library/react": "^13.4.0", | "@testing-library/react": "^13.4.0", | ||||
"@testing-library/user-event": "^14.4.3", | |||||
"@types/node": "^18.14.1", | "@types/node": "^18.14.1", | ||||
"@types/react": "^18.0.27", | "@types/react": "^18.0.27", | ||||
"@types/testing-library__jest-dom": "^5.14.7", | |||||
"eslint": "^8.35.0", | "eslint": "^8.35.0", | ||||
"eslint-config-lxsmnsyc": "^0.5.0", | "eslint-config-lxsmnsyc": "^0.5.0", | ||||
"jsdom": "^21.1.0", | "jsdom": "^21.1.0", | ||||
@@ -83,4 +85,4 @@ | |||||
"typesVersions": { | "typesVersions": { | ||||
"*": {} | "*": {} | ||||
} | } | ||||
} | |||||
} |
@@ -1,9 +1,3 @@ | |||||
{ | { | ||||
"target": "es2018", | |||||
"entryPoints": { | |||||
".": "src/index.ts", | |||||
"./dist/DropdownSelect.css": "src/components/DropdownSelect/DropdownSelect.css", | |||||
"./dist/RadioButton.css": "src/components/RadioButton/RadioButton.css", | |||||
"./dist/RadioTickBox.css": "src/components/RadioTickBox/RadioTickBox.css" | |||||
} | |||||
"target": "es2018" | |||||
} | } |
@@ -10,7 +10,7 @@ export type ComboBoxDerivedElement = HTMLInputElement; | |||||
/** | /** | ||||
* Props of the {@link ComboBox} component. | * Props of the {@link ComboBox} component. | ||||
*/ | */ | ||||
export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list' | 'inputMode'> { | |||||
export interface ComboBoxProps extends Omit<React.HTMLProps<ComboBoxDerivedElement>, 'size' | 'type' | 'label' | 'list' | 'inputMode'> { | |||||
/** | /** | ||||
* Short textual description indicating the nature of the component's value. | * Short textual description indicating the nature of the component's value. | ||||
*/ | */ | ||||
@@ -73,6 +73,7 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>( | |||||
children, | children, | ||||
inputMode = 'text' as const, | inputMode = 'text' as const, | ||||
id: idProp, | id: idProp, | ||||
style, | |||||
...etcProps | ...etcProps | ||||
}: ComboBoxProps, | }: ComboBoxProps, | ||||
forwardedRef, | forwardedRef, | ||||
@@ -106,6 +107,7 @@ export const ComboBox = React.forwardRef<ComboBoxDerivedElement, ComboBoxProps>( | |||||
}, | }, | ||||
className, | className, | ||||
)} | )} | ||||
style={style} | |||||
> | > | ||||
<input | <input | ||||
{...etcProps} | {...etcProps} | ||||
@@ -0,0 +1,235 @@ | |||||
import * as React from 'react'; | |||||
import { | |||||
cleanup, | |||||
render, | |||||
screen, | |||||
} from '@testing-library/react'; | |||||
import { TextControl } from '@tesseract-design/web-base'; | |||||
import userEvent from '@testing-library/user-event'; | |||||
import { | |||||
afterEach, | |||||
expect, | |||||
vi, | |||||
describe, | |||||
it, | |||||
} from 'vitest'; | |||||
import matchers from '@testing-library/jest-dom/matchers'; | |||||
import { | |||||
DropdownSelect, | |||||
} from '.'; | |||||
expect.extend(matchers); | |||||
describe('DropdownSelect', () => { | |||||
afterEach(() => { | |||||
cleanup(); | |||||
}); | |||||
it('renders a combobox', () => { | |||||
render(<DropdownSelect />); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox).toBeInTheDocument(); | |||||
}); | |||||
it('renders a border', () => { | |||||
render( | |||||
<DropdownSelect | |||||
border | |||||
/>, | |||||
); | |||||
const border = screen.getByTestId('border'); | |||||
expect(border).toBeInTheDocument(); | |||||
}); | |||||
it('renders a label', () => { | |||||
render( | |||||
<DropdownSelect | |||||
label="foo" | |||||
/>, | |||||
); | |||||
const combobox = screen.getByLabelText('foo'); | |||||
expect(combobox).toBeInTheDocument(); | |||||
const label = screen.getByTestId('label'); | |||||
expect(label).toHaveTextContent('foo'); | |||||
}); | |||||
it('renders a hidden label', () => { | |||||
render( | |||||
<DropdownSelect | |||||
label="foo" | |||||
hiddenLabel | |||||
/>, | |||||
); | |||||
const combobox = screen.getByLabelText('foo'); | |||||
expect(combobox).toBeInTheDocument(); | |||||
const label = screen.getByTestId('label'); | |||||
expect(label).toHaveClass('sr-only'); | |||||
}); | |||||
it('renders a hint', () => { | |||||
render( | |||||
<DropdownSelect | |||||
hint="foo" | |||||
/>, | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toBeInTheDocument(); | |||||
}); | |||||
it('render options with implicit values', () => { | |||||
render( | |||||
<DropdownSelect> | |||||
<option>foo</option> | |||||
<option>bar</option> | |||||
</DropdownSelect>, | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(2); | |||||
}); | |||||
it('renders valid options', () => { | |||||
render( | |||||
<DropdownSelect> | |||||
<option value="foo">foo</option> | |||||
<option value="bar">bar</option> | |||||
</DropdownSelect>, | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(2); | |||||
}); | |||||
it('renders option groups', () => { | |||||
render( | |||||
<DropdownSelect> | |||||
<optgroup label="foo"> | |||||
<option value="baz">baz</option> | |||||
</optgroup> | |||||
<optgroup label="bar"> | |||||
<option value="quux">quux</option> | |||||
<option value="quuux">quuux</option> | |||||
</optgroup> | |||||
</DropdownSelect>, | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox.children).toHaveLength(2); | |||||
expect(combobox.children[0].children).toHaveLength(1); | |||||
expect(combobox.children[1].children).toHaveLength(2); | |||||
}); | |||||
describe.each` | |||||
size | inputClassNames | hintClassNames | indicatorClassNames | |||||
${'small'} | ${['h-10']} | ${['pr-10']} | ${['w-10']} | |||||
${'medium'} | ${['h-12']} | ${['pr-12']} | ${['w-12']} | |||||
${'large'} | ${['h-16']} | ${['pr-16']} | ${['w-16']} | |||||
`('on $size size', ({ | |||||
size, | |||||
inputClassNames, | |||||
hintClassNames, | |||||
indicatorClassNames, | |||||
}: { | |||||
size: TextControl.Size, | |||||
inputClassNames: string[], | |||||
hintClassNames: string[], | |||||
indicatorClassNames: string[], | |||||
}) => { | |||||
it('renders input styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
size={size} | |||||
/>, | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox).toHaveClass(...inputClassNames); | |||||
}); | |||||
it('renders hint styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
size={size} | |||||
hint="hint" | |||||
indicator="a" | |||||
/>, | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toHaveClass(...hintClassNames); | |||||
}); | |||||
it('renders indicator styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
size={size} | |||||
indicator="a'" | |||||
/>, | |||||
); | |||||
const indicator = screen.getByTestId('indicator'); | |||||
expect(indicator).toHaveClass(...indicatorClassNames); | |||||
}); | |||||
}); | |||||
it('renders a block input', () => { | |||||
render( | |||||
<DropdownSelect | |||||
block | |||||
/>, | |||||
); | |||||
const base = screen.getByTestId('base'); | |||||
expect(base).toHaveClass('block'); | |||||
}); | |||||
describe.each` | |||||
variant | inputClassNames | hintClassNames | |||||
${'default'} | ${['pl-4']} | ${['bottom-0', 'pl-4', 'pb-1']} | |||||
${'alternate'} | ${['pl-1.5', 'pt-4']} | ${['top-0.5']} | |||||
`('on $variant variant', ({ | |||||
variant, | |||||
inputClassNames, | |||||
hintClassNames, | |||||
}: { | |||||
variant: TextControl.Variant, | |||||
inputClassNames: string[], | |||||
hintClassNames: string[], | |||||
}) => { | |||||
it('renders input styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
variant={variant} | |||||
/>, | |||||
); | |||||
const combobox = screen.getByRole('combobox'); | |||||
expect(combobox).toHaveClass(...inputClassNames); | |||||
}); | |||||
it('renders hint styles', () => { | |||||
render( | |||||
<DropdownSelect | |||||
variant={variant} | |||||
hint="hint" | |||||
/>, | |||||
); | |||||
const hint = screen.getByTestId('hint'); | |||||
expect(hint).toHaveClass(...hintClassNames); | |||||
}); | |||||
}); | |||||
it('handles change events', async () => { | |||||
const onChange = vi.fn().mockImplementationOnce((e) => { e.preventDefault(); }) | |||||
render( | |||||
<DropdownSelect | |||||
onChange={onChange} | |||||
> | |||||
<option value="foo">foo</option> | |||||
<option value="bar">bar</option> | |||||
</DropdownSelect>, | |||||
); | |||||
const combobox: HTMLSelectElement = screen.getByRole('combobox'); | |||||
const [, secondOption] = screen.getAllByRole('option'); | |||||
await userEvent.selectOptions(combobox, secondOption); | |||||
expect(onChange).toBeCalled(); | |||||
}); | |||||
}); |
@@ -10,7 +10,7 @@ export type DropdownSelectDerivedElement = HTMLSelectElement; | |||||
/** | /** | ||||
* Props of the {@link DropdownSelect} component. | * Props of the {@link DropdownSelect} component. | ||||
*/ | */ | ||||
export interface DropdownSelectProps extends Omit<React.HTMLProps<DropdownSelectDerivedElement>, 'size' | 'type' | 'style' | 'label' | 'list' | 'multiple'> { | |||||
export interface DropdownSelectProps extends Omit<React.HTMLProps<DropdownSelectDerivedElement>, 'size' | 'type' | 'label' | 'list' | 'multiple'> { | |||||
/** | /** | ||||
* Short textual description indicating the nature of the component's value. | * Short textual description indicating the nature of the component's value. | ||||
*/ | */ | ||||
@@ -61,6 +61,7 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro | |||||
className, | className, | ||||
children, | children, | ||||
id: idProp, | id: idProp, | ||||
style, | |||||
...etcProps | ...etcProps | ||||
}: DropdownSelectProps, | }: DropdownSelectProps, | ||||
forwardedRef, | forwardedRef, | ||||
@@ -80,6 +81,8 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro | |||||
}, | }, | ||||
className, | className, | ||||
)} | )} | ||||
data-testid="base" | |||||
style={style} | |||||
> | > | ||||
<select | <select | ||||
{...etcProps} | {...etcProps} | ||||
@@ -87,6 +90,7 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro | |||||
id={id} | id={id} | ||||
aria-labelledby={labelId} | aria-labelledby={labelId} | ||||
data-testid="input" | data-testid="input" | ||||
role="combobox" | |||||
className={clsx( | className={clsx( | ||||
'tesseract-design-dropdown-select bg-negative rounded-inherit w-full peer block appearance-none cursor-pointer select-none font-inherit', | 'tesseract-design-dropdown-select bg-negative rounded-inherit w-full peer block appearance-none cursor-pointer select-none font-inherit', | ||||
'focus:outline-0', | 'focus:outline-0', | ||||
@@ -180,6 +184,7 @@ export const DropdownSelect = React.forwardRef<DropdownSelectDerivedElement, Dro | |||||
)} | )} | ||||
{indicator && ( | {indicator && ( | ||||
<div | <div | ||||
data-testid="indicator" | |||||
className={clsx( | className={clsx( | ||||
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none', | 'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none select-none', | ||||
{ | { | ||||
@@ -54,6 +54,9 @@ importers: | |||||
'@types/react': | '@types/react': | ||||
specifier: ^18.0.27 | specifier: ^18.0.27 | ||||
version: 18.2.14 | version: 18.2.14 | ||||
'@types/testing-library__jest-dom': | |||||
specifier: ^5.14.7 | |||||
version: 5.14.7 | |||||
eslint: | eslint: | ||||
specifier: ^8.35.0 | specifier: ^8.35.0 | ||||
version: 8.43.0 | version: 8.43.0 | ||||
@@ -152,12 +155,18 @@ importers: | |||||
'@testing-library/react': | '@testing-library/react': | ||||
specifier: ^13.4.0 | specifier: ^13.4.0 | ||||
version: 13.4.0(react-dom@18.2.0)(react@18.2.0) | version: 13.4.0(react-dom@18.2.0)(react@18.2.0) | ||||
'@testing-library/user-event': | |||||
specifier: ^14.4.3 | |||||
version: 14.4.3(@testing-library/dom@8.20.1) | |||||
'@types/node': | '@types/node': | ||||
specifier: ^18.14.1 | specifier: ^18.14.1 | ||||
version: 18.14.1 | version: 18.14.1 | ||||
'@types/react': | '@types/react': | ||||
specifier: ^18.0.27 | specifier: ^18.0.27 | ||||
version: 18.2.14 | version: 18.2.14 | ||||
'@types/testing-library__jest-dom': | |||||
specifier: ^5.14.7 | |||||
version: 5.14.7 | |||||
eslint: | eslint: | ||||
specifier: ^8.35.0 | specifier: ^8.35.0 | ||||
version: 8.43.0 | version: 8.43.0 | ||||
@@ -1906,7 +1915,7 @@ packages: | |||||
dependencies: | dependencies: | ||||
'@adobe/css-tools': 4.2.0 | '@adobe/css-tools': 4.2.0 | ||||
'@babel/runtime': 7.22.5 | '@babel/runtime': 7.22.5 | ||||
'@types/testing-library__jest-dom': 5.14.6 | |||||
'@types/testing-library__jest-dom': 5.14.7 | |||||
aria-query: 5.3.0 | aria-query: 5.3.0 | ||||
chalk: 3.0.0 | chalk: 3.0.0 | ||||
css.escape: 1.5.1 | css.escape: 1.5.1 | ||||
@@ -2054,8 +2063,8 @@ packages: | |||||
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} | resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} | ||||
dev: true | dev: true | ||||
/@types/testing-library__jest-dom@5.14.6: | |||||
resolution: {integrity: sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA==} | |||||
/@types/testing-library__jest-dom@5.14.7: | |||||
resolution: {integrity: sha512-PFDoAbR9y8pD9+41oM1Yy0nVCkaRPlklmDZoPCXhNpR0ZO13HAYWqdNEjLtvIiveBmfB/+jdvmuOVeOXehKOaA==} | |||||
dependencies: | dependencies: | ||||
'@types/jest': 29.5.2 | '@types/jest': 29.5.2 | ||||
dev: true | dev: true | ||||