mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
parent
04ae0327c2
commit
ef404735cd
@ -162,11 +162,8 @@ th {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inset button {
|
||||
.inset button, .modal-overlay button {
|
||||
min-width: 36px;
|
||||
}
|
||||
|
||||
.inset button {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
|
@ -10,14 +10,13 @@ module.exports = {
|
||||
'testMatch': [
|
||||
'**/?(*.)+(spec|test).+(ts|js|tsx)'
|
||||
],
|
||||
|
||||
'transform': {
|
||||
'^.+\\.js$': 'babel-jest',
|
||||
'^.+\\.ts$': 'ts-jest',
|
||||
'^.*\\.tsx$': 'ts-jest',
|
||||
},
|
||||
'setupFilesAfterEnv': [
|
||||
'<rootDir>/test/jest-setup.js'
|
||||
'<rootDir>/test/jest-setup.ts'
|
||||
],
|
||||
'coveragePathIgnorePatterns': [
|
||||
'/node_modules/',
|
||||
|
@ -12,6 +12,7 @@ import { Screen } from './Screen';
|
||||
import { Drives } from './Drives';
|
||||
import { Slinky } from './Slinky';
|
||||
import { ThunderClock } from './ThunderClock';
|
||||
import { ErrorModal } from './ErrorModal';
|
||||
|
||||
/**
|
||||
* Interface for the Apple2 component.
|
||||
@ -40,6 +41,7 @@ export const Apple2 = (props: Apple2Props) => {
|
||||
const [apple2, setApple2] = useState<Apple2Impl>();
|
||||
const [io, setIO] = useState<Apple2IO>();
|
||||
const [cpu, setCPU] = useState<CPU6502>();
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (screen.current) {
|
||||
@ -57,7 +59,13 @@ export const Apple2 = (props: Apple2Props) => {
|
||||
setCPU(cpu);
|
||||
apple2.reset();
|
||||
apple2.run();
|
||||
}).catch(error => console.error(error));
|
||||
}).catch((e) => {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [props]);
|
||||
|
||||
@ -74,6 +82,7 @@ export const Apple2 = (props: Apple2Props) => {
|
||||
<Inset>
|
||||
<Keyboard apple2={apple2} e={e} />
|
||||
</Inset>
|
||||
<ErrorModal error={error} setError={setError} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -4,6 +4,8 @@ import cs from 'classnames';
|
||||
import Disk2 from '../cards/disk2';
|
||||
import { FileModal } from './FileModal';
|
||||
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.
|
||||
@ -38,25 +40,37 @@ export interface DiskIIProps extends DiskIIData {
|
||||
export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
||||
const label = side ? `${name} - ${side}` : name;
|
||||
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(() => {
|
||||
const hashParts = getHashParts();
|
||||
if (disk2 && hashParts && hashParts[number]) {
|
||||
const hashPart = decodeURIComponent(hashParts[number]);
|
||||
if (hashPart.match(/^https?:/)) {
|
||||
loadHttpFile(disk2, number, hashPart)
|
||||
.catch((error) =>
|
||||
console.error(error)
|
||||
);
|
||||
} else {
|
||||
const filename = `/json/disks/${hashPart}.json`;
|
||||
loadJSON(disk2, number, filename)
|
||||
.catch((error) =>
|
||||
console.error(error)
|
||||
);
|
||||
const hashParts = getHashParts(hash);
|
||||
const newHash = hashParts[number];
|
||||
if (disk2 && newHash) {
|
||||
const hashPart = decodeURIComponent(newHash);
|
||||
if (hashPart !== currentHash) {
|
||||
if (hashPart.match(/^https?:/)) {
|
||||
loadHttpFile(disk2, number, hashPart)
|
||||
.catch((e) => handleError(e));
|
||||
} else {
|
||||
const filename = `/json/disks/${hashPart}.json`;
|
||||
loadJSON(disk2, number, filename)
|
||||
.catch((e) => handleError(e));
|
||||
}
|
||||
setCurrentHash(hashPart);
|
||||
}
|
||||
}
|
||||
}, [disk2, number]);
|
||||
}, [currentHash, disk2, hash, number]);
|
||||
|
||||
const doClose = useCallback(() => {
|
||||
setModalOpen(false);
|
||||
@ -69,6 +83,7 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
|
||||
return (
|
||||
<div className="disk">
|
||||
<FileModal disk2={disk2} number={number} onClose={doClose} isOpen={modalOpen} />
|
||||
<ErrorModal error={error} setError={setError} />
|
||||
<div
|
||||
id={`disk${number}`}
|
||||
className={cs('disk-light', { on })}
|
||||
|
34
js/components/ErrorModal.tsx
Normal file
34
js/components/ErrorModal.tsx
Normal file
@ -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;
|
||||
}
|
||||
|
||||
const hasPicker: boolean = !!window.showOpenFilePicker;
|
||||
const hasPicker = !!window.showOpenFilePicker;
|
||||
const controlDefault = hasPicker ? 'picker' : 'input';
|
||||
|
||||
interface InputFileChooserProps {
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { h, JSX } from 'preact';
|
||||
import { h, Fragment, JSX } from 'preact';
|
||||
import { useCallback, useState } from 'preact/hooks';
|
||||
import { DiskDescriptor, DriveNumber, NibbleFormat, NIBBLE_FORMATS } from '../formats/types';
|
||||
import { Modal, ModalContent, ModalFooter } from './Modal';
|
||||
import { loadLocalFile, loadJSON, getHashParts, setHashParts } from './util/files';
|
||||
import DiskII from '../cards/disk2';
|
||||
import { ErrorModal } from './ErrorModal';
|
||||
|
||||
import index from 'json/disks/index.json';
|
||||
import { FileChooser, FilePickerAcceptType, FileSystemFileHandleLike } from './FileChooser';
|
||||
import { noAwait } from './util/promises';
|
||||
import { useHash } from './hooks/useHash';
|
||||
|
||||
const DISK_TYPES: FilePickerAcceptType[] = [
|
||||
{
|
||||
@ -50,39 +52,39 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||
const [category, setCategory] = useState<string>();
|
||||
const [handles, setHandles] = useState<FileSystemFileHandleLike[]>();
|
||||
const [filename, setFilename] = useState<string>();
|
||||
const [error, setError] = useState<string>();
|
||||
const hash = useHash();
|
||||
|
||||
const doCancel = useCallback(() => onClose(true), [onClose]);
|
||||
|
||||
const doOpen = useCallback(async () => {
|
||||
const hashParts = getHashParts();
|
||||
const hashParts = getHashParts(hash);
|
||||
setBusy(true);
|
||||
|
||||
if (disk2 && handles && handles.length === 1) {
|
||||
hashParts[number] = '';
|
||||
setBusy(true);
|
||||
try {
|
||||
try {
|
||||
if (disk2 && handles?.length === 1) {
|
||||
hashParts[number] = '';
|
||||
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$/) || ['', ''];
|
||||
hashParts[number] = name[1];
|
||||
setBusy(true);
|
||||
loadJSON(disk2, number, filename)
|
||||
.catch(console.error)
|
||||
.finally(() => {
|
||||
setBusy(false);
|
||||
onClose();
|
||||
});
|
||||
if (disk2 && filename) {
|
||||
const name = filename.match(/\/([^/]+).json$/) || ['', ''];
|
||||
hashParts[number] = name[1];
|
||||
await loadJSON(disk2, number, filename);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
} finally {
|
||||
setHashParts(hashParts);
|
||||
setBusy(false);
|
||||
onClose();
|
||||
}
|
||||
|
||||
setHashParts(hashParts);
|
||||
}, [disk2, filename, number, onClose, handles]);
|
||||
}, [disk2, filename, number, onClose, handles, hash]);
|
||||
|
||||
const onChange = useCallback((handles: FileSystemFileHandleLike[]) => {
|
||||
setEmpty(handles.length === 0);
|
||||
@ -105,29 +107,32 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
|
||||
const disks = category ? categories[category] : [];
|
||||
|
||||
return (
|
||||
<Modal title="Open File" isOpen={isOpen}>
|
||||
<ModalContent>
|
||||
<div id="load-modal">
|
||||
<select multiple onChange={doSelectCategory}>
|
||||
{categoryNames.map((name) => (
|
||||
<option key={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select multiple onChange={doSelectFilename}>
|
||||
{disks.map((disk) => (
|
||||
<option key={disk.filename} value={disk.filename}>
|
||||
{disk.name}
|
||||
{disk.disk ? ` - ${disk.disk}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<FileChooser onChange={onChange} accept={DISK_TYPES} />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<button onClick={doCancel}>Cancel</button>
|
||||
<button onClick={noAwait(doOpen)} disabled={busy || empty}>Open</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<>
|
||||
<Modal title="Open File" isOpen={isOpen}>
|
||||
<ModalContent>
|
||||
<div id="load-modal">
|
||||
<select multiple onChange={doSelectCategory}>
|
||||
{categoryNames.map((name) => (
|
||||
<option key={name}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
<select multiple onChange={doSelectFilename}>
|
||||
{disks.map((disk) => (
|
||||
<option key={disk.filename} value={disk.filename}>
|
||||
{disk.name}
|
||||
{disk.disk ? ` - ${disk.disk}` : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<FileChooser onChange={onChange} accept={DISK_TYPES} />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<button onClick={doCancel}>Cancel</button>
|
||||
<button onClick={noAwait(doOpen)} disabled={busy || empty}>Open</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
<ErrorModal error={error} setError={setError} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { h, ComponentChildren } from 'preact';
|
||||
import { createPortal } from 'preact/compat';
|
||||
import { useCallback } from 'preact/hooks';
|
||||
import { useHotKey } from './hooks/useHotKey';
|
||||
|
||||
@ -69,7 +70,7 @@ const modalFooterStyle = {
|
||||
*/
|
||||
export const ModalOverlay = ({ children }: { children: ComponentChildren }) => {
|
||||
return (
|
||||
<div style={modalOverlayStyle}>
|
||||
<div style={modalOverlayStyle} className="modal-overlay">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@ -95,9 +96,9 @@ export const ModalContent = ({ children }: { children: ComponentChildren }) => {
|
||||
*/
|
||||
export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
|
||||
return (
|
||||
<div style={modalFooterStyle}>
|
||||
<footer style={modalFooterStyle}>
|
||||
{children}
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -135,6 +136,7 @@ type OnCloseCallback = (closeBox?: boolean) => void;
|
||||
export interface ModalHeaderProps {
|
||||
onClose?: OnCloseCallback;
|
||||
title: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,12 +146,16 @@ export interface ModalHeaderProps {
|
||||
* @param title Modal title
|
||||
* @returns ModalHeader component
|
||||
*/
|
||||
export const ModalHeader = ({ onClose, title }: ModalHeaderProps) => {
|
||||
export const ModalHeader = ({ onClose, title, icon }: ModalHeaderProps) => {
|
||||
return (
|
||||
<div style={modalHeaderStyle}>
|
||||
<span style={modalTitleStyle}>{title}</span>
|
||||
<header style={modalHeaderStyle}>
|
||||
<span style={modalTitleStyle}>
|
||||
{icon && <i className={`fas fa-${icon}`} role="img" />}
|
||||
{' '}
|
||||
{title}
|
||||
</span>
|
||||
{onClose && <ModalCloseButton onClose={onClose} />}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
@ -161,6 +167,7 @@ export interface ModalProps {
|
||||
isOpen: boolean;
|
||||
title: string;
|
||||
children: ComponentChildren;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,13 +187,13 @@ export const Modal = ({
|
||||
...props
|
||||
}: ModalProps) => {
|
||||
return (
|
||||
isOpen ? (
|
||||
isOpen ? createPortal((
|
||||
<ModalOverlay>
|
||||
<div style={modalStyle}>
|
||||
<div style={modalStyle} role="dialog">
|
||||
{title && <ModalHeader title={title} {...props} />}
|
||||
{children}
|
||||
</div>
|
||||
</ModalOverlay>
|
||||
) : null
|
||||
), document.body) : null
|
||||
);
|
||||
};
|
||||
|
19
js/components/hooks/useHash.ts
Normal file
19
js/components/hooks/useHash.ts
Normal file
@ -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
|
||||
*/
|
||||
export const getHashParts = () => {
|
||||
const parts = window.location.hash.match(/^#([^|]*)\|?(.*)$/) || ['', '', ''];
|
||||
export const getHashParts = (hash: string) => {
|
||||
const parts = hash.match(/^#([^|]*)\|?(.*)$/) || ['', '', ''];
|
||||
return ['', parts[1], parts[2]];
|
||||
};
|
||||
|
||||
@ -60,7 +60,7 @@ export const loadLocalFile = (
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reject(`Extension ${ext} not recognized.`);
|
||||
reject(`Extension "${ext}" not recognized.`);
|
||||
}
|
||||
};
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
@ -139,7 +139,7 @@ export const loadHttpFile = async (
|
||||
const ext = fileParts.pop()?.toLowerCase() || '[none]';
|
||||
const name = decodeURIComponent(fileParts.join('.'));
|
||||
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);
|
||||
initGamepad();
|
||||
|
355
package-lock.json
generated
355
package-lock.json
generated
@ -20,6 +20,7 @@
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@testing-library/dom": "^7.30.3",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/preact": "^3.0.1",
|
||||
"@testing-library/user-event": "^13.1.3",
|
||||
"@types/jest": "^27.0.2",
|
||||
@ -2906,6 +2907,104 @@
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/preact/-/preact-3.1.1.tgz",
|
||||
@ -3480,6 +3579,15 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"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": {
|
||||
"version": "2020.9.5",
|
||||
"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=",
|
||||
"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": {
|
||||
"version": "27.2.4",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz",
|
||||
@ -5099,6 +5219,32 @@
|
||||
"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": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||
@ -5160,6 +5306,15 @@
|
||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
|
||||
"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": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||
@ -11256,6 +11411,15 @@
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
@ -12190,6 +12354,19 @@
|
||||
"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": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
@ -12755,6 +12932,17 @@
|
||||
"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": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
@ -12973,6 +13161,18 @@
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/preact/-/preact-3.1.1.tgz",
|
||||
@ -17092,6 +17366,15 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"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": {
|
||||
"version": "2020.9.5",
|
||||
"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=",
|
||||
"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": {
|
||||
"version": "27.2.4",
|
||||
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.2.4.tgz",
|
||||
@ -18347,6 +18636,31 @@
|
||||
"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": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
|
||||
@ -18396,6 +18710,12 @@
|
||||
"integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
|
||||
"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": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||
@ -23010,6 +23330,12 @@
|
||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||
@ -23716,6 +24042,16 @@
|
||||
"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": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
@ -24162,6 +24498,16 @@
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"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": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
@ -24344,6 +24690,15 @@
|
||||
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"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-typescript": "^7.16.7",
|
||||
"@testing-library/dom": "^7.30.3",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/preact": "^3.0.1",
|
||||
"@testing-library/user-event": "^13.1.3",
|
||||
"@types/jest": "^27.0.2",
|
||||
|
45
test/components/ErrorModal.spec.tsx
Normal file
45
test/components/ErrorModal.spec.tsx
Normal file
@ -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);
|
||||
});
|
||||
});
|
97
test/components/Modal.spec.tsx
Normal file
97
test/components/Modal.spec.tsx
Normal file
@ -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 '@testing-library/jest-dom';
|
||||
|
||||
expect.extend({ toMatchImageSnapshot });
|
Loading…
x
Reference in New Issue
Block a user