Browse Source

Migrate tests

Use old tests for components.
master
TheoryOfNekomata 1 year ago
parent
commit
1a1b0473df
40 changed files with 1904 additions and 165 deletions
  1. +9
    -3
      base/src/button.ts
  2. +12
    -4
      base/src/text-control.ts
  3. +19
    -16
      categories/action/react/src/components/ActionButton/ActionButton.test.tsx
  4. +0
    -0
      categories/action/react/src/web-action-react.test.ts
  5. +1
    -1
      categories/choice/react/package.json
  6. +6
    -4
      categories/choice/react/src/components/DropdownSelect/DropdownSelect.test.tsx
  7. +34
    -26
      categories/choice/react/src/components/RadioButton/RadioButton.test.tsx
  8. +1
    -3
      categories/choice/react/src/components/RadioButton/index.tsx
  9. +90
    -0
      categories/choice/react/src/components/RadioTickBox/RadioTickBox.test.tsx
  10. +1
    -0
      categories/choice/react/src/components/RadioTickBox/index.tsx
  11. +0
    -0
      categories/choice/react/src/web-choice-react.test.ts
  12. +2
    -1
      categories/freeform/react/.eslintrc
  13. +4
    -1
      categories/freeform/react/package.json
  14. +242
    -0
      categories/freeform/react/src/components/MaskedTextInput/MaskedTextInput.test.tsx
  15. +2
    -0
      categories/freeform/react/src/components/MaskedTextInput/index.tsx
  16. +241
    -0
      categories/freeform/react/src/components/MultilineTextInput/MultilineTextInput.test.tsx
  17. +2
    -0
      categories/freeform/react/src/components/MultilineTextInput/index.tsx
  18. +263
    -0
      categories/freeform/react/src/components/TextInput/TextInput.test.tsx
  19. +2
    -0
      categories/freeform/react/src/components/TextInput/index.tsx
  20. +12
    -0
      categories/freeform/react/src/web-freeform-react.test.ts
  21. +4
    -1
      categories/information/react/package.json
  22. +44
    -0
      categories/information/react/src/components/Badge/Badge.test.tsx
  23. +1
    -0
      categories/information/react/src/components/Badge/index.tsx
  24. +11
    -0
      categories/information/react/src/web-information-react.test.ts
  25. +2
    -1
      categories/multichoice/react/.eslintrc
  26. +4
    -1
      categories/multichoice/react/package.json
  27. +224
    -0
      categories/multichoice/react/src/components/ToggleButton/ToggleButton.test.tsx
  28. +12
    -11
      categories/multichoice/react/src/components/ToggleButton/index.tsx
  29. +158
    -0
      categories/multichoice/react/src/components/ToggleSwitch/ToggleSwitch.test.tsx
  30. +8
    -7
      categories/multichoice/react/src/components/ToggleSwitch/index.tsx
  31. +130
    -0
      categories/multichoice/react/src/components/ToggleTickBox/ToggleTickBox.test.tsx
  32. +8
    -7
      categories/multichoice/react/src/components/ToggleTickBox/index.tsx
  33. +14
    -0
      categories/multichoice/react/src/web-multichoice-react.test.ts
  34. +4
    -1
      categories/navigation/react/package.json
  35. +203
    -0
      categories/navigation/react/src/components/LinkButton/LinkButton.test.tsx
  36. +76
    -68
      categories/navigation/react/src/components/LinkButton/index.tsx
  37. +10
    -0
      categories/navigation/react/src/web-navigation-react.test.ts
  38. +2
    -1
      categories/number/react/.eslintrc
  39. +44
    -8
      pnpm-lock.yaml
  40. +2
    -0
      showcases/web-kitchensink-reactnext/src/pages/categories/option/index.tsx

+ 9
- 3
base/src/button.ts View File

@@ -1,5 +1,11 @@
export type Type = 'submit' | 'reset' | 'button';
export const AVAILABLE_TYPES = ['submit', 'reset', 'button'] as const;

export type Variant = 'bare' | 'filled' | 'outline';
export type Type = typeof AVAILABLE_TYPES[number];

export type Size = 'small' | 'medium' | 'large';
export const AVAILABLE_VARIANTS = ['bare', 'filled', 'outline'] as const;

export type Variant = typeof AVAILABLE_VARIANTS[number];

export const AVAILABLE_SIZES = ['small', 'medium', 'large'] as const;

export type Size = typeof AVAILABLE_SIZES[number];

+ 12
- 4
base/src/text-control.ts View File

@@ -1,7 +1,15 @@
export type Size = 'small' | 'medium' | 'large';
export const AVAILABLE_SIZES = ['small', 'medium', 'large'] as const;

export type Variant = 'default' | 'alternate';
export type Size = typeof AVAILABLE_SIZES[number];

export type InputType = 'text' | 'search';
export const AVAILABLE_VARIANTS = ['default', 'alternate'] as const;

export type InputMode = 'none' | 'numeric' | 'decimal' | InputType;
export type Variant = typeof AVAILABLE_VARIANTS[number];

export const AVAILABLE_INPUT_TYPES = ['text', 'search'] as const;

export type InputType = typeof AVAILABLE_INPUT_TYPES[number];

export const AVAILABLE_INPUT_MODES = ['none', 'numeric', 'decimal', ...AVAILABLE_INPUT_TYPES] as const;

export type InputMode = typeof AVAILABLE_INPUT_MODES[number];

+ 19
- 16
categories/action/react/src/components/ActionButton/ActionButton.test.tsx View File

@@ -16,6 +16,7 @@ import {
import matchers from '@testing-library/jest-dom/matchers';
import {
ActionButton,
ActionButtonDerivedElement,
} from '.';

expect.extend(matchers);
@@ -29,7 +30,7 @@ describe('ActionButton', () => {
render(
<ActionButton />,
);
const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button).toHaveProperty('type', 'button');
});
@@ -40,7 +41,7 @@ describe('ActionButton', () => {
subtext="subtext"
/>,
);
const subtext: HTMLElement = screen.getByTestId('subtext');
const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

@@ -50,7 +51,7 @@ describe('ActionButton', () => {
badge="badge"
/>,
);
const badge: HTMLElement = screen.getByTestId('badge');
const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});

@@ -60,21 +61,23 @@ describe('ActionButton', () => {
menuItem
/>,
);
const menuItemIndicator: HTMLElement = screen.getByTestId('menuItemIndicator');
const menuItemIndicator = screen.getByTestId('menuItemIndicator');
expect(menuItemIndicator).toBeInTheDocument();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce((e: React.MouseEvent) => {
e.preventDefault();
});
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<ActionButtonDerivedElement>) => {
e.preventDefault();
},
);

render(
<ActionButton
onClick={onClick}
/>,
);
const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
await userEvent.click(button);
expect(onClick).toBeCalled();
});
@@ -86,7 +89,7 @@ describe('ActionButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
expect(button).toHaveClass('pl-2 gap-2 pr-2');
});

@@ -106,7 +109,7 @@ describe('ActionButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
expect(button).toHaveClass(className);
});

