diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f2c549b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: + - main # or the branch you want to trigger the workflow on + pull_request: + branches: + - main + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' # Specify the Node.js version you want to use + + - name: Install dependencies + run: npm install + + - name: Run tests + run: npm run test + + - name: Build project + run: npm run build + + - name: Deploy to Netlify + uses: nwtgck/actions-netlify@v1.2 + with: + publish-dir: ./build + production-branch: main + deploy-message: Deploy from GitHub Actions + enable-pull-request-comment: true + enable-commit-comment: true + overwrites-pull-request-comment: true + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} + timeout-minutes: 1 + diff --git a/package.json b/package.json index a7c5979..21137f2 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "test:ui": "vitest --ui", "lint": "eslint src --max-warnings=0", "typecheck": "tsc --project tsconfig.json --noEmit", - "prepare": "husky install" + "prepare": "husky install", + "prebuild": "npm run test" }, "dependencies": { "@emotion/react": "^11.11.4", diff --git a/src/components/test.tsx b/src/components/test.tsx deleted file mode 100644 index e1bbd3f..0000000 --- a/src/components/test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { render, screen } from '@testing-library/react' - -import App from './App' - -describe('', () => { - it('should render the App', () => { - const { container } = render() - - expect( - screen.getByRole('heading', { - name: /Welcome!/i, - level: 1 - }) - ).toBeInTheDocument() - - expect( - screen.getByText( - /This is a boilerplate build with Vite, React 18, TypeScript, Vitest, Testing Library, TailwindCSS 3, Eslint and Prettier./i - ) - ).toBeInTheDocument() - - expect( - screen.getByRole('link', { - name: /start building for free/i - }) - ).toBeInTheDocument() - - expect(screen.getByRole('img')).toBeInTheDocument() - - expect(container.firstChild).toBeInTheDocument() - }) -}) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index cc9ab3f..1929925 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,4 +1,4 @@ -export {default as useDebounce} from "./useDebounce"; -export {default as useTimeout} from "./useTimeout"; -export {default as usePrevious} from "./usePrevious"; -export {default as useUpdateEffect} from "./useUpdateEffect"; +export { default as useDebounce } from './useDebounce'; +export { default as useTimeout } from './useTimeout'; +export { default as usePrevious } from './usePrevious'; +export { default as useUpdateEffect } from './useUpdateEffect'; diff --git a/src/pages/string/split/index.tsx b/src/pages/string/split/index.tsx index 9e11ded..e75d697 100644 --- a/src/pages/string/split/index.tsx +++ b/src/pages/string/split/index.tsx @@ -9,10 +9,9 @@ import ToolTextResult from '../../../components/result/ToolTextResult'; import { Field, Formik, FormikProps, useFormikContext } from 'formik'; import * as Yup from 'yup'; import ToolOptions from '../../../components/ToolOptions'; -import { splitIntoChunks, splitTextByLength } from './service'; +import { compute, SplitOperatorType } from './service'; import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext'; -type SplitOperatorType = 'symbol' | 'regex' | 'length' | 'chunks'; const initialValues = { splitSeparatorType: 'symbol' as SplitOperatorType, symbolValue: ' ', @@ -160,24 +159,20 @@ export default function SplitText() { regexValue, lengthValue } = values; - 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, Number(lengthValue)); - break; - case 'chunks': - splitText = splitIntoChunks(input, Number(chunksValue)).map( - (chunk) => `${charBeforeChunk}${chunk}${charAfterChunk}` - ); - } - const res = splitText.join(outputSeparator); - setResult(res); + + setResult( + compute( + splitSeparatorType, + input, + symbolValue, + regexValue, + Number(lengthValue), + Number(chunksValue), + charBeforeChunk, + charAfterChunk, + outputSeparator + ) + ); } catch (exception: unknown) { if (exception instanceof Error) showSnackBar(exception.message, 'error'); diff --git a/src/pages/string/split/service.ts b/src/pages/string/split/service.ts index 5775b79..cea1bdc 100644 --- a/src/pages/string/split/service.ts +++ b/src/pages/string/split/service.ts @@ -1,4 +1,6 @@ -export function splitTextByLength(text: string, length: number) { +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) { @@ -7,7 +9,7 @@ export function splitTextByLength(text: string, length: number) { return result; } -export function splitIntoChunks(text: string, numChunks: number) { +function splitIntoChunks(text: string, numChunks: number) { if (numChunks <= 0) throw new Error('Number of chunks must be a positive number'); const totalLength = text.length; @@ -31,3 +33,33 @@ export function splitIntoChunks(text: string, numChunks: number) { 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); +} diff --git a/src/pages/string/split/string-split.test.ts b/src/pages/string/split/string-split.test.ts new file mode 100644 index 0000000..4917cdc --- /dev/null +++ b/src/pages/string/split/string-split.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } 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'); + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 38502f8..0597438 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,7 @@ /// -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' -import tsconfigPaths from 'vite-tsconfig-paths' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react-swc'; +import tsconfigPaths from 'vite-tsconfig-paths'; // https://vitejs.dev/config https://vitest.dev/config export default defineConfig({ @@ -10,6 +10,6 @@ export default defineConfig({ globals: true, environment: 'happy-dom', setupFiles: '.vitest/setup', - include: ['**/test.{ts,tsx}'] + include: ['**/*.test.{ts,tsx}'] } -}) +});