parent
04ae0327c2
commit
ef404735cd
|
@ -162,11 +162,8 @@ th {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inset button {
|
.inset button, .modal-overlay button {
|
||||||
min-width: 36px;
|
min-width: 36px;
|
||||||
}
|
|
||||||
|
|
||||||
.inset button {
|
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,13 @@ module.exports = {
|
||||||
'testMatch': [
|
'testMatch': [
|
||||||
'**/?(*.)+(spec|test).+(ts|js|tsx)'
|
'**/?(*.)+(spec|test).+(ts|js|tsx)'
|
||||||
],
|
],
|
||||||
|
|
||||||
'transform': {
|
'transform': {
|
||||||
'^.+\\.js$': 'babel-jest',
|
'^.+\\.js$': 'babel-jest',
|
||||||
'^.+\\.ts$': 'ts-jest',
|
'^.+\\.ts$': 'ts-jest',
|
||||||
'^.*\\.tsx$': 'ts-jest',
|
'^.*\\.tsx$': 'ts-jest',
|
||||||
},
|
},
|
||||||
'setupFilesAfterEnv': [
|
'setupFilesAfterEnv': [
|
||||||
'<rootDir>/test/jest-setup.js'
|
'<rootDir>/test/jest-setup.ts'
|
||||||
],
|
],
|
||||||
'coveragePathIgnorePatterns': [
|
'coveragePathIgnorePatterns': [
|
||||||
'/node_modules/',
|
'/node_modules/',
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Screen } from './Screen';
|
||||||
import { Drives } from './Drives';
|
import { Drives } from './Drives';
|
||||||
import { Slinky } from './Slinky';
|
import { Slinky } from './Slinky';
|
||||||
import { ThunderClock } from './ThunderClock';
|
import { ThunderClock } from './ThunderClock';
|
||||||
|
import { ErrorModal } from './ErrorModal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for the Apple2 component.
|
* Interface for the Apple2 component.
|
||||||
|
@ -40,6 +41,7 @@ export const Apple2 = (props: Apple2Props) => {
|
||||||
const [apple2, setApple2] = useState<Apple2Impl>();
|
const [apple2, setApple2] = useState<Apple2Impl>();
|
||||||
const [io, setIO] = useState<Apple2IO>();
|
const [io, setIO] = useState<Apple2IO>();
|
||||||
const [cpu, setCPU] = useState<CPU6502>();
|
const [cpu, setCPU] = useState<CPU6502>();
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (screen.current) {
|
if (screen.current) {
|
||||||
|
@ -57,7 +59,13 @@ export const Apple2 = (props: Apple2Props) => {
|
||||||
setCPU(cpu);
|
setCPU(cpu);
|
||||||
apple2.reset();
|
apple2.reset();
|
||||||
apple2.run();
|
apple2.run();
|
||||||
}).catch(error => console.error(error));
|
}).catch((e) => {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
setError(e.message);
|
||||||
|
} else {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [props]);
|
}, [props]);
|
||||||
|
|
||||||
|
@ -74,6 +82,7 @@ export const Apple2 = (props: Apple2Props) => {
|
||||||
<Inset>
|
<Inset>
|
||||||
<Keyboard apple2={apple2} e={e} />
|
<Keyboard apple2={apple2} e={e} />
|
||||||
</Inset>
|
</Inset>
|
||||||
|
<ErrorModal error={error} setError={setError} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,8 @@ import cs from 'classnames';
|
||||||
import Disk2 from '../cards/disk2';
|
import Disk2 from '../cards/disk2';
|
||||||
import { FileModal } from './FileModal';
|
import { FileModal } from './FileModal';
|
||||||
import { loadJSON, loadHttpFile, getHashParts } from './util/files';
|
import { loadJSON, loadHttpFile, getHashParts } from './util/files';
|
||||||
|
import { ErrorModal } from './ErrorModal';
|
||||||
|
import { useHash } from './hooks/useHash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Storage structure for Disk II state returned via callbacks.
|
* Storage structure for Disk II state returned via callbacks.
|
||||||
|
@ -38,25 +40,37 @@ export interface DiskIIProps extends DiskIIData {
|
||||||
export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
||||||
const label = side ? `${name} - ${side}` : name;
|
const label = side ? `${name} - ${side}` : name;
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
const [currentHash, setCurrentHash] = useState<string>();
|
||||||
|
|
||||||
|
const hash = useHash();
|
||||||
|
|
||||||
|
const handleError = (e: unknown) => {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
setError(e.message);
|
||||||
|
} else {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hashParts = getHashParts();
|
const hashParts = getHashParts(hash);
|
||||||
if (disk2 && hashParts && hashParts[number]) {
|
const newHash = hashParts[number];
|
||||||
const hashPart = decodeURIComponent(hashParts[number]);
|
if (disk2 && newHash) {
|
||||||
if (hashPart.match(/^https?:/)) {
|
const hashPart = decodeURIComponent(newHash);
|
||||||
loadHttpFile(disk2, number, hashPart)
|
if (hashPart !== currentHash) {
|
||||||
.catch((error) =>
|
if (hashPart.match(/^https?:/)) {
|
||||||
console.error(error)
|
loadHttpFile(disk2, number, hashPart)
|
||||||
);
|
.catch((e) => handleError(e));
|
||||||
} else {
|
} else {
|
||||||
const filename = `/json/disks/${hashPart}.json`;
|
const filename = `/json/disks/${hashPart}.json`;
|
||||||
loadJSON(disk2, number, filename)
|
loadJSON(disk2, number, filename)
|
||||||
.catch((error) =>
|
.catch((e) => handleError(e));
|
||||||
console.error(error)
|
}
|
||||||
);
|
setCurrentHash(hashPart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [disk2, number]);
|
}, [currentHash, disk2, hash, number]);
|
||||||
|
|
||||||
const doClose = useCallback(() => {
|
const doClose = useCallback(() => {
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
@ -69,6 +83,7 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="disk">
|
<div className="disk">
|
||||||
<FileModal disk2={disk2} number={number} onClose={doClose} isOpen={modalOpen} />
|
<FileModal disk2={disk2} number={number} onClose={doClose} isOpen={modalOpen} />
|
||||||
|
<ErrorModal error={error} setError={setError} />
|
||||||
<div
|
<div
|
||||||
id={`disk${number}`}
|
id={`disk${number}`}
|
||||||
className={cs('disk-light', { on })}
|
className={cs('disk-light', { on })}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { h, Fragment } from 'preact';
|
||||||
|
import { useCallback } from 'preact/hooks';
|
||||||
|
import { Modal, ModalContent, ModalFooter } from './Modal';
|
||||||
|
|
||||||
|
export interface ErrorProps {
|
||||||
|
error: string | undefined;
|
||||||
|
setError: (error: string | undefined) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorModal = ({ error, setError } : ErrorProps) => {
|
||||||
|
const onClose = useCallback(() => setError(undefined), [setError]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ error && (
|
||||||
|
<Modal
|
||||||
|
title="Error"
|
||||||
|
icon="triangle-exclamation"
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
<div style={{ width: 320, fontSize: '1.1em', padding: '5px 11px'}}>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<button onClick={onClose}>OK</button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -27,7 +27,7 @@ export interface FileChooserProps {
|
||||||
control?: typeof controlDefault;
|
control?: typeof controlDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasPicker: boolean = !!window.showOpenFilePicker;
|
const hasPicker = !!window.showOpenFilePicker;
|
||||||
const controlDefault = hasPicker ? 'picker' : 'input';
|
const controlDefault = hasPicker ? 'picker' : 'input';
|
||||||
|
|
||||||
interface InputFileChooserProps {
|
interface InputFileChooserProps {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { h, JSX } from 'preact';
|
import { h, Fragment, JSX } from 'preact';
|
||||||
import { useCallback, useState } from 'preact/hooks';
|
import { useCallback, useState } from 'preact/hooks';
|
||||||
import { DiskDescriptor, DriveNumber, NibbleFormat, NIBBLE_FORMATS } from '../formats/types';
|
import { DiskDescriptor, DriveNumber, NibbleFormat, NIBBLE_FORMATS } from '../formats/types';
|
||||||
import { Modal, ModalContent, ModalFooter } from './Modal';
|
import { Modal, ModalContent, ModalFooter } from './Modal';
|
||||||
import { loadLocalFile, loadJSON, getHashParts, setHashParts } from './util/files';
|
import { loadLocalFile, loadJSON, getHashParts, setHashParts } from './util/files';
|
||||||
import DiskII from '../cards/disk2';
|
import DiskII from '../cards/disk2';
|
||||||
|
import { ErrorModal } from './ErrorModal';
|
||||||
|
|
||||||
import index from 'json/disks/index.json';
|
import index from 'json/disks/index.json';
|
||||||
import { FileChooser, FilePickerAcceptType, FileSystemFileHandleLike } from './FileChooser';
|
import { FileChooser, FilePickerAcceptType, FileSystemFileHandleLike } from './FileChooser';
|
||||||
import { noAwait } from './util/promises';
|
import { noAwait } from './util/promises';
|
||||||
|
import { useHash } from './hooks/useHash';
|
||||||
|
|
||||||
const DISK_TYPES: FilePickerAcceptType[] = [
|
const DISK_TYPES: FilePickerAcceptType[] = [
|
||||||
{
|
{
|
||||||
|
@ -50,39 +52,39 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||||
const [category, setCategory] = useState<string>();
|
const [category, setCategory] = useState<string>();
|
||||||
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>();
|
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>();
|
||||||
const [filename, setFilename] = useState<string>();
|
const [filename, setFilename] = useState<string>();
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
const hash = useHash();
|
||||||
|
|
||||||
const doCancel = useCallback(() => onClose(true), [onClose]);
|
const doCancel = useCallback(() => onClose(true), [onClose]);
|
||||||
|
|
||||||
const doOpen = useCallback(async () => {
|
const doOpen = useCallback(async () => {
|
||||||
const hashParts = getHashParts();
|
const hashParts = getHashParts(hash);
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
if (disk2 && handles && handles.length === 1) {
|
try {
|
||||||
hashParts[number] = '';
|
if (disk2 && handles?.length === 1) {
|
||||||
setBusy(true);
|
hashParts[number] = '';
|
||||||
try {
|
|
||||||
await loadLocalFile(disk2, number, await handles[0].getFile());
|
await loadLocalFile(disk2, number, await handles[0].getFile());
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
onClose();
|
|
||||||
}
|
}
|
||||||
}
|
if (disk2 && filename) {
|
||||||
|
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
|
||||||
if (disk2 && filename) {
|
hashParts[number] = name[1];
|
||||||
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
|
await loadJSON(disk2, number, filename);
|
||||||
hashParts[number] = name[1];
|
}
|
||||||
setBusy(true);
|
} catch (e) {
|
||||||
loadJSON(disk2, number, filename)
|
if (e instanceof Error) {
|
||||||
.catch(console.error)
|
setError(e.message);
|
||||||
.finally(() => {
|
} else {
|
||||||
setBusy(false);
|
console.error(e);
|
||||||
onClose();
|
}
|
||||||
});
|
} finally {
|
||||||
|
setHashParts(hashParts);
|
||||||
|
setBusy(false);
|
||||||
|
onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
setHashParts(hashParts);
|
setHashParts(hashParts);
|
||||||
}, [disk2, filename, number, onClose, handles]);
|
}, [disk2, filename, number, onClose, handles, hash]);
|
||||||
|
|
||||||
const onChange = useCallback((handles: FileSystemFileHandleLike[]) => {
|
const onChange = useCallback((handles: FileSystemFileHandleLike[]) => {
|
||||||
setEmpty(handles.length === 0);
|
setEmpty(handles.length === 0);
|
||||||
|
@ -105,29 +107,32 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||||
const disks = category ? categories[category] : [];
|
const disks = category ? categories[category] : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title="Open File" isOpen={isOpen}>
|
<>
|
||||||
<ModalContent>
|
<Modal title="Open File" isOpen={isOpen}>
|
||||||
<div id="load-modal">
|
<ModalContent>
|
||||||
<select multiple onChange={doSelectCategory}>
|
<div id="load-modal">
|
||||||
{categoryNames.map((name) => (
|
<select multiple onChange={doSelectCategory}>
|
||||||
<option key={name}>{name}</option>
|
{categoryNames.map((name) => (
|
||||||
))}
|
<option key={name}>{name}</option>
|
||||||
</select>
|
))}
|
||||||
<select multiple onChange={doSelectFilename}>
|
</select>
|
||||||
{disks.map((disk) => (
|
<select multiple onChange={doSelectFilename}>
|
||||||
<option key={disk.filename} value={disk.filename}>
|
{disks.map((disk) => (
|
||||||
{disk.name}
|
<option key={disk.filename} value={disk.filename}>
|
||||||
{disk.disk ? ` - ${disk.disk}` : ''}
|
{disk.name}
|
||||||
</option>
|
{disk.disk ? ` - ${disk.disk}` : ''}
|
||||||
))}
|
</option>
|
||||||
</select>
|
))}
|
||||||
</div>
|
</select>
|
||||||
<FileChooser onChange={onChange} accept={DISK_TYPES} />
|
</div>
|
||||||
</ModalContent>
|
<FileChooser onChange={onChange} accept={DISK_TYPES} />
|
||||||
<ModalFooter>
|
</ModalContent>
|
||||||
<button onClick={doCancel}>Cancel</button>
|
<ModalFooter>
|
||||||
<button onClick={noAwait(doOpen)} disabled={busy || empty}>Open</button>
|
<button onClick={doCancel}>Cancel</button>
|
||||||
</ModalFooter>
|
<button onClick={noAwait(doOpen)} disabled={busy || empty}>Open</button>
|
||||||
</Modal>
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
<ErrorModal error={error} setError={setError} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { h, ComponentChildren } from 'preact';
|
import { h, ComponentChildren } from 'preact';
|
||||||
|
import { createPortal } from 'preact/compat';
|
||||||
import { useCallback } from 'preact/hooks';
|
import { useCallback } from 'preact/hooks';
|
||||||
import { useHotKey } from './hooks/useHotKey';
|
import { useHotKey } from './hooks/useHotKey';
|
||||||
|
|
||||||
|
@ -69,7 +70,7 @@ const modalFooterStyle = {
|
||||||
*/
|
*/
|
||||||
export const ModalOverlay = ({ children }: { children: ComponentChildren }) => {
|
export const ModalOverlay = ({ children }: { children: ComponentChildren }) => {
|
||||||
return (
|
return (
|
||||||
<div style={modalOverlayStyle}>
|
<div style={modalOverlayStyle} className="modal-overlay">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -95,9 +96,9 @@ export const ModalContent = ({ children }: { children: ComponentChildren }) => {
|
||||||
*/
|
*/
|
||||||
export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
|
export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
|
||||||
return (
|
return (
|
||||||
<div style={modalFooterStyle}>
|
<footer style={modalFooterStyle}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</footer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -135,6 +136,7 @@ type OnCloseCallback = (closeBox?: boolean) => void;
|
||||||
export interface ModalHeaderProps {
|
export interface ModalHeaderProps {
|
||||||
onClose?: OnCloseCallback;
|
onClose?: OnCloseCallback;
|
||||||
title: string;
|
title: string;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,12 +146,16 @@ export interface ModalHeaderProps {
|
||||||
* @param title Modal title
|
* @param title Modal title
|
||||||
* @returns ModalHeader component
|
* @returns ModalHeader component
|
||||||
*/
|
*/
|
||||||
export const ModalHeader = ({ onClose, title }: ModalHeaderProps) => {
|
export const ModalHeader = ({ onClose, title, icon }: ModalHeaderProps) => {
|
||||||
return (
|
return (
|
||||||
<div style={modalHeaderStyle}>
|
<header style={modalHeaderStyle}>
|
||||||
<span style={modalTitleStyle}>{title}</span>
|
<span style={modalTitleStyle}>
|
||||||
|
{icon && <i className={`fas fa-${icon}`} role="img" />}
|
||||||
|
{' '}
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
{onClose && <ModalCloseButton onClose={onClose} />}
|
{onClose && <ModalCloseButton onClose={onClose} />}
|
||||||
</div>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,6 +167,7 @@ export interface ModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
children: ComponentChildren;
|
children: ComponentChildren;
|
||||||
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,13 +187,13 @@ export const Modal = ({
|
||||||
...props
|
...props
|
||||||
}: ModalProps) => {
|
}: ModalProps) => {
|
||||||
return (
|
return (
|
||||||
isOpen ? (
|
isOpen ? createPortal((
|
||||||
<ModalOverlay>
|
<ModalOverlay>
|
||||||
<div style={modalStyle}>
|
<div style={modalStyle} role="dialog">
|
||||||
{title && <ModalHeader title={title} {...props} />}
|
{title && <ModalHeader title={title} {...props} />}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</ModalOverlay>
|
</ModalOverlay>
|
||||||
) : null
|
), document.body) : null
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
export const useHash = () => {
|
||||||
|
const [hash, setHash] = useState(window.location.hash);
|
||||||
|
|
||||||
|
const popstateListener = () => {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
setHash(hash);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('popstate', popstateListener);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('popstate', popstateListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
};
|
|
@ -12,8 +12,8 @@ import DiskII from 'js/cards/disk2';
|
||||||
*
|
*
|
||||||
* @returns an padded array for 1 based indexing
|
* @returns an padded array for 1 based indexing
|
||||||
*/
|
*/
|
||||||
export const getHashParts = () => {
|
export const getHashParts = (hash: string) => {
|
||||||
const parts = window.location.hash.match(/^#([^|]*)\|?(.*)$/) || ['', '', ''];
|
const parts = hash.match(/^#([^|]*)\|?(.*)$/) || ['', '', ''];
|
||||||
return ['', parts[1], parts[2]];
|
return ['', parts[1], parts[2]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ export const loadLocalFile = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject(`Extension ${ext} not recognized.`);
|
reject(`Extension "${ext}" not recognized.`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fileReader.readAsArrayBuffer(file);
|
fileReader.readAsArrayBuffer(file);
|
||||||
|
@ -139,7 +139,7 @@ export const loadHttpFile = async (
|
||||||
const ext = fileParts.pop()?.toLowerCase() || '[none]';
|
const ext = fileParts.pop()?.toLowerCase() || '[none]';
|
||||||
const name = decodeURIComponent(fileParts.join('.'));
|
const name = decodeURIComponent(fileParts.join('.'));
|
||||||
if (!includes(NIBBLE_FORMATS, ext)) {
|
if (!includes(NIBBLE_FORMATS, ext)) {
|
||||||
throw new Error(`Extension ${ext} not recognized.`);
|
throw new Error(`Extension "${ext}" not recognized.`);
|
||||||
}
|
}
|
||||||
disk2.setBinary(number, name, ext, data);
|
disk2.setBinary(number, name, ext, data);
|
||||||
initGamepad();
|
initGamepad();
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"@babel/preset-env": "^7.9.0",
|
"@babel/preset-env": "^7.9.0",
|
||||||
"@babel/preset-typescript": "^7.16.7",
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
"@testing-library/dom": "^7.30.3",
|
"@testing-library/dom": "^7.30.3",
|
||||||
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/preact": "^3.0.1",
|
"@testing-library/preact": "^3.0.1",
|
||||||
"@testing-library/user-event": "^13.1.3",
|
"@testing-library/user-event": "^13.1.3",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
|
@ -2906,6 +2907,104 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom": {
|
||||||
|
"version": "5.16.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz",
|
||||||
|
"integrity": "sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2",
|
||||||
|
"@types/testing-library__jest-dom": "^5.9.1",
|
||||||
|
"aria-query": "^5.0.0",
|
||||||
|
"chalk": "^3.0.0",
|
||||||
|
"css": "^3.0.0",
|
||||||
|
"css.escape": "^1.5.1",
|
||||||
|
"dom-accessibility-api": "^0.5.6",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"redent": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8",
|
||||||
|
"npm": ">=6",
|
||||||
|
"yarn": ">=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/aria-query": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/chalk": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@testing-library/jest-dom/node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@testing-library/preact": {
|
"node_modules/@testing-library/preact": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/preact/-/preact-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/preact/-/preact-3.1.1.tgz",
|
||||||
|
@ -3480,6 +3579,15 @@
|
||||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/testing-library__jest-dom": {
|
||||||
|
"version": "5.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz",
|
||||||
|
"integrity": "sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jest": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/wicg-file-system-access": {
|
"node_modules/@types/wicg-file-system-access": {
|
||||||
"version": "2020.9.5",
|
"version": "2020.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
|
||||||
|
@ -4260,6 +4368,18 @@
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/atob": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"atob": "bin/atob.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
"version": "27.2.4",
|
"version": "27.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz",
|
||||||
|
@ -5099,6 +5219,32 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"source-map": "^0.6.1",
|
||||||
|
"source-map-resolve": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css.escape": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/css/node_modules/source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssom": {
|
"node_modules/cssom": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||||
|
@ -5160,6 +5306,15 @@
|
||||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
|
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/decode-uri-component": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/decompress-response": {
|
"node_modules/decompress-response": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
|
@ -11256,6 +11411,15 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/min-indent": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimalistic-assert": {
|
"node_modules/minimalistic-assert": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
|
@ -12190,6 +12354,19 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redent": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"indent-string": "^4.0.0",
|
||||||
|
"strip-indent": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regenerate": {
|
"node_modules/regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
@ -12755,6 +12932,17 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map-resolve": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
|
||||||
|
"deprecated": "See https://github.com/lydell/source-map-resolve#deprecated",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"atob": "^2.1.2",
|
||||||
|
"decode-uri-component": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-support": {
|
"node_modules/source-map-support": {
|
||||||
"version": "0.5.19",
|
"version": "0.5.19",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||||
|
@ -12973,6 +13161,18 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strip-indent": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"min-indent": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-json-comments": {
|
"node_modules/strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
|
@ -16595,6 +16795,80 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@testing-library/jest-dom": {
|
||||||
|
"version": "5.16.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz",
|
||||||
|
"integrity": "sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.9.2",
|
||||||
|
"@types/testing-library__jest-dom": "^5.9.1",
|
||||||
|
"aria-query": "^5.0.0",
|
||||||
|
"chalk": "^3.0.0",
|
||||||
|
"css": "^3.0.0",
|
||||||
|
"css.escape": "^1.5.1",
|
||||||
|
"dom-accessibility-api": "^0.5.6",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"redent": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aria-query": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@testing-library/preact": {
|
"@testing-library/preact": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/preact/-/preact-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/preact/-/preact-3.1.1.tgz",
|
||||||
|
@ -17092,6 +17366,15 @@
|
||||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/testing-library__jest-dom": {
|
||||||
|
"version": "5.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz",
|
||||||
|
"integrity": "sha512-oKZe+Mf4ioWlMuzVBaXQ9WDnEm1+umLx0InILg+yvZVBBDmzV5KfZyLrCvadtWcx8+916jLmHafcmqqffl+iIw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/jest": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/wicg-file-system-access": {
|
"@types/wicg-file-system-access": {
|
||||||
"version": "2020.9.5",
|
"version": "2020.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
|
||||||
|
@ -17683,6 +17966,12 @@
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"atob": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"babel-jest": {
|
"babel-jest": {
|
||||||
"version": "27.2.4",
|
"version": "27.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz",
|
||||||
|
@ -18347,6 +18636,31 @@
|
||||||
"which": "^2.0.1"
|
"which": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"css": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"source-map": "^0.6.1",
|
||||||
|
"source-map-resolve": "^0.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"css.escape": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"cssom": {
|
"cssom": {
|
||||||
"version": "0.4.4",
|
"version": "0.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||||
|
@ -18396,6 +18710,12 @@
|
||||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
|
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"decode-uri-component": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"decompress-response": {
|
"decompress-response": {
|
||||||
"version": "4.2.1",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||||
|
@ -23010,6 +23330,12 @@
|
||||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"min-indent": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"minimalistic-assert": {
|
"minimalistic-assert": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
|
@ -23716,6 +24042,16 @@
|
||||||
"resolve": "^1.9.0"
|
"resolve": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redent": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"indent-string": "^4.0.0",
|
||||||
|
"strip-indent": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"regenerate": {
|
"regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
@ -24162,6 +24498,16 @@
|
||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"source-map-resolve": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"atob": "^2.1.2",
|
||||||
|
"decode-uri-component": "^0.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"source-map-support": {
|
"source-map-support": {
|
||||||
"version": "0.5.19",
|
"version": "0.5.19",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||||
|
@ -24344,6 +24690,15 @@
|
||||||
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
|
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"strip-indent": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"min-indent": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"strip-json-comments": {
|
"strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
"@babel/preset-env": "^7.9.0",
|
"@babel/preset-env": "^7.9.0",
|
||||||
"@babel/preset-typescript": "^7.16.7",
|
"@babel/preset-typescript": "^7.16.7",
|
||||||
"@testing-library/dom": "^7.30.3",
|
"@testing-library/dom": "^7.30.3",
|
||||||
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/preact": "^3.0.1",
|
"@testing-library/preact": "^3.0.1",
|
||||||
"@testing-library/user-event": "^13.1.3",
|
"@testing-library/user-event": "^13.1.3",
|
||||||
"@types/jest": "^27.0.2",
|
"@types/jest": "^27.0.2",
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/** @jest-environment jsdom */
|
||||||
|
import { h } from 'preact';
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/preact';
|
||||||
|
import {
|
||||||
|
ErrorModal,
|
||||||
|
} from 'js/components/ErrorModal';
|
||||||
|
|
||||||
|
describe('ErrorModal', () => {
|
||||||
|
it('renders when there is an error', () => {
|
||||||
|
const setError = jest.fn();
|
||||||
|
render(
|
||||||
|
<ErrorModal error="My Error" setError={setError} />
|
||||||
|
);
|
||||||
|
expect(screen.queryByRole('banner')).toBeVisible();
|
||||||
|
expect(screen.queryByRole('banner')).toHaveTextContent('Error');
|
||||||
|
expect(screen.queryByText('My Error')).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render when there is not an error', () => {
|
||||||
|
const setError = jest.fn();
|
||||||
|
render(
|
||||||
|
<ErrorModal error={undefined} setError={setError} />
|
||||||
|
);
|
||||||
|
expect(screen.queryByRole('banner')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('My Error')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setError when close is clicked', () => {
|
||||||
|
const setError = jest.fn();
|
||||||
|
render(
|
||||||
|
<ErrorModal error="My Error" setError={setError} />
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByTitle('Close'));
|
||||||
|
expect(setError).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setError when OK is clicked', () => {
|
||||||
|
const setError = jest.fn();
|
||||||
|
render(
|
||||||
|
<ErrorModal error="My Error" setError={setError} />
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByText('OK'));
|
||||||
|
expect(setError).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,97 @@
|
||||||
|
/** @jest-environment jsdom */
|
||||||
|
import { h } from 'preact';
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/preact';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
} from 'js/components/Modal';
|
||||||
|
|
||||||
|
describe('Modal', () => {
|
||||||
|
it('renders a title and content when open', () => {
|
||||||
|
render(
|
||||||
|
<Modal
|
||||||
|
title="My Title"
|
||||||
|
isOpen={true}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
My Content
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
expect(screen.queryByRole('banner')).toBeVisible();
|
||||||
|
expect(screen.queryByRole('banner')).toHaveTextContent('My Title');
|
||||||
|
expect(screen.queryByText('My Content')).toBeVisible();
|
||||||
|
|
||||||
|
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByTitle('Close')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render a title and content when not open', () => {
|
||||||
|
render(
|
||||||
|
<Modal
|
||||||
|
title="My Title"
|
||||||
|
isOpen={false}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
My Content
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
expect(screen.queryByRole('banner')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('My Content')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a footer', () => {
|
||||||
|
render(
|
||||||
|
<Modal
|
||||||
|
title="My Title"
|
||||||
|
isOpen={true}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
My Content
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
My Footer
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
expect(screen.queryByRole('banner')).toHaveTextContent('My Title');
|
||||||
|
expect(screen.queryByText('My Content')).toBeVisible();
|
||||||
|
expect(screen.getByRole('contentinfo')).toHaveTextContent('My Footer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can have a close button', () => {
|
||||||
|
const onClose = jest.fn();
|
||||||
|
render(
|
||||||
|
<Modal
|
||||||
|
title="My Title"
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
My Content
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
const button = screen.getByTitle('Close');
|
||||||
|
expect(button).toBeVisible();
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(onClose).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can have an icon', () => {
|
||||||
|
render(
|
||||||
|
<Modal
|
||||||
|
title="My Title"
|
||||||
|
isOpen={true}
|
||||||
|
icon="warning"
|
||||||
|
>
|
||||||
|
<ModalContent>
|
||||||
|
My Content
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
expect(screen.getByRole('img')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,3 +1,4 @@
|
||||||
import { toMatchImageSnapshot } from 'jest-image-snapshot';
|
import { toMatchImageSnapshot } from 'jest-image-snapshot';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
expect.extend({ toMatchImageSnapshot });
|
expect.extend({ toMatchImageSnapshot });
|
Loading…
Reference in New Issue