@@ -138,7 +141,7 @@ describe('ActionButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
expect(button).toHaveClass(className);
});

@@ -149,7 +152,7 @@ describe('ActionButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
expect(button).toHaveClass('w-full flex');
});

@@ -160,17 +163,17 @@ describe('ActionButton', () => {
</ActionButton>,
);

const children: HTMLElement = screen.getByTestId('children');
const children = screen.getByTestId('children');
expect(children).toHaveTextContent('Foo');
});

it.each(['button', 'submit'] as const)('renders a button with type %s', (buttonType) => {
it.each(Button.AVAILABLE_TYPES)('renders a button with type %s', (buttonType) => {
render(
<ActionButton
type={buttonType}
/>,
);
const button: HTMLButtonElement = screen.getByRole('button');
const button = screen.getByRole('button');
expect(button).toHaveProperty('type', buttonType);
});

@@ -180,7 +183,7 @@ describe('ActionButton', () => {
disabled
/>,
);
const button: HTMLButtonElement = screen.getByTestId('button');
const button = screen.getByTestId('button');
expect(button).toBeDisabled();
});
});

categories/action/react/src/index.test.ts → categories/action/react/src/web-action-react.test.ts View File


+ 1
- 1
categories/choice/react/package.json View File

@@ -86,4 +86,4 @@
"typesVersions": {
"*": {}
}
}
}

+ 6
- 4
categories/choice/react/src/components/DropdownSelect/DropdownSelect.test.tsx View File

@@ -15,7 +15,7 @@ import {
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
DropdownSelect,
DropdownSelect, DropdownSelectDerivedElement,
} from '.';

expect.extend(matchers);
@@ -231,9 +231,11 @@ describe('DropdownSelect', () => {
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce((e: React.ChangeEvent) => {
e.preventDefault();
});
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<DropdownSelectDerivedElement>) => {
e.preventDefault();
},
);

render(
<DropdownSelect


+ 34
- 26
categories/choice/react/src/components/RadioButton/RadioButton.test.tsx View File

@@ -10,10 +10,14 @@ import {
vi,
expect,
describe,
it, afterEach,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import { RadioButton } from '.';
import {
RadioButton,
RadioButtonDerivedElement,
} from '.';

expect.extend(matchers);

@@ -36,7 +40,7 @@ describe('RadioButton', () => {
subtext="subtext"
/>,
);
const subtext: HTMLElement = screen.getByTestId('subtext');
const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

@@ -46,24 +50,10 @@ describe('RadioButton', () => {
badge="badge"
/>,
);
const badge: HTMLElement = screen.getByTestId('badge');
const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce((e: React.MouseEvent) => {
e.preventDefault();
});
render(
<RadioButton
onClick={onClick}
/>,
);
const button: HTMLInputElement = screen.getByRole('radio');
await userEvent.click(button);
expect(onClick).toBeCalled();
});

it('renders a compact button', () => {
render(
<RadioButton
@@ -91,7 +81,7 @@ describe('RadioButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByTestId('button');
const button = screen.getByTestId('button');
expect(button).toHaveClass(className);
});

@@ -123,7 +113,7 @@ describe('RadioButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByTestId('button');
const button = screen.getByTestId('button');
expect(button).toHaveClass(className);
});

@@ -134,7 +124,7 @@ describe('RadioButton', () => {
/>,
);

const button: HTMLButtonElement = screen.getByTestId('button');
const button = screen.getByTestId('button');
expect(button).toHaveClass('w-full flex');
});

@@ -145,7 +135,7 @@ describe('RadioButton', () => {
</RadioButton>,
);

const children: HTMLElement = screen.getByTestId('children');
const children = screen.getByTestId('children');
expect(children).toHaveTextContent('Foo');
});

@@ -155,14 +145,32 @@ describe('RadioButton', () => {
disabled
/>,
);
const button: HTMLButtonElement = screen.getByRole('radio');
const button = screen.getByRole('radio');
expect(button).toBeDisabled();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<RadioButtonDerivedElement>) => {
e.preventDefault();
},
);
render(
<RadioButton
onClick={onClick}
/>,
);
const button = screen.getByRole('radio');
await userEvent.click(button);
expect(onClick).toBeCalled();
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce((e: React.ChangeEvent) => {
e.preventDefault();
});
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<RadioButtonDerivedElement>) => {
e.preventDefault();
},
);

render(
<RadioButton


+ 1
- 3
categories/choice/react/src/components/RadioButton/index.tsx View File

@@ -78,7 +78,6 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
'peer-focus:outline-0 peer-focus:ring-4 peer-focus:ring-secondary/50',
'active:ring-tertiary/50 active:ring-4',
'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:ring-0',
'text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary',
{
'flex w-full': block,
'inline-flex max-w-full align-middle': !block,
@@ -89,7 +88,7 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
},
{
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary': variant !== 'bare',
'bg-negative': variant === 'bare',
'bg-negative text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary': variant !== 'filled',
'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled',
},
{
@@ -104,7 +103,6 @@ export const RadioButton = React.forwardRef<RadioButtonDerivedElement, RadioButt
className={clsx(
'w-6 h-6 block rounded-full border-2 p-0.5 box-border',
{
'-mr-2': compact,
'border-current': variant !== 'filled',
'border-negative': variant === 'filled',
},


+ 90
- 0
categories/choice/react/src/components/RadioTickBox/RadioTickBox.test.tsx View File

@@ -0,0 +1,90 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
RadioTickBox,
RadioTickBoxDerivedElement,
} from '.';

expect.extend(matchers);

describe('RadioTickBox', () => {
afterEach(() => {
cleanup();
});

it('renders a radio button', () => {
render(
<RadioTickBox />,
);
const checkbox = screen.getByRole('radio');
expect(checkbox).toBeInTheDocument();
});

it('renders a block tick box', () => {
render(
<RadioTickBox
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('flex');
});

it('renders a subtext', () => {
render(
<RadioTickBox
subtext="subtext"
/>,
);
const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<RadioTickBoxDerivedElement>) => {
e.preventDefault();
},
);

render(
<RadioTickBox
onClick={onClick}
/>,
);
const radio = screen.getByRole('radio');
await userEvent.click(radio);
expect(onClick).toBeCalled();
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<RadioTickBoxDerivedElement>) => {
e.preventDefault();
},
);

render(
<RadioTickBox
onChange={onChange}
/>,
);
const radio = screen.getByRole('radio');
await userEvent.click(radio);
expect(onChange).toBeCalled();
});
});

+ 1
- 0
categories/choice/react/src/components/RadioTickBox/index.tsx View File

@@ -48,6 +48,7 @@ export const RadioTickBox = React.forwardRef<RadioTickBoxDerivedElement, RadioTi
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}


categories/choice/react/src/index.test.ts → categories/choice/react/src/web-choice-react.test.ts View File


+ 2
- 1
categories/freeform/react/.eslintrc View File

