From 7810b3c7d824c36f23a0bf76cb8ab5f69a0531ff Mon Sep 17 00:00:00 2001 From: TJ Maynes Date: Mon, 24 Jun 2024 14:30:31 -0500 Subject: [PATCH] chore: added core functionality, ready for further styling --- e2e/home.spec.ts | 102 +++++++++++++++++++--------- package-lock.json | 29 +++++++- package.json | 3 +- src/components/SimpleJsonEditor.tsx | 80 ++++++++++++++++++---- src/utils/json-utils.ts | 8 ++- 5 files changed, 169 insertions(+), 53 deletions(-) diff --git a/e2e/home.spec.ts b/e2e/home.spec.ts index 8d1e128..8ea0041 100644 --- a/e2e/home.spec.ts +++ b/e2e/home.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test' +import { test, expect, Locator } from '@playwright/test' import { getJsonExamples } from './fixtures' test.describe('when a user navigates to the homepage', () => { @@ -29,29 +29,65 @@ test.describe('when a user navigates to the homepage', () => { }) }) - test('should copy json to clipboard when copy button clicked', async ({ - page, - browserName, - }) => { + test.describe('other functionality', () => { const expected = '[{"hello" : "world"}, {"green": "red"}]' + let placeholderText: Locator - await page.getByLabel('Type or paste your json here...').fill(expected) + test.beforeEach(async ({ page }) => { + placeholderText = page.getByPlaceholder('Type or paste your json here...') - await page.getByLabel('Copy').click() + await placeholderText.fill(expected) + }) - if (!['webkit', 'Desktop Safari', 'Mobile Safari'].includes(browserName)) { - const handle = await page.evaluateHandle(() => - navigator.clipboard.readText() - ) - const clipboardContent = await handle.jsonValue() - expect(clipboardContent).toEqual(expected) - } - }) + test('should copy json to clipboard when copy button clicked', async ({ + page, + browserName, + }) => { + await page.getByLabel('Copy').click() + + if ( + !['webkit', 'Desktop Safari', 'Mobile Safari'].includes(browserName) + ) { + const handle = await page.evaluateHandle(() => + navigator.clipboard.readText() + ) + const clipboardContent = await handle.jsonValue() + expect(clipboardContent).toEqual(expected) + } + + await expect(page.getByText('Copied to clipboard!')).toBeVisible() + }) + + test('should compress json when compress button clicked', async ({ + page, + }) => { + await expect(placeholderText.getByText(expected)).toBeVisible() + + await page.getByLabel('Compress').click() + + await expect( + placeholderText.getByText('[{"hello":"world"},{"green":"red"}]') + ).toBeVisible() + + await expect(page.getByText('Compressed')).toBeVisible() + }) + + test('should clear textarea when clear button clicked', async ({ + page, + }) => { + await expect(placeholderText.getByText(expected)).toBeVisible() - test('should prettify unpretty json input when pretty button clicked and input is valid', async ({ - page, - }) => { - const expectedOutput = `[ + await page.getByLabel('Clear').click() + + await expect(placeholderText.getByText(expected)).not.toBeVisible() + + await expect(page.getByText('Cleared')).toBeVisible() + }) + + test('should prettify unpretty json input when pretty button clicked and input is valid', async ({ + page, + }) => { + const expectedOutput = `[ { "hello": "world" }, @@ -60,21 +96,23 @@ test.describe('when a user navigates to the homepage', () => { } ]` - await page - .getByLabel('Type or paste your json here...') - .fill('[ {"hello" : "world"}, { "green": "red"}]') + await page + .getByLabel('Type or paste your json here...') + .fill('[ {"hello" : "world"}, { "green": "red"}]') - await expect(page.getByText('👍')).toBeVisible() + await expect(page.getByText('👍')).toBeVisible() - await page.getByLabel('Pretty').click() + await page.getByLabel('Pretty').click() - await expect( - page - .getByPlaceholder('Type or paste your json here...') - .getByText(expectedOutput) - ).toBeVisible() - await expect(page.getByText('👍')).toBeVisible() - }) + await expect( + page + .getByPlaceholder('Type or paste your json here...') + .getByText(expectedOutput) + ).toBeVisible() + + await expect(page.getByText('👍')).toBeVisible() + }) - test('pretty button is disabled when input is invalid ', () => {}) + test('pretty button is disabled when input is invalid ', () => {}) + }) }) diff --git a/package-lock.json b/package-lock.json index 0d64826..17466fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "@uiw/react-textarea-code-editor": "^3.0.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1" }, "devDependencies": { "@playwright/test": "^1.44.1", @@ -2065,8 +2066,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/debug": { "version": "4.3.5", @@ -2845,6 +2845,14 @@ "url": "/~https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -4231,6 +4239,21 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", diff --git a/package.json b/package.json index 3b6ba36..887f463 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "dependencies": { "@uiw/react-textarea-code-editor": "^3.0.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1" }, "devDependencies": { "@playwright/test": "^1.44.1", diff --git a/src/components/SimpleJsonEditor.tsx b/src/components/SimpleJsonEditor.tsx index 114f0d4..304add2 100644 --- a/src/components/SimpleJsonEditor.tsx +++ b/src/components/SimpleJsonEditor.tsx @@ -1,6 +1,7 @@ import { useCallback, useReducer } from 'react' import { JsonEditor } from '@/components/JsonEditor.tsx' -import { isValidJson, prettifyJson } from '@/utils/json-utils.ts' +import { compressJson, isValidJson, prettifyJson } from '@/utils/json-utils.ts' +import toast, { Toaster } from 'react-hot-toast' enum SimpleJsonEditorStates { INITIAL, @@ -26,7 +27,9 @@ type SimpleJsonEditorState = enum SimpleJsonEditorActions { ON_TYPING, - ON_FORMAT_BUTTON_CLICK, + ON_PRETTY_BUTTON_CLICK, + ON_COMPRESS_BUTTON_CLICK, + ON_CLEAR_BUTTON_CLICK, } type SimpleJsonEditorAction = @@ -35,12 +38,17 @@ type SimpleJsonEditorAction = entry: string } | { - action: SimpleJsonEditorActions.ON_FORMAT_BUTTON_CLICK - value: string + action: SimpleJsonEditorActions.ON_PRETTY_BUTTON_CLICK + } + | { + action: SimpleJsonEditorActions.ON_COMPRESS_BUTTON_CLICK + } + | { + action: SimpleJsonEditorActions.ON_CLEAR_BUTTON_CLICK } const simpleJsonEditorReducer = ( - _: SimpleJsonEditorState, + state: SimpleJsonEditorState, action: SimpleJsonEditorAction ): SimpleJsonEditorState => { switch (action.action) { @@ -60,10 +68,20 @@ const simpleJsonEditorReducer = ( value: action.entry, } } - case SimpleJsonEditorActions.ON_FORMAT_BUTTON_CLICK: + case SimpleJsonEditorActions.ON_PRETTY_BUTTON_CLICK: + return { + state: SimpleJsonEditorStates.VALID, + value: prettifyJson(state.value), + } + case SimpleJsonEditorActions.ON_COMPRESS_BUTTON_CLICK: return { state: SimpleJsonEditorStates.VALID, - value: prettifyJson(action.value), + value: compressJson(state.value), + } + case SimpleJsonEditorActions.ON_CLEAR_BUTTON_CLICK: + return { + state: SimpleJsonEditorStates.INITIAL, + value: '', } } } @@ -89,12 +107,36 @@ export const SimpleJsonEditor = () => { const { color } = getPresentationStyle(state) + const onPrettyButtonClickedHandler = useCallback(() => { + dispatch({ + action: SimpleJsonEditorActions.ON_PRETTY_BUTTON_CLICK, + }) + + toast('Prettified') + }, []) + + const onCompressButtonClickedHandler = useCallback(() => { + dispatch({ + action: SimpleJsonEditorActions.ON_COMPRESS_BUTTON_CLICK, + }) + + toast('Compressed') + }, []) + const onCopyButtonClickedHandler = useCallback(() => { navigator.clipboard.writeText(state.value) - // TODO: show toast? + toast.success('Copied to clipboard!') }, [state]) + const onClearButtonClickedHandler = useCallback(() => { + dispatch({ + action: SimpleJsonEditorActions.ON_CLEAR_BUTTON_CLICK, + }) + + toast('Cleared') + }, []) + return (
{ + +
)} + ) } diff --git a/src/utils/json-utils.ts b/src/utils/json-utils.ts index 172788f..2539008 100644 --- a/src/utils/json-utils.ts +++ b/src/utils/json-utils.ts @@ -7,6 +7,8 @@ export const isValidJson = (rawValue: string): boolean => { } } -export const prettifyJson = (rawValue: string): string => { - return JSON.stringify(JSON.parse(rawValue), null, ' ') -} +export const prettifyJson = (rawValue: string): string => + JSON.stringify(JSON.parse(rawValue), null, ' ') + +export const compressJson = (rawValue: string): string => + JSON.stringify(JSON.parse(rawValue), null, '')