Browse Source

Add blob tests

Include some basic tests for FileSelectBox.
master
TheoryOfNekomata 1 year ago
parent
commit
9e0be18026
3 changed files with 163 additions and 25 deletions
  1. +85
    -0
      categories/web/blob/react/src/components/FileSelectBox/FileSelectBox.test.tsx
  2. +68
    -25
      categories/web/blob/react/src/components/FileSelectBox/index.tsx
  3. +10
    -0
      categories/web/blob/react/src/web-blob-react.test.ts

+ 85
- 0
categories/web/blob/react/src/components/FileSelectBox/FileSelectBox.test.tsx View File

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

expect.extend(matchers);

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

it('renders a file input', () => {
render(
<FileSelectBox />,
);

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

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

it('renders a label', () => {
render(
<FileSelectBox
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(
<FileSelectBox
label="foo"
hiddenLabel
/>,
);
const textbox = screen.getByLabelText('foo');
expect(textbox).toBeInTheDocument();
const label = screen.queryByTestId('label');
expect(label).toBeInTheDocument();
expect(label).toHaveClass('sr-only');
});

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

+ 68
- 25
categories/web/blob/react/src/components/FileSelectBox/index.tsx View File

@@ -2,6 +2,10 @@ import * as React from 'react';
import { useClientSide, useFallbackId, useProxyInput } from '@modal-sh/react-utils'; import { useClientSide, useFallbackId, useProxyInput } from '@modal-sh/react-utils';
import clsx from 'clsx'; import clsx from 'clsx';


const DEFAULT_ENHANCED_HEIGHT_PX = 64 as const;

const DEFAULT_NON_ENHANCED_SIDE_HEIGHT_PX = 256 as const;

/** /**
* Common props for the {@link FileSelectBoxProps.previewComponent|previewComponent prop} of the * Common props for the {@link FileSelectBoxProps.previewComponent|previewComponent prop} of the
* {@link FileSelectBox} component. * {@link FileSelectBox} component.
@@ -65,6 +69,14 @@ export interface FileSelectBoxProps<
* Preview component for the selected file(s). * Preview component for the selected file(s).
*/ */
previewComponent?: React.ElementType<P>, previewComponent?: React.ElementType<P>,
/**
* Reselect label.
*/
reselectLabel?: string,
/**
* Clear label.
*/
clearLabel?: string,
} }


/** /**
@@ -127,6 +139,16 @@ export const FileSelectBoxDefaultPreviewComponent = React.forwardRef<
</div> </div>
)); ));


const isButtonElement = (el: HTMLElement): el is HTMLButtonElement => el.tagName === 'BUTTON';

const isInputElement = (el: HTMLElement): el is HTMLInputElement => el.tagName === 'INPUT';

const isKeyUpEvent = (e: React.SyntheticEvent): e is React.KeyboardEvent => e.type === 'keyup';

const isMouseUpEvent = (e: React.SyntheticEvent): e is React.MouseEvent => e.type === 'mouseup';

const isDropEvent = (e: React.SyntheticEvent): e is React.DragEvent => e.type === 'drop';

/** /**
* Component for selecting files. * Component for selecting files.
*/ */
@@ -147,6 +169,8 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
previewComponent: FilePreviewComponent = FileSelectBoxDefaultPreviewComponent, previewComponent: FilePreviewComponent = FileSelectBoxDefaultPreviewComponent,
style, style,
onBlur, onBlur,
reselectLabel = 'Reselect',
clearLabel = 'Clear',
...etcProps ...etcProps
}, },
forwardedRef, forwardedRef,
@@ -157,6 +181,21 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
const clearFileListRef = React.useRef(false); const clearFileListRef = React.useRef(false);
const [deleteKeyPressed, setDeleteKeyPressed] = React.useState(false); const [deleteKeyPressed, setDeleteKeyPressed] = React.useState(false);
const [aboutToSelect, setAboutToSelect] = React.useState(false); const [aboutToSelect, setAboutToSelect] = React.useState(false);