@@ -2,7 +2,8 @@
"root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
"react/jsx-props-no-spreading": "off",
"import/no-extraneous-dependencies": "off"
},
"extends": [
"lxsmnsyc/typescript/react"


+ 4
- 1
categories/freeform/react/package.json View File

@@ -15,8 +15,11 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^18.14.1",
"@types/react": "^18.0.27",
"@types/testing-library__jest-dom": "^5.14.7",
"@vitest/coverage-v8": "^0.33.0",
"eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0",
"jsdom": "^21.1.0",
@@ -26,7 +29,7 @@
"react-test-renderer": "^18.2.0",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
"vitest": "^0.33.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",


+ 242
- 0
categories/freeform/react/src/components/MaskedTextInput/MaskedTextInput.test.tsx View File

@@ -0,0 +1,242 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TextControl } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
MaskedTextInput,
MaskedTextInputDerivedElement,
} from '.';

expect.extend(matchers);

describe('MaskedTextInput', () => {
afterEach(() => {
cleanup();
});

it('renders a password input', () => {
render(
<MaskedTextInput />,
);
const textbox = screen.getByTestId('input');
expect(textbox).toBeInTheDocument();
expect(textbox).toHaveProperty('type', 'password');
});

it('renders a border', () => {
render(
<MaskedTextInput
border
/>,
);
const border = screen.getByTestId('border');
expect(border).toBeInTheDocument();
});

it('renders a label', () => {
render(
<MaskedTextInput
label="foo"
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.getByTestId('label');
expect(label).toHaveTextContent('foo');
});

it('renders a hidden label', () => {
render(
<MaskedTextInput
label="foo"
hiddenLabel
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.queryByTestId('label');
expect(label).toBeInTheDocument();
expect(label).toHaveClass('sr-only');
});

it('renders a hint', () => {
render(
<MaskedTextInput
hint="foo"
/>,
);
const hint = screen.getByTestId('hint');
expect(hint).toBeInTheDocument();
});

it('renders an indicator', () => {
render(
<MaskedTextInput
indicator={
<div />
}
/>,
);
const indicator = screen.getByTestId('indicator');
expect(indicator).toBeInTheDocument();
});

describe.each`
size | inputClassName | hintClassName | indicatorClassName
${'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,
inputClassName,
hintClassName,
indicatorClassName,
}: {
size: TextControl.Size,
inputClassName: string,
hintClassName: string,
indicatorClassName: string,
}) => {
it('renders input styles', () => {
render(
<MaskedTextInput
size={size}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders label styles with indicator', () => {
render(
<MaskedTextInput
size={size}
label="foo"
indicator={<div />}
/>,
);
const label = screen.getByTestId('label');
expect(label).toHaveClass(hintClassName);
});

it('renders hint styles when indicator is present', () => {
render(
<MaskedTextInput
size={size}
hint="hint"
indicator={<div />}
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});

it('renders indicator styles', () => {
render(
<MaskedTextInput
size={size}
indicator={
<div />
}
/>,
);

const indicator = screen.getByTestId('indicator');
expect(indicator).toHaveClass(indicatorClassName);
});
});

it('renders a block textbox', () => {
render(
<MaskedTextInput
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('block');
});

describe.each`
variant | inputClassName | hintClassName
${'default'} | ${'pl-4'} | ${'bottom-0 pl-4 pb-1'}
${'alternate'} | ${'pl-1.5 pt-4'} | ${'top-0.5'}
`('on $variant style', ({
variant,
inputClassName,
hintClassName,
}: {
variant: TextControl.Variant,
inputClassName: string,
hintClassName: string,
}) => {
it('renders input styles', () => {
render(
<MaskedTextInput
variant={variant}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders hint styles', () => {
render(
<MaskedTextInput
variant={variant}
hint="hint"
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<MaskedTextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<MaskedTextInput
onChange={onChange}
/>,
);
const textbox = screen.getByTestId('input');
await userEvent.type(textbox, 'foobar');
expect(onChange).toBeCalled();
});

it('handles input events', async () => {
const onInput = vi.fn().mockImplementationOnce(
(e: React.SyntheticEvent<MaskedTextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<MaskedTextInput
onInput={onInput}
/>,
);
const textbox = screen.getByTestId('input');
await userEvent.type(textbox, 'foobar');
expect(onInput).toBeCalled();
});
});

+ 2
- 0
categories/freeform/react/src/components/MaskedTextInput/index.tsx View File

@@ -78,6 +78,7 @@ export const MaskedTextInput = React.forwardRef<
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
@@ -177,6 +178,7 @@ export const MaskedTextInput = React.forwardRef<
)}
{indicator && (
<div
data-testid="indicator"
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',
{


+ 241
- 0
categories/freeform/react/src/components/MultilineTextInput/MultilineTextInput.test.tsx View File

@@ -0,0 +1,241 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TextControl } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
MultilineTextInput,
MultilineTextInputDerivedElement,
} from '.';

expect.extend(matchers);

describe('TextInput', () => {
afterEach(() => {
cleanup();
});

it('renders a textbox', () => {
render(
<MultilineTextInput />,
);
const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument();
});

it('renders a border', () => {
render(
<MultilineTextInput
border
/>,
);
const border = screen.getByTestId('border');
expect(border).toBeInTheDocument();
});

it('renders a label', () => {
render(
<MultilineTextInput
label="foo"
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.getByTestId('label');
expect(label).toHaveTextContent('foo');
});

it('renders a hidden label', () => {
render(
<MultilineTextInput
label="foo"
hiddenLabel
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.queryByTestId('label');
expect(label).toBeInTheDocument();
expect(label).toHaveClass('sr-only');
});

it('renders a hint', () => {
render(
<MultilineTextInput
hint="foo"
/>,
);
const hint = screen.getByTestId('hint');
expect(hint).toBeInTheDocument();
});

it('renders an indicator', () => {
render(
<MultilineTextInput
indicator={
<div />
}
/>,
);
const indicator = screen.getByTestId('indicator');
expect(indicator).toBeInTheDocument();
});

describe.each`
size | inputClassName | hintClassName | indicatorClassName
${'small'} | ${'min-h-10'} | ${'pr-10'} | ${'w-10'}
${'medium'} | ${'min-h-12'} | ${'pr-12'} | ${'w-12'}
${'large'} | ${'min-h-16'} | ${'pr-16'} | ${'w-16'}
`('on $size size', ({
size,
inputClassName,
hintClassName,
indicatorClassName,
}: {
size: TextControl.Size,
inputClassName: string,
hintClassName: string,
indicatorClassName: string,
}) => {
it('renders input styles', () => {
render(
<MultilineTextInput
size={size}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders label styles with indicator', () => {
render(
<MultilineTextInput
size={size}
label="foo"
indicator={<div />}
/>,
);
const label = screen.getByTestId('label');
expect(label).toHaveClass(hintClassName);
});

it('renders hint styles when indicator is present', () => {
render(
<MultilineTextInput
size={size}
hint="hint"
indicator={<div />}
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});

it('renders indicator styles', () => {
render(
<MultilineTextInput
size={size}
indicator={
<div />
}
/>,
);

const indicator = screen.getByTestId('indicator');
expect(indicator).toHaveClass(indicatorClassName);
});
});

it('renders a block textbox', () => {
render(
<MultilineTextInput
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('block');
});

describe.each`
variant | inputClassName | hintClassName
${'default'} | ${'pl-4'} | ${'bottom-0 pl-4 pb-1'}
${'alternate'} | ${'pl-1.5'} | ${'top-0.5'}
`('on $variant style', ({
variant,
inputClassName,
hintClassName,
}: {
variant: TextControl.Variant,
inputClassName: string,
hintClassName: string,
}) => {
it('renders input styles', () => {
render(
<MultilineTextInput
variant={variant}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders hint styles', () => {
render(
<MultilineTextInput
variant={variant}
hint="hint"
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<MultilineTextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<MultilineTextInput
onChange={onChange}
/>,
);
const textbox: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(textbox, 'foobar');
expect(onChange).toBeCalled();
});

it('handles input events', async () => {
const onInput = vi.fn().mockImplementationOnce(
(e: React.SyntheticEvent<MultilineTextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<MultilineTextInput
onInput={onInput}
/>,
);
const textbox: HTMLInputElement = screen.getByTestId('input');
await userEvent.type(textbox, 'foobar');
expect(onInput).toBeCalled();
});
});

+ 2
- 0
categories/freeform/react/src/components/MultilineTextInput/index.tsx View File

@@ -80,6 +80,7 @@ export const MultilineTextInput = React.forwardRef<
className,
)}
style={style}
data-testid="base"
>
<textarea
{...etcProps}
@@ -192,6 +193,7 @@ export const MultilineTextInput = React.forwardRef<
)}
{indicator && (
<div
data-testid="indicator"
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',
{


+ 263
- 0
categories/freeform/react/src/components/TextInput/TextInput.test.tsx View File

@@ -0,0 +1,263 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TextControl } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
TextInput,
TextInputDerivedElement,
} from '.';

expect.extend(matchers);

describe('TextInput', () => {
afterEach(() => {
cleanup();
});

it('renders a textbox', () => {
render(
<TextInput />,
);
const textbox = screen.getByRole('textbox');
expect(textbox).toBeInTheDocument();
expect(textbox).toHaveProperty('type', 'text');
});

it('renders a border', () => {
render(
<TextInput
border
/>,
);
const border = screen.getByTestId('border');
expect(border).toBeInTheDocument();
});

it('renders a label', () => {
render(
<TextInput
label="foo"
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.getByTestId('label');
expect(label).toHaveTextContent('foo');
});

it('renders a hidden label', () => {
render(
<TextInput
label="foo"
hiddenLabel
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.queryByTestId('label');
expect(label).toBeInTheDocument();
expect(label).toHaveClass('sr-only');
});

it('renders a hint', () => {
render(
<TextInput
hint="foo"
/>,
);
const hint = screen.getByTestId('hint');
expect(hint).toBeInTheDocument();
});

it('renders an indicator', () => {
render(
<TextInput
indicator={
<div />
}
/>,
);
const indicator = screen.getByTestId('indicator');
expect(indicator).toBeInTheDocument();
});

describe.each`
size | inputClassName | hintClassName | indicatorClassName
${'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,
inputClassName,
hintClassName,
indicatorClassName,
}: {
size: TextControl.Size,
inputClassName: string,
hintClassName: string,
indicatorClassName: string,
}) => {
it('renders input styles', () => {
render(
<TextInput
size={size}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders label styles with indicator', () => {
render(
<TextInput
size={size}
label="foo"
indicator={<div />}
/>,
);
const label = screen.getByTestId('label');
expect(label).toHaveClass(hintClassName);
});

it('renders hint styles when indicator is present', () => {
render(
<TextInput
size={size}
hint="hint"
indicator={<div />}
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});

it('renders indicator styles', () => {
render(
<TextInput
size={size}
indicator={
<div />
}
/>,
);

const indicator = screen.getByTestId('indicator');
expect(indicator).toHaveClass(indicatorClassName);
});
});

it('renders a block textbox', () => {
render(
<TextInput
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('block');
});

it.each(TextControl.AVAILABLE_INPUT_TYPES)('renders a textbox with type %s', (inputType) => {
render(
<TextInput
type={inputType}
/>,
);
const textbox = screen.getByTestId('input');
expect(textbox).toHaveProperty('type', inputType);
});

it('falls back to text input mode when it clashes with the input type', () => {
render(
<TextInput
type="text"
inputMode="search"
/>,
);
const textbox = screen.getByTestId('input');
expect(textbox).toHaveProperty('inputMode', 'text');
});

describe.each`
variant | inputClassName | hintClassName
${'default'} | ${'pl-4'} | ${'bottom-0 pl-4 pb-1'}
${'alternate'} | ${'pl-1.5 pt-4'} | ${'top-0.5'}
`('on $variant style', ({
variant,
inputClassName,
hintClassName,
}: {
variant: TextControl.Variant,
inputClassName: string,
hintClassName: string,
}) => {
it('renders input styles', () => {
render(
<TextInput
variant={variant}
/>,
);

const input = screen.getByTestId('input');
expect(input).toHaveClass(inputClassName);
});

it('renders hint styles', () => {
render(
<TextInput
variant={variant}
hint="hint"
/>,
);

const hint = screen.getByTestId('hint');
expect(hint).toHaveClass(hintClassName);
});
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<TextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<TextInput
onChange={onChange}
/>,
);
const textbox: HTMLInputElement = screen.getByRole('textbox');
await userEvent.type(textbox, 'foobar');
expect(onChange).toBeCalled();
});

it('handles input events', async () => {
const onInput = vi.fn().mockImplementationOnce(
(e: React.SyntheticEvent<TextInputDerivedElement>) => {
e.preventDefault();
},
);
render(
<TextInput
onInput={onInput}
/>,
);
const textbox: HTMLInputElement = screen.getByTestId('input');
await userEvent.type(textbox, 'foobar');
expect(onInput).toBeCalled();
});
});

+ 2
- 0
categories/freeform/react/src/components/TextInput/index.tsx View File

@@ -94,6 +94,7 @@ export const TextInput = React.forwardRef<TextInputDerivedElement, TextInputProp
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
@@ -194,6 +195,7 @@ export const TextInput = React.forwardRef<TextInputDerivedElement, TextInputProp
)}
{indicator && (
<div
data-testid="indicator"
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',
{


+ 12
- 0
categories/freeform/react/src/web-freeform-react.test.ts View File

@@ -0,0 +1,12 @@
import { describe, it, expect } from 'vitest';
import * as WebFreeformReact from '.';

describe('web-freeform-react', () => {
it.each([
'MaskedTextInput',
'MultilineTextInput',
'TextInput',
])('exports %s', (namedExport) => {
expect(WebFreeformReact).toHaveProperty(namedExport);
});
});

+ 4
- 1
categories/information/react/package.json View File

@@ -15,8 +15,11 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^18.14.1",
"@types/react": "^18.0.27",
"@types/testing-library__jest-dom": "^5.14.7",
"@vitest/coverage-v8": "^0.33.0",
"eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0",
"jsdom": "^21.1.0",
@@ -26,7 +29,7 @@
"react-test-renderer": "^18.2.0",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
"vitest": "^0.33.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",


+ 44
- 0
categories/information/react/src/components/Badge/Badge.test.tsx View File

@@ -0,0 +1,44 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import {
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
Badge,
} from '.';

expect.extend(matchers);

describe('Badge', () => {
afterEach(() => {
cleanup();
});

it('renders a badge', () => {
render(
<Badge />,
);
const badge: HTMLButtonElement = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
expect(badge).toHaveClass('rounded px-1');
});

it('renders a rounded badge', () => {
render(
<Badge
rounded
/>,
);
const badge: HTMLButtonElement = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
expect(badge).toHaveClass('rounded-full px-2');
});
});

+ 1
- 0
categories/information/react/src/components/Badge/index.tsx View File

@@ -28,6 +28,7 @@ export const Badge = React.forwardRef<BadgeDerivedElement, BadgeProps>((
},
className,
)}
data-testid="badge"
>
<span className="relative w-full">
{children}


+ 11
- 0
categories/information/react/src/web-information-react.test.ts View File

@@ -0,0 +1,11 @@
import { describe, it, expect } from 'vitest';
import * as WebInformationReact from '.';

describe('web-information-react', () => {
it.each([
'Badge',
'KeyValueTable',
])('exports %s', (namedExport) => {
expect(WebInformationReact).toHaveProperty(namedExport);
});
});

+ 2
- 1
categories/multichoice/react/.eslintrc View File

@@ -2,7 +2,8 @@
"root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
"react/jsx-props-no-spreading": "off",
"import/no-extraneous-dependencies": "off"
},
"extends": [
"lxsmnsyc/typescript/react"


+ 4
- 1
categories/multichoice/react/package.json View File

@@ -15,8 +15,11 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^18.14.1",
"@types/react": "^18.0.27",
"@types/testing-library__jest-dom": "^5.14.7",
"@vitest/coverage-v8": "^0.33.0",
"eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0",
"jsdom": "^21.1.0",
@@ -27,7 +30,7 @@
"tslib": "^2.5.0",
"tsx": "^3.12.7",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
"vitest": "^0.33.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",


+ 224
- 0
categories/multichoice/react/src/components/ToggleButton/ToggleButton.test.tsx View File

@@ -0,0 +1,224 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
ToggleButton,
ToggleButtonDerivedElement,
} from '.';

expect.extend(matchers);

describe('ToggleButton', () => {
afterEach(() => {
cleanup();
});

it('renders a checkbox', () => {
render(
<ToggleButton />,
);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeInTheDocument();
});

it('renders a subtext', () => {
render(
<ToggleButton
subtext="subtext"
/>,
);
const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

it('renders a badge', () => {
render(
<ToggleButton
badge="badge"
/>,
);
const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});

describe('on indeterminate', () => {
it('renders an indeterminate checkbox', () => {
render(
<ToggleButton
indeterminate
/>,
);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveProperty('indeterminate', true);
});

it('acknowledges passed ref object', () => {
const ref = React.createRef<ToggleButtonDerivedElement>();
render(
<ToggleButton
indeterminate
ref={ref}
/>,
);
expect(ref.current).toHaveProperty('indeterminate', true);
});

it('acknowledges passed legacy ref', () => {
let refElement = null as null | ToggleButtonDerivedElement;
const ref = (element: ToggleButtonDerivedElement) => {
refElement = element;
};

render(
<ToggleButton
indeterminate
ref={ref}
/>,
);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveProperty('indeterminate', true);
expect(refElement).toBe(checkbox);
});
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<ToggleButtonDerivedElement>) => {
e.preventDefault();
},
);
render(
<ToggleButton
onClick={onClick}
/>,
);
const checkbox = screen.getByRole('checkbox');
await userEvent.click(checkbox);
expect(onClick).toBeCalled();
});

it('renders a compact button', () => {
render(
<ToggleButton
compact
/>,
);

const button = screen.getByTestId('button');
expect(button).toHaveClass('pl-2 gap-2 pr-2');
});

describe.each`
size | className
${'small'} | ${'h-10'}
${'medium'} | ${'h-12'}
${'large'} | ${'h-16'}
`('on $size size', ({
size,
className,
}: { size: Button.Size, className: string }) => {
it('renders button styles', () => {
render(
<ToggleButton
size={size}
/>,
);

const button = screen.getByTestId('button');
expect(button).toHaveClass(className);
});

it('renders badge styles', () => {
render(
<ToggleButton
size={size}
badge="badge"
/>,
);

const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});
});

it.each`
variant | className
${'bare'} | ${'bg-negative'}
${'outline'} | ${'border-2'}
${'filled'} | ${'bg-primary'}
`('renders a button with $variant variant', ({
variant,
className,
}: { variant: Button.Variant, className: string }) => {
render(
<ToggleButton
variant={variant}
/>,
);

const button = screen.getByTestId('button');
expect(button).toHaveClass(className);
});

it('renders a block button', () => {
render(
<ToggleButton
block
/>,
);

const button = screen.getByTestId('button');
expect(button).toHaveClass('w-full flex');
});

it('renders children', () => {
render(
<ToggleButton>
Foo
</ToggleButton>,
);

const children = screen.getByTestId('children');
expect(children).toHaveTextContent('Foo');
});

it('renders a disabled button', () => {
render(
<ToggleButton
disabled
/>,
);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeDisabled();
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<ToggleButtonDerivedElement>) => {
e.preventDefault();
},
);
render(
<ToggleButton
onChange={onChange}
/>,
);
const checkbox = screen.getByRole('checkbox');
await userEvent.click(checkbox);
expect(onChange).toBeCalled();
});
});

