mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-24 08:29:32 +02:00
refactor: tools folder inside pages
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { createPalindrome, createPalindromeList } from './service';
|
||||
|
||||
describe('createPalindrome', () => {
|
||||
test('should create palindrome by reversing the entire string', () => {
|
||||
const input = 'hello';
|
||||
const result = createPalindrome(input, true);
|
||||
expect(result).toBe('helloolleh');
|
||||
});
|
||||
|
||||
test('should create palindrome by reversing the string excluding the last character', () => {
|
||||
const input = 'hello';
|
||||
const result = createPalindrome(input, false);
|
||||
expect(result).toBe('hellolleh');
|
||||
});
|
||||
|
||||
test('should return an empty string if input is empty', () => {
|
||||
const input = '';
|
||||
const result = createPalindrome(input, true);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPalindromeList', () => {
|
||||
test('should create palindrome for single-line input', () => {
|
||||
const input = 'hello';
|
||||
const result = createPalindromeList(input, true, false);
|
||||
expect(result).toBe('helloolleh');
|
||||
});
|
||||
|
||||
test('should create palindrome for single-line input considering trailing spaces', () => {
|
||||
const input = 'hello ';
|
||||
const result = createPalindromeList(input, true, false);
|
||||
expect(result).toBe('hello olleh');
|
||||
});
|
||||
|
||||
test('should create palindrome for single-line input ignoring trailing spaces if lastChar is set to false', () => {
|
||||
const input = 'hello ';
|
||||
const result = createPalindromeList(input, true, false);
|
||||
expect(result).toBe('hello olleh');
|
||||
});
|
||||
|
||||
test('should create palindrome for multi-line input', () => {
|
||||
const input = 'hello\nworld';
|
||||
const result = createPalindromeList(input, true, true);
|
||||
expect(result).toBe('helloolleh\nworlddlrow');
|
||||
});
|
||||
|
||||
test('should create palindrome for no multi-line input', () => {
|
||||
const input = 'hello\nworld\n';
|
||||
const result = createPalindromeList(input, true, false);
|
||||
expect(result).toBe('hello\nworld\n\ndlrow\nolleh');
|
||||
});
|
||||
|
||||
test('should handle multi-line input with lastChar set to false', () => {
|
||||
const input = 'hello\nworld';
|
||||
const result = createPalindromeList(input, false, true);
|
||||
expect(result).toBe('hellolleh\nworldlrow');
|
||||
});
|
||||
|
||||
test('should return an empty string if input is empty', () => {
|
||||
const input = '';
|
||||
const result = createPalindromeList(input, true, false);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
11
src/pages/tools/string/create-palindrome/index.tsx
Normal file
11
src/pages/tools/string/create-palindrome/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const initialValues = {};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function CreatePalindrome() {
|
||||
return <Box>Lorem ipsum</Box>;
|
||||
}
|
13
src/pages/tools/string/create-palindrome/meta.ts
Normal file
13
src/pages/tools/string/create-palindrome/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Create palindrome',
|
||||
path: 'create-palindrome',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['create', 'palindrome'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
36
src/pages/tools/string/create-palindrome/service.ts
Normal file
36
src/pages/tools/string/create-palindrome/service.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { reverseString } from 'utils/string';
|
||||
|
||||
export function createPalindrome(
|
||||
input: string,
|
||||
lastChar: boolean // only checkbox is need here to handle it [instead of two combo boxes]
|
||||
) {
|
||||
if (!input) return '';
|
||||
let result: string;
|
||||
let reversedString: string;
|
||||
|
||||
// reverse the whole input if lastChar enabled
|
||||
reversedString = lastChar
|
||||
? reverseString(input)
|
||||
: reverseString(input.slice(0, -1));
|
||||
result = input.concat(reversedString);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createPalindromeList(
|
||||
input: string,
|
||||
lastChar: boolean,
|
||||
multiLine: boolean
|
||||
): string {
|
||||
if (!input) return '';
|
||||
let array: string[];
|
||||
const result: string[] = [];
|
||||
|
||||
if (!multiLine) return createPalindrome(input, lastChar);
|
||||
else {
|
||||
array = input.split('\n');
|
||||
for (const word of array) {
|
||||
result.push(createPalindrome(word, lastChar));
|
||||
}
|
||||
}
|
||||
return result.join('\n');
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { extractSubstring } from './service';
|
||||
|
||||
describe('extractSubstring', () => {
|
||||
it('should extract a substring from single-line input', () => {
|
||||
const input = 'hello world';
|
||||
const result = extractSubstring(input, 1, 4, false, false);
|
||||
expect(result).toBe('hell');
|
||||
});
|
||||
|
||||
it('should extract and reverse a substring from single-line input', () => {
|
||||
const input = 'hello world';
|
||||
const result = extractSubstring(input, 1, 5, false, true);
|
||||
expect(result).toBe('olleh');
|
||||
});
|
||||
|
||||
it('should extract substrings from multi-line input', () => {
|
||||
const input = 'hello\nworld';
|
||||
const result = extractSubstring(input, 1, 5, true, false);
|
||||
expect(result).toBe('hello\nworld');
|
||||
});
|
||||
|
||||
it('should extract and reverse substrings from multi-line input', () => {
|
||||
const input = 'hello\nworld';
|
||||
const result = extractSubstring(input, 1, 4, true, true);
|
||||
expect(result).toBe('lleh\nlrow');
|
||||
});
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const input = '';
|
||||
const result = extractSubstring(input, 1, 5, false, false);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle start and length out of bounds', () => {
|
||||
const input = 'hello';
|
||||
const result = extractSubstring(input, 10, 5, false, false);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle negative start and length', () => {
|
||||
expect(() => extractSubstring('hello', -1, 5, false, false)).toThrow(
|
||||
'Start index must be greater than zero.'
|
||||
);
|
||||
expect(() => extractSubstring('hello', 1, -5, false, false)).toThrow(
|
||||
'Length value must be greater than or equal to zero.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle zero length', () => {
|
||||
const input = 'hello';
|
||||
const result = extractSubstring(input, 1, 0, false, false);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
const input = 'je me nomme king\n22 est mon chiffre';
|
||||
const result = extractSubstring(input, 12, 7, true, false);
|
||||
expect(result).toBe(' king\nchiffre');
|
||||
});
|
||||
});
|
11
src/pages/tools/string/extract-substring/index.tsx
Normal file
11
src/pages/tools/string/extract-substring/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const initialValues = {};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function ExtractSubstring() {
|
||||
return <Box>Lorem ipsum</Box>;
|
||||
}
|
13
src/pages/tools/string/extract-substring/meta.ts
Normal file
13
src/pages/tools/string/extract-substring/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Extract substring',
|
||||
path: 'extract-substring',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['extract', 'substring'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
36
src/pages/tools/string/extract-substring/service.ts
Normal file
36
src/pages/tools/string/extract-substring/service.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { reverseString } from 'utils/string';
|
||||
|
||||
export function extractSubstring(
|
||||
input: string,
|
||||
start: number,
|
||||
length: number,
|
||||
multiLine: boolean,
|
||||
reverse: boolean
|
||||
): string {
|
||||
if (!input) return '';
|
||||
// edge Cases
|
||||
if (start <= 0) throw new Error('Start index must be greater than zero.');
|
||||
if (length < 0)
|
||||
throw new Error('Length value must be greater than or equal to zero.');
|
||||
if (length === 0) return '';
|
||||
|
||||
let array: string[];
|
||||
let result: string[] = [];
|
||||
|
||||
const extract = (str: string, start: number, length: number): string => {
|
||||
const end = start - 1 + length;
|
||||
if (start - 1 >= str.length) return '';
|
||||
return str.substring(start - 1, Math.min(end, str.length));
|
||||
};
|
||||
|
||||
if (!multiLine) {
|
||||
result.push(extract(input, start, length));
|
||||
} else {
|
||||
array = input.split('\n');
|
||||
for (const word of array) {
|
||||
result.push(extract(word, start, length));
|
||||
}
|
||||
}
|
||||
result = reverse ? result.map((word) => reverseString(word)) : result;
|
||||
return result.join('\n');
|
||||
}
|
21
src/pages/tools/string/index.ts
Normal file
21
src/pages/tools/string/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { tool as stringReverse } from './reverse/meta';
|
||||
import { tool as stringRandomizeCase } from './randomize-case/meta';
|
||||
import { tool as stringUppercase } from './uppercase/meta';
|
||||
import { tool as stringExtractSubstring } from './extract-substring/meta';
|
||||
import { tool as stringCreatePalindrome } from './create-palindrome/meta';
|
||||
import { tool as stringPalindrome } from './palindrome/meta';
|
||||
import { tool as stringToMorse } from './to-morse/meta';
|
||||
import { tool as stringSplit } from './split/meta';
|
||||
import { tool as stringJoin } from './join/meta';
|
||||
|
||||
export const stringTools = [
|
||||
stringSplit,
|
||||
stringJoin,
|
||||
stringToMorse,
|
||||
stringReverse,
|
||||
stringRandomizeCase,
|
||||
stringUppercase,
|
||||
stringExtractSubstring,
|
||||
stringCreatePalindrome,
|
||||
stringPalindrome
|
||||
];
|
190
src/pages/tools/string/join/index.tsx
Normal file
190
src/pages/tools/string/join/index.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import ToolTextInput from '@components/input/ToolTextInput';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import ToolOptions from '@components/options/ToolOptions';
|
||||
import { mergeText } from './service';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
|
||||
import ToolInputAndResult from '@components/ToolInputAndResult';
|
||||
|
||||
import ToolInfo from '@components/ToolInfo';
|
||||
import Separator from '@components/Separator';
|
||||
import Examples from '@components/examples/Examples';
|
||||
|
||||
const initialValues = {
|
||||
joinCharacter: '',
|
||||
deleteBlank: true,
|
||||
deleteTrailing: true
|
||||
};
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
joinCharacter: Yup.string().required('Join character is required'),
|
||||
deleteBlank: Yup.boolean().required('Delete blank is required'),
|
||||
deleteTrailing: Yup.boolean().required('Delete trailing is required')
|
||||
});
|
||||
|
||||
const mergeOptions = {
|
||||
placeholder: 'Join Character',
|
||||
description:
|
||||
'Symbol that connects broken\n' + 'pieces of text. (Space by default.)\n',
|
||||
accessor: 'joinCharacter' as keyof typeof initialValues
|
||||
};
|
||||
|
||||
const blankTrailingOptions: {
|
||||
title: string;
|
||||
description: string;
|
||||
accessor: keyof typeof initialValues;
|
||||
}[] = [
|
||||
{
|
||||
title: 'Delete Blank Lines',
|
||||
description: "Delete lines that don't have\n text symbols.\n",
|
||||
accessor: 'deleteBlank'
|
||||
},
|
||||
{
|
||||
title: 'Delete Trailing Spaces',
|
||||
description: 'Remove spaces and tabs at\n the end of the lines.\n',
|
||||
accessor: 'deleteTrailing'
|
||||
}
|
||||
];
|
||||
|
||||
const exampleCards = [
|
||||
{
|
||||
title: 'Merge a To-Do List',
|
||||
description:
|
||||
"In this example, we merge a bullet point list into one sentence, separating each item by the word 'and'. We also remove all empty lines and trailing spaces. If we didn't remove the empty lines, then they'd be joined with the separator word, making the separator word appear multiple times. If we didn't remove the trailing tabs and spaces, then they'd create extra spacing in the joined text and it wouldn't look nice.",
|
||||
sampleText: `clean the house
|
||||
|
||||
go shopping
|
||||
feed the cat
|
||||
|
||||
make dinner
|
||||
build a rocket ship and fly away`,
|
||||
sampleResult: `clean the house and go shopping and feed the cat and make dinner and build a rocket ship and fly away`,
|
||||
requiredOptions: {
|
||||
joinCharacter: 'and',
|
||||
deleteBlankLines: true,
|
||||
deleteTrailingSpaces: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Comma Separated List',
|
||||
description:
|
||||
'This example joins a column of words into a comma separated list of words.',
|
||||
sampleText: `computer
|
||||
memory
|
||||
processor
|
||||
mouse
|
||||
keyboard`,
|
||||
sampleResult: `computer, memory, processor, mouse, keyboard`,
|
||||
requiredOptions: {
|
||||
joinCharacter: ',',
|
||||
deleteBlankLines: false,
|
||||
deleteTrailingSpaces: false
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Vertical Word to Horizontal',
|
||||
description:
|
||||
'This example rotates words from a vertical position to horizontal. An empty separator is used for this purpose.',
|
||||
sampleText: `T
|
||||
e
|
||||
x
|
||||
t
|
||||
a
|
||||
b
|
||||
u
|
||||
l
|
||||
o
|
||||
u
|
||||
s
|
||||
!`,
|
||||
sampleResult: `Textabulous!`,
|
||||
requiredOptions: {
|
||||
joinCharacter: '',
|
||||
deleteBlankLines: false,
|
||||
deleteTrailingSpaces: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export default function JoinText() {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
|
||||
const compute = (optionsValues: typeof initialValues, input: any) => {
|
||||
const { joinCharacter, deleteBlank, deleteTrailing } = optionsValues;
|
||||
setResult(mergeText(input, deleteBlank, deleteTrailing, joinCharacter));
|
||||
};
|
||||
|
||||
function changeInputResult(input: string, result: string) {
|
||||
setInput(input);
|
||||
setResult(result);
|
||||
|
||||
const toolsElement = document.getElementById('tool');
|
||||
if (toolsElement) {
|
||||
toolsElement.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ToolInputAndResult
|
||||
input={
|
||||
<ToolTextInput
|
||||
title={'Text Pieces'}
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
/>
|
||||
}
|
||||
result={<ToolTextResult title={'Joined Text'} value={result} />}
|
||||
/>
|
||||
<ToolOptions
|
||||
compute={compute}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Text Merged Options',
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
placeholder={mergeOptions.placeholder}
|
||||
value={values['joinCharacter']}
|
||||
onOwnChange={(value) =>
|
||||
updateField(mergeOptions.accessor, value)
|
||||
}
|
||||
description={mergeOptions.description}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Blank Lines and Trailing Spaces',
|
||||
component: blankTrailingOptions.map((option) => (
|
||||
<CheckboxWithDesc
|
||||
key={option.accessor}
|
||||
title={option.title}
|
||||
checked={!!values[option.accessor]}
|
||||
onChange={(value) => updateField(option.accessor, value)}
|
||||
description={option.description}
|
||||
/>
|
||||
))
|
||||
}
|
||||
]}
|
||||
initialValues={initialValues}
|
||||
input={input}
|
||||
/>
|
||||
<ToolInfo
|
||||
title="What Is a Text Joiner?"
|
||||
description="With this tool you can join parts of the text together. It takes a list of text values, separated by newlines, and merges them together. You can set the character that will be placed between the parts of the combined text. Also, you can ignore all empty lines and remove spaces and tabs at the end of all lines. Textabulous!"
|
||||
/>
|
||||
<Separator backgroundColor="#5581b5" margin="50px" />
|
||||
<Examples
|
||||
title="Text Joiner Examples"
|
||||
subtitle="Click to try!"
|
||||
exampleCards={exampleCards.map((card) => ({
|
||||
...card,
|
||||
changeInputResult
|
||||
}))}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
14
src/pages/tools/string/join/meta.ts
Normal file
14
src/pages/tools/string/join/meta.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
path: 'join',
|
||||
name: 'Text Joiner',
|
||||
image,
|
||||
description:
|
||||
"World's Simplest Text Tool World's simplest browser-based utility for joining text. Load your text in the input form on the left and you'll automatically get merged text on the right. Powerful, free, and fast. Load text – get joined lines",
|
||||
shortDescription: 'Quickly merge texts',
|
||||
keywords: ['text', 'join'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
17
src/pages/tools/string/join/service.ts
Normal file
17
src/pages/tools/string/join/service.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export function mergeText(
|
||||
text: string,
|
||||
deleteBlankLines: boolean = true,
|
||||
deleteTrailingSpaces: boolean = true,
|
||||
joinCharacter: string = ''
|
||||
): string {
|
||||
let processedLines: string[] = text.split('\n');
|
||||
if (deleteTrailingSpaces) {
|
||||
processedLines = processedLines.map((line) => line.trimEnd());
|
||||
}
|
||||
|
||||
if (deleteBlankLines) {
|
||||
processedLines = processedLines.filter((line) => line.trim());
|
||||
}
|
||||
|
||||
return processedLines.join(joinCharacter);
|
||||
}
|
18
src/pages/tools/string/join/string-join.e2e.spec.ts
Normal file
18
src/pages/tools/string/join/string-join.e2e.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('JoinText Component', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/string/join');
|
||||
});
|
||||
|
||||
test('should merge text pieces with specified join character', async ({
|
||||
page
|
||||
}) => {
|
||||
// Input the text pieces
|
||||
await page.getByTestId('text-input').fill('1\n2');
|
||||
|
||||
const result = await page.getByTestId('text-result').inputValue();
|
||||
|
||||
expect(result).toBe('12');
|
||||
});
|
||||
});
|
64
src/pages/tools/string/join/string-join.service.test.ts
Normal file
64
src/pages/tools/string/join/string-join.service.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mergeText } from './service';
|
||||
|
||||
describe('mergeText', () => {
|
||||
it('should merge lines with default settings (delete blank lines, delete trailing spaces, join with empty string)', () => {
|
||||
const input = 'line1 \n \nline2\nline3 \n\nline4';
|
||||
const expected = 'line1line2line3line4';
|
||||
expect(mergeText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should merge lines and preserve blank lines when deleteBlankLines is false', () => {
|
||||
const input = 'line1 \n \nline2\nline3 \n\nline4';
|
||||
const expected = 'line1line2line3line4';
|
||||
expect(mergeText(input, false, true, '')).toBe(expected);
|
||||
});
|
||||
|
||||
it('should merge lines and preserve trailing spaces when deleteTrailingSpaces is false', () => {
|
||||
const input = 'line1 \n \nline2\nline3 \n\nline4';
|
||||
const expected = 'line1 line2line3 line4';
|
||||
expect(mergeText(input, true, false)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should merge lines, preserve blank lines and trailing spaces when both deleteBlankLines and deleteTrailingSpaces are false', () => {
|
||||
const input = 'line1 \n \nline2\nline3 \n\nline4';
|
||||
const expected = 'line1 line2line3 line4';
|
||||
expect(mergeText(input, false, false)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should merge lines with a specified joinCharacter', () => {
|
||||
const input = 'line1 \n \nline2\nline3 \n\nline4';
|
||||
const expected = 'line1 line2 line3 line4';
|
||||
expect(mergeText(input, true, true, ' ')).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const input = '';
|
||||
const expected = '';
|
||||
expect(mergeText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle input with only blank lines', () => {
|
||||
const input = ' \n \n\n';
|
||||
const expected = '';
|
||||
expect(mergeText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle input with only trailing spaces', () => {
|
||||
const input = 'line1 \nline2 \nline3 ';
|
||||
const expected = 'line1line2line3';
|
||||
expect(mergeText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle single line input', () => {
|
||||
const input = 'single line';
|
||||
const expected = 'single line';
|
||||
expect(mergeText(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it('should join lines with new line character when joinCharacter is set to "\\n"', () => {
|
||||
const input = 'line1 \n \nline2\nline3 \n\nline4';
|
||||
const expected = 'line1\nline2\nline3\nline4';
|
||||
expect(mergeText(input, true, true, '\n')).toBe(expected);
|
||||
});
|
||||
});
|
11
src/pages/tools/string/palindrome/index.tsx
Normal file
11
src/pages/tools/string/palindrome/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const initialValues = {};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function Palindrome() {
|
||||
return <Box>Lorem ipsum</Box>;
|
||||
}
|
13
src/pages/tools/string/palindrome/meta.ts
Normal file
13
src/pages/tools/string/palindrome/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Palindrome',
|
||||
path: 'palindrome',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['palindrome'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
60
src/pages/tools/string/palindrome/palindrome.service.test.ts
Normal file
60
src/pages/tools/string/palindrome/palindrome.service.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { describe, expect } from 'vitest';
|
||||
import { palindromeList } from './service';
|
||||
|
||||
describe('palindromeList', () => {
|
||||
test('should return true for single character words', () => {
|
||||
const input = 'a|b|c';
|
||||
const separator = '|';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('true|true|true');
|
||||
});
|
||||
|
||||
test('should return false for non-palindromes', () => {
|
||||
const input = 'hello|world';
|
||||
const separator = '|';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('false|false');
|
||||
});
|
||||
|
||||
test('should split using regex', () => {
|
||||
const input = 'racecar,abba,hello';
|
||||
const separator = ',';
|
||||
const result = palindromeList('regex', input, separator);
|
||||
expect(result).toBe('true,true,false');
|
||||
});
|
||||
|
||||
test('should return empty string for empty input', () => {
|
||||
const input = '';
|
||||
const separator = '|';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
test('should split using custom separator', () => {
|
||||
const input = 'racecar;abba;hello';
|
||||
const separator = ';';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('true;true;false');
|
||||
});
|
||||
|
||||
test('should handle leading and trailing spaces', () => {
|
||||
const input = ' racecar | abba | hello ';
|
||||
const separator = '|';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('true|true|false');
|
||||
});
|
||||
|
||||
test('should handle multilines checking with trimming', () => {
|
||||
const input = ' racecar \n abba \n hello ';
|
||||
const separator = '\n';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('true\ntrue\nfalse');
|
||||
});
|
||||
|
||||
test('should handle empty strings in input', () => {
|
||||
const input = 'racecar||hello';
|
||||
const separator = '|';
|
||||
const result = palindromeList('symbol', input, separator);
|
||||
expect(result).toBe('true|true|false');
|
||||
});
|
||||
});
|
41
src/pages/tools/string/palindrome/service.ts
Normal file
41
src/pages/tools/string/palindrome/service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export type SplitOperatorType = 'symbol' | 'regex';
|
||||
|
||||
function isPalindrome(word: string, left: number, right: number): boolean {
|
||||
if (left >= right) return true;
|
||||
if (word[left] !== word[right]) return false;
|
||||
|
||||
return isPalindrome(word, left + 1, right - 1);
|
||||
}
|
||||
|
||||
// check each word of the input and add the palindrome status in an array
|
||||
function checkPalindromes(array: string[]): boolean[] {
|
||||
const status: boolean[] = [];
|
||||
for (const word of array) {
|
||||
const palindromeStatus = isPalindrome(word, 0, word.length - 1);
|
||||
status.push(palindromeStatus);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
export function palindromeList(
|
||||
splitOperatorType: SplitOperatorType,
|
||||
input: string,
|
||||
separator: string // the splitting separator will be the joining separator for visual satisfaction
|
||||
): string {
|
||||
if (!input) return '';
|
||||
let array: string[];
|
||||
switch (splitOperatorType) {
|
||||
case 'symbol':
|
||||
array = input.split(separator);
|
||||
break;
|
||||
case 'regex':
|
||||
array = input.split(new RegExp(separator));
|
||||
break;
|
||||
}
|
||||
// trim all items to focus on the word and not biasing the result due to spaces (leading and trailing)
|
||||
array = array.map((item) => item.trim());
|
||||
|
||||
const statusArray = checkPalindromes(array);
|
||||
|
||||
return statusArray.map((status) => status.toString()).join(separator);
|
||||
}
|
11
src/pages/tools/string/randomize-case/index.tsx
Normal file
11
src/pages/tools/string/randomize-case/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const initialValues = {};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function RandomizeCase() {
|
||||
return <Box>Lorem ipsum</Box>;
|
||||
}
|
13
src/pages/tools/string/randomize-case/meta.ts
Normal file
13
src/pages/tools/string/randomize-case/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Randomize case',
|
||||
path: 'randomize-case',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['randomize', 'case'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { randomizeCase } from './service';
|
||||
|
||||
describe('randomizeCase', () => {
|
||||
it('should randomize the case of each character in the string', () => {
|
||||
const input = 'hello world';
|
||||
const result = randomizeCase(input);
|
||||
|
||||
// Ensure the output length is the same
|
||||
expect(result).toHaveLength(input.length);
|
||||
|
||||
// Ensure each character in the input string appears in the result
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const inputChar = input[i];
|
||||
const resultChar = result[i];
|
||||
|
||||
if (/[a-zA-Z]/.test(inputChar)) {
|
||||
expect([inputChar.toLowerCase(), inputChar.toUpperCase()]).toContain(
|
||||
resultChar
|
||||
);
|
||||
} else {
|
||||
expect(inputChar).toBe(resultChar);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle an empty string', () => {
|
||||
const input = '';
|
||||
const result = randomizeCase(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle a string with numbers and symbols', () => {
|
||||
const input = '123 hello! @world';
|
||||
const result = randomizeCase(input);
|
||||
|
||||
// Ensure the output length is the same
|
||||
expect(result).toHaveLength(input.length);
|
||||
|
||||
// Ensure numbers and symbols remain unchanged
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const inputChar = input[i];
|
||||
const resultChar = result[i];
|
||||
|
||||
if (!/[a-zA-Z]/.test(inputChar)) {
|
||||
expect(inputChar).toBe(resultChar);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
8
src/pages/tools/string/randomize-case/service.ts
Normal file
8
src/pages/tools/string/randomize-case/service.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function randomizeCase(input: string): string {
|
||||
return input
|
||||
.split('')
|
||||
.map((char) =>
|
||||
Math.random() < 0.5 ? char.toLowerCase() : char.toUpperCase()
|
||||
)
|
||||
.join('');
|
||||
}
|
11
src/pages/tools/string/reverse/index.tsx
Normal file
11
src/pages/tools/string/reverse/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const initialValues = {};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function Reverse() {
|
||||
return <Box>Lorem ipsum</Box>;
|
||||
}
|
13
src/pages/tools/string/reverse/meta.ts
Normal file
13
src/pages/tools/string/reverse/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Reverse',
|
||||
path: 'reverse',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['reverse'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
52
src/pages/tools/string/reverse/reverse.service.test.ts
Normal file
52
src/pages/tools/string/reverse/reverse.service.test.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { stringReverser } from './service';
|
||||
|
||||
describe('stringReverser', () => {
|
||||
it('should reverse a single-line string', () => {
|
||||
const input = 'hello world';
|
||||
const result = stringReverser(input, false, false, false);
|
||||
expect(result).toBe('dlrow olleh');
|
||||
});
|
||||
|
||||
it('should reverse each line in a multi-line string', () => {
|
||||
const input = 'hello\nworld';
|
||||
const result = stringReverser(input, true, false, false);
|
||||
expect(result).toBe('olleh\ndlrow');
|
||||
});
|
||||
|
||||
it('should remove empty items if emptyItems is true', () => {
|
||||
const input = 'hello\n\nworld';
|
||||
const result = stringReverser(input, true, true, false);
|
||||
expect(result).toBe('olleh\ndlrow');
|
||||
});
|
||||
|
||||
it('should trim each line if trim is true', () => {
|
||||
const input = ' hello \n world ';
|
||||
const result = stringReverser(input, true, false, true);
|
||||
expect(result).toBe('olleh\ndlrow');
|
||||
});
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const input = '';
|
||||
const result = stringReverser(input, false, false, false);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle a single line with emptyItems and trim', () => {
|
||||
const input = ' hello world ';
|
||||
const result = stringReverser(input, false, true, true);
|
||||
expect(result).toBe('dlrow olleh');
|
||||
});
|
||||
|
||||
it('should handle a single line with emptyItems and non trim', () => {
|
||||
const input = ' hello world ';
|
||||
const result = stringReverser(input, false, true, false);
|
||||
expect(result).toBe(' dlrow olleh ');
|
||||
});
|
||||
|
||||
it('should handle a multi line with emptyItems and non trim', () => {
|
||||
const input = ' hello\n\n\n\nworld ';
|
||||
const result = stringReverser(input, true, true, false);
|
||||
expect(result).toBe('olleh \n dlrow');
|
||||
});
|
||||
});
|
30
src/pages/tools/string/reverse/service.ts
Normal file
30
src/pages/tools/string/reverse/service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { reverseString } from 'utils/string';
|
||||
|
||||
export function stringReverser(
|
||||
input: string,
|
||||
multiLine: boolean,
|
||||
emptyItems: boolean,
|
||||
trim: boolean
|
||||
) {
|
||||
let array: string[] = [];
|
||||
let result: string[] = [];
|
||||
|
||||
// split the input in multiLine mode
|
||||
if (multiLine) {
|
||||
array = input.split('\n');
|
||||
} else {
|
||||
array.push(input);
|
||||
}
|
||||
|
||||
// handle empty items
|
||||
if (emptyItems) {
|
||||
array = array.filter(Boolean);
|
||||
}
|
||||
// Handle trim
|
||||
if (trim) {
|
||||
array = array.map((line) => line.trim());
|
||||
}
|
||||
|
||||
result = array.map((element) => reverseString(element));
|
||||
return result.join('\n');
|
||||
}
|
148
src/pages/tools/string/split/index.tsx
Normal file
148
src/pages/tools/string/split/index.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import ToolTextInput from '@components/input/ToolTextInput';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import ToolOptions from '@components/options/ToolOptions';
|
||||
import { compute, SplitOperatorType } from './service';
|
||||
import RadioWithTextField from '@components/options/RadioWithTextField';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import ToolInputAndResult from '@components/ToolInputAndResult';
|
||||
|
||||
const initialValues = {
|
||||
splitSeparatorType: 'symbol' as SplitOperatorType,
|
||||
symbolValue: ' ',
|
||||
regexValue: '/\\s+/',
|
||||
lengthValue: '16',
|
||||
chunksValue: '4',
|
||||
|
||||
outputSeparator: '\\n',
|
||||
charBeforeChunk: '',
|
||||
charAfterChunk: ''
|
||||
};
|
||||
const splitOperators: {
|
||||
title: string;
|
||||
description: string;
|
||||
type: SplitOperatorType;
|
||||
}[] = [
|
||||
{
|
||||
title: 'Use a Symbol for Splitting',
|
||||
description:
|
||||
'Character that will be used to\n' +
|
||||
'break text into parts.\n' +
|
||||
'(Space by default.)',
|
||||
type: 'symbol'
|
||||
},
|
||||
{
|
||||
title: 'Use a Regex for Splitting',
|
||||
type: 'regex',
|
||||
description:
|
||||
'Regular expression that will be\n' +
|
||||
'used to break text into parts.\n' +
|
||||
'(Multiple spaces by default.)'
|
||||
},
|
||||
{
|
||||
title: 'Use Length for Splitting',
|
||||
description:
|
||||
'Number of symbols that will be\n' + 'put in each output chunk.',
|
||||
type: 'length'
|
||||
},
|
||||
{
|
||||
title: 'Use a Number of Chunks',
|
||||
description: 'Number of chunks of equal\n' + 'length in the output.',
|
||||
type: 'chunks'
|
||||
}
|
||||
];
|
||||
const outputOptions: {
|
||||
description: string;
|
||||
accessor: keyof typeof initialValues;
|
||||
}[] = [
|
||||
{
|
||||
description:
|
||||
'Character that will be put\n' +
|
||||
'between the split chunks.\n' +
|
||||
'(It\'s newline "\\n" by default.)',
|
||||
accessor: 'outputSeparator'
|
||||
},
|
||||
{
|
||||
description: 'Character before each chunk',
|
||||
accessor: 'charBeforeChunk'
|
||||
},
|
||||
{
|
||||
description: 'Character after each chunk',
|
||||
accessor: 'charAfterChunk'
|
||||
}
|
||||
];
|
||||
|
||||
export default function SplitText() {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
// const formRef = useRef<FormikProps<typeof initialValues>>(null);
|
||||
const computeExternal = (optionsValues: typeof initialValues, input: any) => {
|
||||
const {
|
||||
splitSeparatorType,
|
||||
outputSeparator,
|
||||
charBeforeChunk,
|
||||
charAfterChunk,
|
||||
chunksValue,
|
||||
symbolValue,
|
||||
regexValue,
|
||||
lengthValue
|
||||
} = optionsValues;
|
||||
|
||||
setResult(
|
||||
compute(
|
||||
splitSeparatorType,
|
||||
input,
|
||||
symbolValue,
|
||||
regexValue,
|
||||
Number(lengthValue),
|
||||
Number(chunksValue),
|
||||
charBeforeChunk,
|
||||
charAfterChunk,
|
||||
outputSeparator
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ToolInputAndResult
|
||||
input={<ToolTextInput value={input} onChange={setInput} />}
|
||||
result={<ToolTextResult title={'Text pieces'} value={result} />}
|
||||
/>
|
||||
<ToolOptions
|
||||
compute={computeExternal}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Split separator options',
|
||||
component: splitOperators.map(({ title, description, type }) => (
|
||||
<RadioWithTextField
|
||||
key={type}
|
||||
checked={type === values.splitSeparatorType}
|
||||
title={title}
|
||||
fieldName={'splitSeparatorType'}
|
||||
description={description}
|
||||
value={values[`${type}Value`]}
|
||||
onRadioClick={() => updateField('splitSeparatorType', type)}
|
||||
onTextChange={(val) => updateField(`${type}Value`, val)}
|
||||
/>
|
||||
))
|
||||
},
|
||||
{
|
||||
title: 'Output separator options',
|
||||
component: outputOptions.map((option) => (
|
||||
<TextFieldWithDesc
|
||||
key={option.accessor}
|
||||
value={values[option.accessor]}
|
||||
onOwnChange={(value) => updateField(option.accessor, value)}
|
||||
description={option.description}
|
||||
/>
|
||||
))
|
||||
}
|
||||
]}
|
||||
initialValues={initialValues}
|
||||
input={input}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
14
src/pages/tools/string/split/meta.ts
Normal file
14
src/pages/tools/string/split/meta.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
path: 'split',
|
||||
name: 'Text splitter',
|
||||
image,
|
||||
description:
|
||||
"World's simplest browser-based utility for splitting text. Load your text in the input form on the left and you'll automatically get pieces of this text on the right. Powerful, free, and fast. Load text – get chunks.",
|
||||
shortDescription: 'Quickly split a text',
|
||||
keywords: ['text', 'split'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
65
src/pages/tools/string/split/service.ts
Normal file
65
src/pages/tools/string/split/service.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export type SplitOperatorType = 'symbol' | 'regex' | 'length' | 'chunks';
|
||||
|
||||
function splitTextByLength(text: string, length: number) {
|
||||
if (length <= 0) throw new Error('Length must be a positive number');
|
||||
const result: string[] = [];
|
||||
for (let i = 0; i < text.length; i += length) {
|
||||
result.push(text.slice(i, i + length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function splitIntoChunks(text: string, numChunks: number) {
|
||||
if (numChunks <= 0)
|
||||
throw new Error('Number of chunks must be a positive number');
|
||||
const totalLength = text.length;
|
||||
if (totalLength < numChunks)
|
||||
throw new Error(
|
||||
'Text length must be at least as long as the number of chunks'
|
||||
);
|
||||
|
||||
const chunkSize = Math.ceil(totalLength / numChunks); // Calculate the chunk size, rounding up to handle remainders
|
||||
let result = [];
|
||||
|
||||
for (let i = 0; i < totalLength; i += chunkSize) {
|
||||
result.push(text.slice(i, i + chunkSize));
|
||||
}
|
||||
|
||||
// Ensure the result contains exactly numChunks, adjusting the last chunk if necessary
|
||||
if (result.length > numChunks) {
|
||||
result[numChunks - 1] = result.slice(numChunks - 1).join(''); // Merge any extra chunks into the last chunk
|
||||
result = result.slice(0, numChunks); // Take only the first numChunks chunks
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function compute(
|
||||
splitSeparatorType: SplitOperatorType,
|
||||
input: string,
|
||||
symbolValue: string,
|
||||
regexValue: string,
|
||||
lengthValue: number,
|
||||
chunksValue: number,
|
||||
charBeforeChunk: string,
|
||||
charAfterChunk: string,
|
||||
outputSeparator: string
|
||||
) {
|
||||
let splitText;
|
||||
switch (splitSeparatorType) {
|
||||
case 'symbol':
|
||||
splitText = input.split(symbolValue);
|
||||
break;
|
||||
case 'regex':
|
||||
splitText = input.split(new RegExp(regexValue));
|
||||
break;
|
||||
case 'length':
|
||||
splitText = splitTextByLength(input, lengthValue);
|
||||
break;
|
||||
case 'chunks':
|
||||
splitText = splitIntoChunks(input, chunksValue).map(
|
||||
(chunk) => `${charBeforeChunk}${chunk}${charAfterChunk}`
|
||||
);
|
||||
}
|
||||
return splitText.join(outputSeparator);
|
||||
}
|
72
src/pages/tools/string/split/string-split.service.test.ts
Normal file
72
src/pages/tools/string/split/string-split.service.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { compute } from './service';
|
||||
|
||||
describe('compute function', () => {
|
||||
it('should split by symbol', () => {
|
||||
const result = compute('symbol', 'hello world', ' ', '', 0, 0, '', '', ',');
|
||||
expect(result).toBe('hello,world');
|
||||
});
|
||||
|
||||
it('should split by regex', () => {
|
||||
const result = compute(
|
||||
'regex',
|
||||
'hello1world2again',
|
||||
'',
|
||||
'\\d',
|
||||
0,
|
||||
0,
|
||||
'',
|
||||
'',
|
||||
','
|
||||
);
|
||||
expect(result).toBe('hello,world,again');
|
||||
});
|
||||
|
||||
it('should split by length', () => {
|
||||
const result = compute('length', 'helloworld', '', '', 3, 0, '', '', ',');
|
||||
expect(result).toBe('hel,low,orl,d');
|
||||
});
|
||||
|
||||
it('should split into chunks', () => {
|
||||
const result = compute(
|
||||
'chunks',
|
||||
'helloworldagain',
|
||||
'',
|
||||
'',
|
||||
0,
|
||||
3,
|
||||
'[',
|
||||
']',
|
||||
','
|
||||
);
|
||||
expect(result).toBe('[hello],[world],[again]');
|
||||
});
|
||||
|
||||
it('should handle empty input', () => {
|
||||
const result = compute('symbol', '', ' ', '', 0, 0, '', '', ',');
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle length greater than text length', () => {
|
||||
const result = compute('length', 'hi', '', '', 5, 0, '', '', ',');
|
||||
expect(result).toBe('hi');
|
||||
});
|
||||
|
||||
it('should handle chunks greater than text length', () => {
|
||||
expect(() => {
|
||||
compute('chunks', 'hi', '', '', 0, 5, '', '', ',');
|
||||
}).toThrow('Text length must be at least as long as the number of chunks');
|
||||
});
|
||||
|
||||
it('should handle invalid length', () => {
|
||||
expect(() => {
|
||||
compute('length', 'hello', '', '', -1, 0, '', '', ',');
|
||||
}).toThrow('Length must be a positive number');
|
||||
});
|
||||
|
||||
it('should handle invalid chunks', () => {
|
||||
expect(() => {
|
||||
compute('chunks', 'hello', '', '', 0, 0, '', '', ',');
|
||||
}).toThrow('Number of chunks must be a positive number');
|
||||
});
|
||||
});
|
63
src/pages/tools/string/to-morse/index.tsx
Normal file
63
src/pages/tools/string/to-morse/index.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import ToolTextInput from '@components/input/ToolTextInput';
|
||||
import ToolTextResult from '@components/result/ToolTextResult';
|
||||
import ToolOptions from '@components/options/ToolOptions';
|
||||
import { compute } from './service';
|
||||
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
||||
import ToolInputAndResult from '@components/ToolInputAndResult';
|
||||
|
||||
const initialValues = {
|
||||
dotSymbol: '.',
|
||||
dashSymbol: '-'
|
||||
};
|
||||
|
||||
export default function ToMorse() {
|
||||
const [input, setInput] = useState<string>('');
|
||||
const [result, setResult] = useState<string>('');
|
||||
// const formRef = useRef<FormikProps<typeof initialValues>>(null);
|
||||
const computeOptions = (optionsValues: typeof initialValues, input: any) => {
|
||||
const { dotSymbol, dashSymbol } = optionsValues;
|
||||
setResult(compute(input, dotSymbol, dashSymbol));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ToolInputAndResult
|
||||
input={<ToolTextInput value={input} onChange={setInput} />}
|
||||
result={<ToolTextResult title={'Morse code'} value={result} />}
|
||||
/>
|
||||
<ToolOptions
|
||||
compute={computeOptions}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Short Signal',
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
description={
|
||||
'Symbol that will correspond to the dot in Morse code.'
|
||||
}
|
||||
value={values.dotSymbol}
|
||||
onOwnChange={(val) => updateField('dotSymbol', val)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Long Signal',
|
||||
component: (
|
||||
<TextFieldWithDesc
|
||||
description={
|
||||
'Symbol that will correspond to the dash in Morse code.'
|
||||
}
|
||||
value={values.dashSymbol}
|
||||
onOwnChange={(val) => updateField('dashSymbol', val)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]}
|
||||
initialValues={initialValues}
|
||||
input={input}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
14
src/pages/tools/string/to-morse/meta.ts
Normal file
14
src/pages/tools/string/to-morse/meta.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'String To morse',
|
||||
path: 'to-morse',
|
||||
// image,
|
||||
description:
|
||||
"World's simplest browser-based utility for converting text to Morse code. Load your text in the input form on the left and you'll instantly get Morse code in the output area. Powerful, free, and fast. Load text – get Morse code.",
|
||||
shortDescription: 'Quickly encode text to morse',
|
||||
keywords: ['to', 'morse'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
9
src/pages/tools/string/to-morse/service.ts
Normal file
9
src/pages/tools/string/to-morse/service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { encode } from 'morsee';
|
||||
|
||||
export const compute = (
|
||||
input: string,
|
||||
dotSymbol: string,
|
||||
dashSymbol: string
|
||||
): string => {
|
||||
return encode(input).replaceAll('.', dotSymbol).replaceAll('-', dashSymbol);
|
||||
};
|
50
src/pages/tools/string/to-morse/to-morse.service.test.ts
Normal file
50
src/pages/tools/string/to-morse/to-morse.service.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { compute } from './service';
|
||||
|
||||
describe('compute function', () => {
|
||||
it('should replace dots and dashes with specified symbols', () => {
|
||||
const input = 'test';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected = '# * *** #';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return an empty string for empty input', () => {
|
||||
const input = '';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
// Test case 3: Special characters handling
|
||||
it('should handle input with special characters', () => {
|
||||
const input = 'hello, world!';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected =
|
||||
'**** * *#** *#** ### ##**## / *## ### *#* *#** #** #*#*##';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should work with different symbols for dots and dashes', () => {
|
||||
const input = 'morse';
|
||||
const dotSymbol = '!';
|
||||
const dashSymbol = '@';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected = '@@ @@@ !@! !!! !';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle numeric input correctly', () => {
|
||||
const input = '12345';
|
||||
const dotSymbol = '*';
|
||||
const dashSymbol = '#';
|
||||
const result = compute(input, dotSymbol, dashSymbol);
|
||||
const expected = '*#### **### ***## ****# *****'; // This depends on how "12345" is encoded in morse code
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
11
src/pages/tools/string/uppercase/index.tsx
Normal file
11
src/pages/tools/string/uppercase/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const initialValues = {};
|
||||
const validationSchema = Yup.object({
|
||||
// splitSeparator: Yup.string().required('The separator is required')
|
||||
});
|
||||
export default function Uppercase() {
|
||||
return <Box>Lorem ipsum</Box>;
|
||||
}
|
13
src/pages/tools/string/uppercase/meta.ts
Normal file
13
src/pages/tools/string/uppercase/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
// import image from '@assets/text.png';
|
||||
|
||||
export const tool = defineTool('string', {
|
||||
name: 'Uppercase',
|
||||
path: 'uppercase',
|
||||
// image,
|
||||
description: '',
|
||||
shortDescription: '',
|
||||
keywords: ['uppercase'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
3
src/pages/tools/string/uppercase/service.ts
Normal file
3
src/pages/tools/string/uppercase/service.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function UppercaseInput(input: string): string {
|
||||
return input.toUpperCase();
|
||||
}
|
34
src/pages/tools/string/uppercase/uppercase.service.test.ts
Normal file
34
src/pages/tools/string/uppercase/uppercase.service.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { UppercaseInput } from './service';
|
||||
|
||||
describe('UppercaseInput', () => {
|
||||
it('should convert a lowercase string to uppercase', () => {
|
||||
const input = 'hello';
|
||||
const result = UppercaseInput(input);
|
||||
expect(result).toBe('HELLO');
|
||||
});
|
||||
|
||||
it('should convert a mixed case string to uppercase', () => {
|
||||
const input = 'HeLLo WoRLd';
|
||||
const result = UppercaseInput(input);
|
||||
expect(result).toBe('HELLO WORLD');
|
||||
});
|
||||
|
||||
it('should convert an already uppercase string to uppercase', () => {
|
||||
const input = 'HELLO';
|
||||
const result = UppercaseInput(input);
|
||||
expect(result).toBe('HELLO');
|
||||
});
|
||||
|
||||
it('should handle an empty string', () => {
|
||||
const input = '';
|
||||
const result = UppercaseInput(input);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle a string with numbers and symbols', () => {
|
||||
const input = '123 hello! @world';
|
||||
const result = UppercaseInput(input);
|
||||
expect(result).toBe('123 HELLO! @WORLD');
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user