const clearFiles = (
fileInput: FileSelectBoxDerivedElement,
clearRef: React.MutableRefObject<boolean>,
) => {
const clearRefMut = clearRef;
clearRefMut.current = true;

const fileInputMut = fileInput;
fileInputMut.value = '';

setFileList(undefined);
setLastUpdated(Date.now());
};

const { defaultRef, handleChange: doSetFileList } = useProxyInput< const { defaultRef, handleChange: doSetFileList } = useProxyInput<
React.ChangeEvent<FileSelectBoxDerivedElement> React.ChangeEvent<FileSelectBoxDerivedElement>
| React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLButtonElement>
@@ -166,34 +205,36 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
>({ >({
forwardedRef, forwardedRef,
valueSetterFn: (e) => { valueSetterFn: (e) => {
if (e.type === 'mouseup') {
// delete
const fileInput = defaultRef.current as FileSelectBoxDerivedElement;
clearFileListRef.current = true;
fileInput.value = '';
setFileList(undefined);
setLastUpdated(Date.now());
} else if (e.type === 'drop') {
const fileInput = defaultRef.current;
if (!fileInput) {
return;
}
if (isButtonElement(e.currentTarget) && isMouseUpEvent(e)) {
clearFiles(fileInput, clearFileListRef);
return;
}
if (isInputElement(e.currentTarget) && isKeyUpEvent(e)) {
// delete via keyboard
const { code } = e;

if (!(code === 'Backspace' || code === 'Delete')) {
return;
}

clearFiles(e.currentTarget, clearFileListRef);
return;
}
if (isDropEvent(e)) {
// drop file // drop file
const fileInput = defaultRef.current as FileSelectBoxDerivedElement;
const { dataTransfer } = e as React.DragEvent<HTMLDivElement>;
const { dataTransfer } = e;
const { files } = dataTransfer; const { files } = dataTransfer;
if (files && files.length > 0) { if (files && files.length > 0) {
setFileList(fileInput.files = files); setFileList(fileInput.files = files);
setLastUpdated(Date.now()); setLastUpdated(Date.now());
} }
} else if (e.type === 'keyup') {
const {
currentTarget: fileInput,
code,
} = e as React.KeyboardEvent<FileSelectBoxDerivedElement>;
if (code === 'Backspace' || code === 'Delete') {
clearFileListRef.current = true;
fileInput.value = '';
setFileList(undefined);
setLastUpdated(Date.now());
}
return;
} }
console.warn('Unhandled event', e);
}, },
}); });
const labelId = React.useId(); const labelId = React.useId();
@@ -316,7 +357,7 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
onDrop={clientSide ? handleDrop : undefined} onDrop={clientSide ? handleDrop : undefined}
data-testid="root" data-testid="root"
style={{ style={{
height: clientSide ? 64 : undefined,
height: clientSide ? DEFAULT_ENHANCED_HEIGHT_PX : undefined,
...(style ?? {}), ...(style ?? {}),
}} }}
> >
@@ -374,7 +415,7 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
data-testid="input" data-testid="input"
aria-labelledby={label ? `${labelId}` : undefined} aria-labelledby={label ? `${labelId}` : undefined}
style={{ style={{
height: clientSide ? undefined : 256,
height: clientSide ? undefined : DEFAULT_NON_ENHANCED_SIDE_HEIGHT_PX,
}} }}
/> />
{filesCount < 1 {filesCount < 1
@@ -459,7 +500,7 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
<span <span
className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded" className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
> >
Reselect
{reselectLabel}
</span> </span>
</button> </button>
</div> </div>
@@ -484,7 +525,7 @@ export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileS
<span <span
className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded" className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
> >
Clear
{clearLabel}
</span> </span>
</button> </button>
</div> </div>
@@ -511,6 +552,8 @@ FileSelectBox.defaultProps = {
label: undefined, label: undefined,
hint: undefined, hint: undefined,
previewComponent: FileSelectBoxDefaultPreviewComponent, previewComponent: FileSelectBoxDefaultPreviewComponent,
reselectLabel: 'Reselect' as const,
clearLabel: 'Clear' as const,
}; };


FileSelectBoxDefaultPreviewComponent.defaultProps = { FileSelectBoxDefaultPreviewComponent.defaultProps = {


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

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

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

Loading…
Cancel
Save