+ 12
- 11
categories/multichoice/react/src/components/ToggleButton/index.tsx View File

@@ -36,29 +36,30 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
const id = idProp ?? defaultId;

React.useEffect(() => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: element } = ref;
if (!element) {
if (typeof ref === 'function') {
const defaultElement = defaultRef.current as ToggleButtonDerivedElement;
defaultElement.indeterminate = indeterminate;
ref(defaultElement);
return;
}
const element = ref.current as ToggleButtonDerivedElement;
element.indeterminate = indeterminate;
}, [indeterminate, ref]);
}, [indeterminate, defaultRef, ref]);

return (
<>
<input
{...etcProps}
ref={ref}
ref={typeof ref === 'function' ? defaultRef : ref}
type="checkbox"
id={id}
className="sr-only peer tesseract-design-toggle-button"
/>
<label
data-testid="button"
htmlFor={id}
className={clsx(
'items-center justify-start rounded overflow-hidden ring-secondary/50 gap-4 leading-none select-none cursor-pointer',
'items-center justify-start rounded overflow-hidden ring-secondary/50 leading-none select-none cursor-pointer',
'peer-focus:outline-0 peer-focus:ring-4 peer-focus:ring-secondary/50',
'active:ring-tertiary/50 active:ring-4',
'peer-disabled:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:ring-0',
@@ -68,11 +69,12 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
'inline-flex max-w-full align-middle': !block,
},
{
'pl-2 pr-2': compact,
'pl-4 pr-4': !compact,
'pl-2 gap-2 pr-2': compact,
'pl-4 gap-4 pr-4': !compact,
},
{
'border-2 border-primary peer-disabled:border-primary peer-focus:border-secondary peer-checked:border-tertiary active:border-tertiary': variant !== 'bare',
'bg-negative text-primary peer-disabled:text-primary peer-focus:text-secondary peer-checked:text-tertiary active:text-tertiary': variant !== 'filled',
'bg-primary text-negative peer-disabled:bg-primary peer-focus:bg-secondary peer-checked:bg-tertiary active:bg-tertiary': variant === 'filled',
},
{
@@ -89,7 +91,6 @@ export const ToggleButton = React.forwardRef<ToggleButtonDerivedElement, ToggleB
{
'border-current': variant !== 'filled',
'border-negative': variant === 'filled',
'-mr-2': compact,
},
)}
>


+ 158
- 0
categories/multichoice/react/src/components/ToggleSwitch/ToggleSwitch.test.tsx View File

@@ -0,0 +1,158 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
ToggleSwitch,
ToggleSwitchDerivedElement,
} from '.';

