diff --git a/.eslintrc.json b/.eslintrc.json index 895e356..a2864e2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -103,4 +103,4 @@ } ] } -} \ No newline at end of file +} diff --git a/__TESTS__/sorting.spec.tsx b/__TESTS__/sorting.spec.tsx new file mode 100644 index 0000000..c8d1b1c --- /dev/null +++ b/__TESTS__/sorting.spec.tsx @@ -0,0 +1,99 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +/* eslint-enable import/no-extraneous-dependencies */ +import React from 'react'; + +import Sorting from '../pages/sorting'; + +describe('Sorting', () => { + it('should sort', () => { + render(); + + const CommaInputs = [ + 'c,a,b', + 'c,a,b', + 'cc,a,bbb', + 'cc,a,bbb', + 'c,a,b', + ]; + const otherSeparatorsInputs = [ + 'c a b', + 'c.a.b', + 'c;a;b', + 'c\na\nb', + ]; + const CommaOutputs = [ + 'a,b,c', + 'c,b,a', + 'a,cc,bbb', + 'bbb,cc,a', + 'b,a,c', + ]; + const otherSeparatorOutputs = [ + 'a b c', + 'a.b.c', + 'a;b;c', + 'a\nb\nc', + ]; + + const SortStyleList = [ + 'ascending', + 'descending', + 'ascending2', + 'descending2', + 'reverse', + ]; + + const SortButton = screen.getByRole('button', { + name: 'Sort', + }) as HTMLButtonElement; + + CommaInputs.forEach((input, index) => { + const inputText = screen.getByLabelText( + 'Input', + ) as HTMLInputElement; + userEvent.type(inputText, input).then(() => { + const sortStyle = screen.getByLabelText( + 'Sort Style', + ) as HTMLInputElement; + fireEvent.change(sortStyle, { + target: { value: SortStyleList[index] }, + }); + userEvent.click(SortButton).then(() => { + const outputText = screen.getByLabelText('Output'); + expect(outputText.innerText).toBe(CommaOutputs[index]); + }); + }); + }); + + const SeparatorsList = ['space', 'dot', 'semicolon', 'lineBreak']; + otherSeparatorsInputs.forEach((input, index) => { + const inputText = screen.getByLabelText( + 'Input', + ) as HTMLInputElement; + userEvent.type(inputText, input).then(() => { + const separator = screen.getByLabelText( + 'Separator', + ) as HTMLInputElement; + fireEvent.change(separator, { + target: { value: SeparatorsList[index] }, + }); + + const sortStyle = screen.getByLabelText( + 'Sort Style', + ) as HTMLInputElement; + fireEvent.change(sortStyle, { + target: { value: SortStyleList[index] }, + }); + + userEvent.click(SortButton).then(() => { + const outputText = screen.getByLabelText('Output'); + expect(outputText.innerText).toBe( + otherSeparatorOutputs[index], + ); + }); + }); + }); + }); +}); diff --git a/data/nav.ts b/data/nav.ts index df2c2ca..aa43dec 100644 --- a/data/nav.ts +++ b/data/nav.ts @@ -7,6 +7,7 @@ import Fingerprint from '@mui/icons-material/Fingerprint'; import HomeIcon from '@mui/icons-material/Home'; import LinkIcon from '@mui/icons-material/Link'; import Looks6 from '@mui/icons-material/Looks6'; +import SortByAlphaIcon from '@mui/icons-material/SortByAlpha'; // Icons Index: https://mui.com/material-ui/material-icons/ @@ -46,6 +47,11 @@ const navItems = [ href: '/url-encode', Icon: LinkIcon, }, + { + title: 'Sorting', + href: '/sorting', + Icon: SortByAlphaIcon, + }, { title: 'HTML', href: '/html', diff --git a/pages/sorting.tsx b/pages/sorting.tsx new file mode 100644 index 0000000..31511ff --- /dev/null +++ b/pages/sorting.tsx @@ -0,0 +1,251 @@ +import ClearIcon from '@mui/icons-material/Clear'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import ContentPasteGoIcon from '@mui/icons-material/ContentPasteGo'; +import { Typography } from '@mui/material'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import FormControl from '@mui/material/FormControl'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import Select, { SelectChangeEvent } from '@mui/material/Select'; +import TextField from '@mui/material/TextField'; +import React, { useState } from 'react'; + +import Heading from '../components/Heading'; +import Layout from '../components/Layout'; +import Toast, { ToastProps } from '../components/Toast'; +import useLocalState from '../hooks/useLocalState'; +import useSupportsClipboardRead from '../hooks/useSupportsClipboardRead'; + +export default function Sorting() { + const supportsClipboardRead = useSupportsClipboardRead(); + const [inputText, setInputText] = useLocalState({ + key: 'inputTextLocalState', + defaultValue: '', + }); + + const [sortStyle, setSortStyle] = useLocalState({ + key: 'sortStyleLocalState', + defaultValue: 'ascending', + }); + + const [separator, setSeparator] = useLocalState({ + key: 'separatorLocalState', + defaultValue: ',', + }); + + const [outputText, setOutputText] = useLocalState({ + key: 'outputTextLocalState', + defaultValue: '', + }); + + const [toastOpen, setToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(''); + const [toastSeverity, setToastSeverity] = + useState('success'); + + const handleSortStyleChange = (event: SelectChangeEvent) => { + setSortStyle(event.target.value); + }; + + const handleSeparatorChange = (event: SelectChangeEvent) => { + setSeparator(event.target.value); + }; + + const handleClear = () => { + setInputText(''); + setOutputText(''); + }; + + const handlePaste = async () => { + const textCopied = await navigator.clipboard.readText(); + setInputText(textCopied); + setOutputText(decodeURIComponent(textCopied)); + }; + + const handleSort = () => { + if (inputText === '') { + setToastMessage('Please enter some text to sort.'); + setToastSeverity('error'); + setToastOpen(true); + return; + } + + if (separator === '') { + setToastMessage('Please enter a separator.'); + setToastSeverity('error'); + setToastOpen(true); + return; + } + + let inputTextArray; + + if (separator === 'newLine') { + inputTextArray = inputText.split(/\r\n|\n\r|\n|\r/); + } else { + inputTextArray = inputText.split(separator); + } + const outputTextArray = + sortStyle === 'reverse' + ? inputTextArray.reverse() + : inputTextArray.sort((a, b) => { + if (sortStyle === 'ascending') { + return a.localeCompare(b); + } + if (sortStyle === 'descending') { + return b.localeCompare(a); + } + if (sortStyle === 'ascending2') { + if (a.length === b.length) { + return a.localeCompare(b); + } + return a.length - b.length; + } + if (sortStyle === 'descending2') { + if (a.length === b.length) { + return b.localeCompare(a); + } + return b.length - a.length; + } + return 0; + }); + + if (separator === 'newLine') + setOutputText(outputTextArray.join('\n')); + else setOutputText(outputTextArray.join(separator)); + }; + + return ( + + Sorting Tool + + Paste or Type an list and select options from the dropdown to + sort + + + + + setInputText(event.target.value)} + multiline + /> + + + {supportsClipboardRead && ( + + )} + + + Sort Style + + + + + Separator + + + + + + + + + + + + + + + setToastOpen(false)} + /> + + ); +}