expect.extend(matchers);

describe('ToggleSwitch', () => {
afterEach(() => {
cleanup();
});

it('renders a checkbox', () => {
render(
<ToggleSwitch />,
);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeInTheDocument();
});

it('renders a block switch', () => {
render(
<ToggleSwitch
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('flex');
});

it('renders a label when the component is unchecked', () => {
render(
<ToggleSwitch
uncheckedLabel="label"
/>,
);

const uncheckedLabel = screen.getByTestId('uncheckedLabel');
expect(uncheckedLabel).toBeInTheDocument();
});

describe('on subtext', () => {
it('renders without an unchecked label', () => {
render(
<ToggleSwitch
subtext="subtext"
/>,
);

const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
expect(subtext).toHaveClass('pl-16');
});

it('renders with an unchecked label', () => {
render(
<ToggleSwitch
subtext="subtext"
uncheckedLabel="label"
/>,
);

const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
expect(subtext).toHaveClass('pt-2');
});
});

describe('on indeterminate', () => {
it('renders an indeterminate checkbox', () => {
render(
<ToggleSwitch
indeterminate
/>,
);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveProperty('indeterminate', true);
});

it('acknowledges passed ref object', () => {
const ref = React.createRef<ToggleSwitchDerivedElement>();
render(
<ToggleSwitch
indeterminate
ref={ref}
/>,
);
expect(ref.current).toHaveProperty('indeterminate', true);
});

it('acknowledges passed legacy ref', () => {
let refElement = null as null | ToggleSwitchDerivedElement;
const ref = (element: ToggleSwitchDerivedElement) => {
refElement = element;
};

render(
<ToggleSwitch
indeterminate
ref={ref}
/>,
);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveProperty('indeterminate', true);
expect(refElement).toBe(checkbox);
});
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<ToggleSwitchDerivedElement>) => {
e.preventDefault();
},
);
render(
<ToggleSwitch
onClick={onClick}
/>,
);
const checkbox: HTMLInputElement = screen.getByRole('checkbox');
await userEvent.click(checkbox);
expect(onClick).toBeCalled();
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.ChangeEvent<ToggleSwitchDerivedElement>) => {
e.preventDefault();
},
);
render(
<ToggleSwitch
onChange={onChange}
/>,
);
const checkbox: HTMLInputElement = screen.getByRole('checkbox');
await userEvent.click(checkbox);
expect(onChange).toBeCalled();
});
});

+ 8
- 7
categories/multichoice/react/src/components/ToggleSwitch/index.tsx View File

@@ -31,15 +31,15 @@ export const ToggleSwitch = React.forwardRef<ToggleSwitchDerivedElement, ToggleS
const id = idProp ?? defaultId;

React.useEffect(() => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: element } = ref;
if (!element) {
if (typeof ref === 'function') {
const defaultElement = defaultRef.current as ToggleSwitchDerivedElement;
defaultElement.indeterminate = indeterminate;
ref(defaultElement);
return;
}
const element = ref.current as ToggleSwitchDerivedElement;
element.indeterminate = indeterminate;
}, [indeterminate, ref]);
}, [indeterminate, defaultRef, ref]);

return (
<div
@@ -50,10 +50,11 @@ export const ToggleSwitch = React.forwardRef<ToggleSwitchDerivedElement, ToggleS
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
ref={ref}
ref={typeof ref === 'function' ? defaultRef : ref}
type="checkbox"
id={id}
className="sr-only peer/radio tesseract-design-toggle-switch"


+ 130
- 0
categories/multichoice/react/src/components/ToggleTickBox/ToggleTickBox.test.tsx View File

@@ -0,0 +1,130 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
ToggleTickBox,
ToggleTickBoxDerivedElement,
} from '.';

expect.extend(matchers);

describe('ToggleTickBox', () => {
afterEach(() => {
cleanup();
});

it('renders a checkbox', () => {
render(
<ToggleTickBox />,
);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeInTheDocument();
});

describe('on indeterminate', () => {
it('renders an indeterminate checkbox', () => {
render(
<ToggleTickBox
indeterminate
/>,
);
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveProperty('indeterminate', true);
});

it('acknowledges passed ref object', () => {
const ref = React.createRef<ToggleTickBoxDerivedElement>();
render(
<ToggleTickBox
indeterminate
ref={ref}
/>,
);
expect(ref.current).toHaveProperty('indeterminate', true);
});

it('acknowledges passed legacy ref', () => {
let refElement = null as null | ToggleTickBoxDerivedElement;
const ref = (element: ToggleTickBoxDerivedElement) => {
refElement = element;
};

render(
<ToggleTickBox
indeterminate
ref={ref}
/>,
);

const checkbox = screen.getByRole('checkbox');
expect(checkbox).toHaveProperty('indeterminate', true);
expect(refElement).toBe(checkbox);
});
});

it('renders a block tick box', () => {
render(
<ToggleTickBox
block
/>,
);

const base = screen.getByTestId('base');
expect(base).toHaveClass('flex');
});

it('renders a subtext', () => {
render(
<ToggleTickBox
subtext="subtext"
/>,
);
const subtext: HTMLElement = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<ToggleTickBoxDerivedElement>) => {
e.preventDefault();
},
);
render(
<ToggleTickBox
onClick={onClick}
/>,
);
const checkbox: HTMLInputElement = screen.getByRole('checkbox');
await userEvent.click(checkbox);
expect(onClick).toBeCalled();
});

it('handles change events', async () => {
const onChange = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<ToggleTickBoxDerivedElement>) => {
e.preventDefault();
},
);

render(
<ToggleTickBox
onChange={onChange}
/>,
);
const checkbox: HTMLInputElement = screen.getByRole('checkbox');
await userEvent.click(checkbox);
expect(onChange).toBeCalled();
});
});

+ 8
- 7
categories/multichoice/react/src/components/ToggleTickBox/index.tsx View File

@@ -28,15 +28,15 @@ export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, Toggl
const id = idProp ?? defaultId;

React.useEffect(() => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: element } = ref;
if (!element) {
if (typeof ref === 'function') {
const defaultElement = defaultRef.current as ToggleTickBoxDerivedElement;
defaultElement.indeterminate = indeterminate;
ref(defaultElement);
return;
}
const element = ref.current as ToggleTickBoxDerivedElement;
element.indeterminate = indeterminate;
}, [indeterminate, ref]);
}, [indeterminate, defaultRef, ref]);

return (
<div
@@ -47,10 +47,11 @@ export const ToggleTickBox = React.forwardRef<ToggleTickBoxDerivedElement, Toggl
className,
)}
style={style}
data-testid="base"
>
<input
{...etcProps}
ref={ref}
ref={typeof ref === 'function' ? defaultRef : ref}
type="checkbox"
id={id}
className="sr-only peer/radio tesseract-design-toggle-tick-box"


+ 14
- 0
categories/multichoice/react/src/web-multichoice-react.test.ts View File

@@ -0,0 +1,14 @@
import { describe, it, expect } from 'vitest';
import * as WebMultiChoiceReact from '.';

describe('web-multichoice-react', () => {
it.each([
'MenuMultiSelect',
'TagInput',
'ToggleButton',
'ToggleSwitch',
'ToggleTickBox',
])('exports %s', (namedExport) => {
expect(WebMultiChoiceReact).toHaveProperty(namedExport);
});
});

+ 4
- 1
categories/navigation/react/package.json View File

@@ -15,8 +15,11 @@
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^18.14.1",
"@types/react": "^18.0.27",
"@types/testing-library__jest-dom": "^5.14.7",
"@vitest/coverage-v8": "^0.33.0",
"eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0",
"jsdom": "^21.1.0",
@@ -26,7 +29,7 @@
"react-test-renderer": "^18.2.0",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
"vitest": "^0.33.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",


+ 203
- 0
categories/navigation/react/src/components/LinkButton/LinkButton.test.tsx View File

@@ -0,0 +1,203 @@
import * as React from 'react';
import {
cleanup,
render,
screen,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from '@tesseract-design/web-base';
import {
vi,
expect,
describe,
it,
afterEach,
} from 'vitest';
import matchers from '@testing-library/jest-dom/matchers';
import {
LinkButton,
LinkButtonDerivedElement,
} from '.';

expect.extend(matchers);

describe('LinkButton', () => {
afterEach(() => {
cleanup();
});

it('renders a button', () => {
render(
<LinkButton
href="https://www.example.com"
/>,
);
const button = screen.getByRole('link');
expect(button).toBeInTheDocument();
});

it('renders a subtext', () => {
render(
<LinkButton
href="#"
subtext="subtext"
/>,
);
const subtext = screen.getByTestId('subtext');
expect(subtext).toBeInTheDocument();
});

it('renders a badge', () => {
render(
<LinkButton
href="#"
badge="badge"
/>,
);
const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});

it('renders as a menu item', () => {
render(
<LinkButton
href="#"
menuItem
/>,
);
const menuItemIndicator = screen.getByTestId('menuItemIndicator');
expect(menuItemIndicator).toBeInTheDocument();
});

it('handles click events', async () => {
const onClick = vi.fn().mockImplementationOnce(
(e: React.MouseEvent<LinkButtonDerivedElement>) => {
e.preventDefault();
},
);

render(
<LinkButton
href="#"
onClick={onClick}
/>,
);
const button = screen.getByRole('link');
await userEvent.click(button);
expect(onClick).toBeCalled();
});

it('renders a compact link', () => {
render(
<LinkButton
href="#"
compact
/>,
);

const button = screen.getByRole('link');
expect(button).toHaveClass('pl-2 gap-2 pr-2');
});

describe.each`
size | className
${'small'} | ${'h-10'}
${'medium'} | ${'h-12'}
${'large'} | ${'h-16'}
`('on $size size', ({
size,
className,
}: { size: Button.Size, className: string }) => {
it('renders link styles', () => {
render(
<LinkButton
href="#"
size={size}
/>,
);

const button = screen.getByRole('link');
expect(button).toHaveClass(className);
});

it('renders badge styles', () => {
render(
<LinkButton
href="#"
size={size}
badge="badge"
/>,
);

const badge = screen.getByTestId('badge');
expect(badge).toBeInTheDocument();
});
});

it.each`
variant | className
${'bare'} | ${'bg-negative'}
${'outline'} | ${'border-2'}
${'filled'} | ${'bg-primary'}
`('renders a link with $variant variant', ({
variant,
className,
}: { variant: Button.Variant, className: string }) => {
render(
<LinkButton
href="#"
variant={variant}
/>,
);

const button = screen.getByRole('link');
expect(button).toHaveClass(className);
});

it('renders a block link', () => {
render(
<LinkButton
href="#"
block
/>,
);

const button = screen.getByRole('link');
expect(button).toHaveClass('w-full flex');
});

it('renders children', () => {
render(
<LinkButton
href="#"
>
Foo
</LinkButton>,
);

const children = screen.getByTestId('children');
expect(children).toHaveTextContent('Foo');
});

it.each(Button.AVAILABLE_TYPES)('renders a link with type %s', (buttonType) => {
render(
<LinkButton
href="#"
type={buttonType}
/>,
);
const button = screen.getByRole('link');
expect(button).toHaveProperty('type', buttonType);
});

it('renders a disabled link', () => {
render(
<LinkButton
href="#"
disabled
/>,
);
const button = screen.queryByRole('link');
expect(button).toBeNull();
});
});

+ 76
- 68
categories/navigation/react/src/components/LinkButton/index.tsx View File

@@ -13,6 +13,7 @@ export interface LinkButtonProps<T = any> extends Omit<React.HTMLProps<LinkButto
size?: Button.Size;
compact?: boolean;
component?: React.ElementType<T>;
disabled?: boolean;
}

export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonProps>((
@@ -26,97 +27,103 @@ export const LinkButton = React.forwardRef<LinkButtonDerivedElement, LinkButtonP
compact = false,
className,
block = false,
component: Component = 'a',
component: EnabledComponent = 'a',
disabled = false,
...etcProps
},
forwardedRef,
) => (
<Component
{...etcProps}
ref={forwardedRef}
className={clsx(
'items-center justify-center rounded overflow-hidden ring-secondary/50 leading-none select-none no-underline',
'focus:outline-0 focus:ring-4',
'active:ring-tertiary/50',
{
'flex w-full': block,
'inline-flex max-w-full align-middle': !block,
},
{
'pl-2 gap-2': compact,
'pl-4 gap-4': !compact,
'pr-4': !(compact || menuItem),
'pr-2': compact || menuItem,
},
{
'border-2 border-primary focus:border-secondary active:border-tertiary': variant !== 'bare',
'bg-negative text-primary focus:text-secondary active:text-tertiary': variant !== 'filled',
'bg-primary text-negative focus:bg-secondary active:bg-tertiary focus:text-negative active:text-negative': variant === 'filled',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
className,
)}
>
<span
) => {
const Component = disabled ? 'span' : EnabledComponent;
return (
<Component
{...etcProps}
ref={forwardedRef}
className={clsx(
'flex-auto min-w-0',
'items-center justify-center rounded overflow-hidden ring-secondary/50 leading-none select-none no-underline m-0',
'focus:outline-0 focus:ring-4',
'active:ring-tertiary/50',
disabled && 'opacity-50 cursor-not-allowed',
{
'text-left': compact || menuItem,
'text-center': !(compact || menuItem),
'flex w-full': block,
'inline-flex max-w-full align-middle': !block,
},
{
'pl-2 gap-2': compact,
'pl-4 gap-4': !compact,
'pr-4': !(compact || menuItem),
'pr-2': compact || menuItem,
},
{
'border-2 border-primary focus:border-secondary active:border-tertiary': variant !== 'bare',
'bg-negative text-primary focus:text-secondary active:text-tertiary': variant !== 'filled',
'bg-primary text-negative focus:bg-secondary active:bg-tertiary focus:text-negative active:text-negative': variant === 'filled',
},
{
'h-10': size === 'small',
'h-12': size === 'medium',
'h-16': size === 'large',
},
className,
)}
data-testid="link"
>
<span
className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
data-testid="children"
className={clsx(
'flex-auto min-w-0',
{
'text-left': compact || menuItem,
'text-center': !(compact || menuItem),
},
)}
>
{children}
<span
className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
data-testid="children"
>
{children}
</span>
{subtext && (
<>
<span className="sr-only">
{' - '}
</span>
<span
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs"
data-testid="subtext"
>
{subtext}
</span>
</>
)}
</span>
{subtext && (
{badge && (
<>
<span className="sr-only">
{' - '}
</span>
<span
className="block h-[1.3em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded font-bold text-xs"
data-testid="subtext"
data-testid="badge"
>
{subtext}
{badge}
</span>
</>
)}
</span>
{badge && (
<>
<span className="sr-only">
{' - '}
</span>
{menuItem && (
<span
data-testid="badge"
data-testid="menuItemIndicator"
>
{badge}
<svg
className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
viewBox="0 0 24 24"
role="presentation"
>
<polyline points="9 18 15 12 9 6" />
</svg>
</span>
</>
)}
{menuItem && (
<span
data-testid="menuItemIndicator"
>
<svg
className="w-6 h-6 fill-none stroke-current stroke-2 linejoin-round linecap-round"
viewBox="0 0 24 24"
role="presentation"
>
<polyline points="9 18 15 12 9 6" />
</svg>
</span>
)}
</Component>
));
)}
</Component>
);
});

LinkButton.displayName = 'LinkButton';

@@ -129,4 +136,5 @@ LinkButton.defaultProps = {
badge: undefined,
subtext: undefined,
block: false,
disabled: false,
};

+ 10
- 0
categories/navigation/react/src/web-navigation-react.test.ts View File

@@ -0,0 +1,10 @@
import { describe, it, expect } from 'vitest';
import * as WebNavigationReact from '.';

describe('web-navigation-react', () => {
it.each([
'LinkButton',
])('exports %s', (namedExport) => {
expect(WebNavigationReact).toHaveProperty(namedExport);
});
});

+ 2
- 1
categories/number/react/.eslintrc View File

@@ -2,7 +2,8 @@
"root": true,
"rules": {
"quote-props": "off",
"react/jsx-props-no-spreading": "off"
"react/jsx-props-no-spreading": "off",
"import/no-extraneous-dependencies": "off"
},
"extends": [
"lxsmnsyc/typescript/react"


+ 44
- 8
pnpm-lock.yaml View File

@@ -329,12 +329,21 @@ importers:
'@testing-library/react':
specifier: ^13.4.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':
specifier: ^18.14.1
version: 18.14.1
'@types/react':
specifier: ^18.0.27
version: 18.2.14
'@types/testing-library__jest-dom':
specifier: ^5.14.7
version: 5.14.7
'@vitest/coverage-v8':
specifier: ^0.33.0
version: 0.33.0(vitest@0.33.0)
eslint:
specifier: ^8.35.0
version: 8.43.0
@@ -363,8 +372,8 @@ importers:
specifier: ^4.9.5
version: 4.9.5
vitest:
specifier: ^0.28.1
version: 0.28.1(jsdom@21.1.0)
specifier: ^0.33.0
version: 0.33.0(jsdom@21.1.0)

categories/information/react:
dependencies:
@@ -378,12 +387,21 @@ importers:
'@testing-library/react':
specifier: ^13.4.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':
specifier: ^18.14.1
version: 18.14.1
'@types/react':
specifier: ^18.0.27
version: 18.2.14
'@types/testing-library__jest-dom':
specifier: ^5.14.7
version: 5.14.7
'@vitest/coverage-v8':
specifier: ^0.33.0
version: 0.33.0(vitest@0.33.0)
eslint:
specifier: ^8.35.0
version: 8.43.0
@@ -412,8 +430,8 @@ importers:
specifier: ^4.9.5
version: 4.9.5
vitest:
specifier: ^0.28.1
version: 0.28.1(jsdom@21.1.0)
specifier: ^0.33.0
version: 0.33.0(jsdom@21.1.0)

categories/multichoice/react:
dependencies:
@@ -436,12 +454,21 @@ importers:
'@testing-library/react':
specifier: ^13.4.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':
specifier: ^18.14.1
version: 18.14.1
'@types/react':
specifier: ^18.0.27
version: 18.2.14
'@types/testing-library__jest-dom':
specifier: ^5.14.7
version: 5.14.7
'@vitest/coverage-v8':
specifier: ^0.33.0
version: 0.33.0(vitest@0.33.0)
eslint:
specifier: ^8.35.0
version: 8.43.0
@@ -473,8 +500,8 @@ importers:
specifier: ^4.9.5
version: 4.9.5
vitest:
specifier: ^0.28.1
version: 0.28.1(jsdom@21.1.0)
specifier: ^0.33.0
version: 0.33.0(jsdom@21.1.0)

categories/navigation/react:
dependencies:
@@ -491,12 +518,21 @@ importers:
'@testing-library/react':
specifier: ^13.4.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':
specifier: ^18.14.1
version: 18.14.1
'@types/react':
specifier: ^18.0.27
version: 18.2.14
'@types/testing-library__jest-dom':
specifier: ^5.14.7
version: 5.14.7
'@vitest/coverage-v8':
specifier: ^0.33.0
version: 0.33.0(vitest@0.33.0)
eslint:
specifier: ^8.35.0
version: 8.43.0
@@ -525,8 +561,8 @@ importers:
specifier: ^4.9.5
version: 4.9.5
vitest:
specifier: ^0.28.1
version: 0.28.1(jsdom@21.1.0)
specifier: ^0.33.0
version: 0.33.0(jsdom@21.1.0)

categories/number/react:
dependencies:


+ 2
- 0
showcases/web-kitchensink-reactnext/src/pages/categories/option/index.tsx View File

@@ -905,6 +905,8 @@ const OptionPage: NextPage<Props> = ({
<div>
<MultiChoice.ToggleButton
variant="bare"
indeterminate
ref={(el) => { console.log(el); }}
block
>
Button


Loading…
Cancel
Save