diff --git a/.gitignore b/.gitignore index 4f80759..93a6164 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,3 @@ yarn-error.log* next-env.d.ts .vscode - -firebase.config.ts \ No newline at end of file diff --git a/firebase.config.ts b/firebase.config.ts new file mode 100644 index 0000000..4b6c547 --- /dev/null +++ b/firebase.config.ts @@ -0,0 +1,11 @@ +const firebaseKeys = { + NEXT_PUBLIC_FIREBASE_API_KEY: 'test', + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: 'test', + NEXT_PUBLIC_FIREBASE_PROJECT_ID: 'test', + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: 'test', + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: 'test', + NEXT_PUBLIC_FIREBASE_APP_ID: 'test', + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: 'test', +}; + +export default firebaseKeys; diff --git a/jest.config.mjs b/jest.config.mjs index 127f855..c3a4f4a 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -11,6 +11,11 @@ const config = { coverageReporters: ['text', 'lcov', 'clover'], collectCoverageFrom: ['src/**/*.ts', 'src/**/*.tsx'], coverageDirectory: path.join(process.cwd(), 'coverage'), + coveragePathIgnorePatterns: [ + 'src/test/setup.ts', + 'lib/store', + 'src/pages/_document.tsx', + ], }; export default createJestConfig(config); diff --git a/src/images/favicon.ico b/public/favicon.ico similarity index 100% rename from src/images/favicon.ico rename to public/favicon.ico diff --git a/src/components/EndpointEditor/EndpointEditor.tsx b/src/components/EndpointEditor/EndpointEditor.tsx index d271746..55a8f02 100644 --- a/src/components/EndpointEditor/EndpointEditor.tsx +++ b/src/components/EndpointEditor/EndpointEditor.tsx @@ -14,7 +14,10 @@ const EndpointEditor = () => { const text = textContent[lang as keyof TextContentType].dashboard; return ( -
+
{!isEditing ? (
{inputValue} @@ -24,9 +27,14 @@ const EndpointEditor = () => { className="pl-2 my-5 w-full h-[40px] bg-white rounded text-black outline-none" value={inputValue} onChange={(e) => dispatch(setUrl(e.target.value))} + data-testid="input" /> )} -
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index b019cdf..3a93449 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -10,8 +10,8 @@ import { textContent, TextContentType } from '@/lib/langText'; import Image from 'next/image'; import homeSvg from '@/images/home.svg'; import exitSvg from '@/images/exit.svg'; -import signIn from '@/images/signin.svg' -import signUp from '@/images/signup.svg' +import signIn from '@/images/signin.svg'; +import signUp from '@/images/signup.svg'; const Header = () => { const [isScrolled, setIsScrolled] = useState(false); diff --git a/src/components/JSONViewerButtons/JSONViewerButtons.tsx b/src/components/JSONViewerButtons/JSONViewerButtons.tsx index 789f40d..a9b7c44 100644 --- a/src/components/JSONViewerButtons/JSONViewerButtons.tsx +++ b/src/components/JSONViewerButtons/JSONViewerButtons.tsx @@ -1,7 +1,7 @@ import playImage from '@/images/play.svg'; import Image from 'next/image'; import broomImage from '@/images/broom.svg'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { fetchData } from '@/lib/store/slices'; import { AppDispatch } from '@/lib/store/store'; import { prettifyText } from '@/lib/utils'; diff --git a/src/components/QueryEditor/index.tsx b/src/components/QueryEditor/index.tsx index e024764..e98a343 100644 --- a/src/components/QueryEditor/index.tsx +++ b/src/components/QueryEditor/index.tsx @@ -6,23 +6,23 @@ import { setQuery } from '@/lib/store/slices'; import { dracula } from '@uiw/codemirror-theme-dracula'; const QueryEditor = () => { - const query = useSelector((state: RootState) => state.data.query); - const dispatch = useDispatch(); + const query = useSelector((state: RootState) => state.data.query); + const dispatch = useDispatch(); - const editorChangeHandler = (value: string) => { - dispatch(setQuery(value)); - }; + const editorChangeHandler = (value: string) => { + dispatch(setQuery(value)); + }; return ( - ) -} + value={query} + theme={dracula} + extensions={[javascript({ jsx: true })]} + width="100%" + height="100%" + className="max-h-[100%] w-full min-h-[100%] h-full" + onChange={editorChangeHandler} + /> + ); +}; -export default QueryEditor \ No newline at end of file +export default QueryEditor; diff --git a/src/components/ResponseViewier/index.tsx b/src/components/ResponseViewier/index.tsx index 81ce2b0..7086b7f 100644 --- a/src/components/ResponseViewier/index.tsx +++ b/src/components/ResponseViewier/index.tsx @@ -5,25 +5,25 @@ import CodeMirror from '@uiw/react-codemirror'; import { useSelector } from 'react-redux'; const ResponseViewier = () => { - const data = useSelector((state: RootState) => state.data.data); - const error = useSelector((state: RootState) => state.data.error); + const data = useSelector((state: RootState) => state.data.data); + const error = useSelector((state: RootState) => state.data.error); return ( - ) -} + value={ + JSON.stringify(data, null, 2) !== '{}' + ? JSON.stringify(data, null, 2) + : String(error) === null + ? '' + : String(error) + } + theme={dracula} + extensions={[javascript({ jsx: true })]} + width="100%" + height="100%" + className="max-h-[100%] w-full min-w-[100%] min-h-[100%] h-full" + readOnly + /> + ); +}; -export default ResponseViewier \ No newline at end of file +export default ResponseViewier; diff --git a/src/lib/store/slices.ts b/src/lib/store/slices.ts index 31d99ef..e815801 100644 --- a/src/lib/store/slices.ts +++ b/src/lib/store/slices.ts @@ -156,7 +156,6 @@ const dataSlice = createSlice({ }); builder.addCase(fetchSchema.rejected, (state) => { - state.schemaLoading = false; }); }, diff --git a/src/pages/layout.tsx b/src/pages/layout.tsx index d5085fa..d3bd3d8 100644 --- a/src/pages/layout.tsx +++ b/src/pages/layout.tsx @@ -18,7 +18,7 @@ const Layout = ({ children }: { children: ReactNode }) => { }, []); return ( -
+
GraphQl Sandbox diff --git a/src/pages/main/index.tsx b/src/pages/main/index.tsx index 3654b65..4bb26e9 100644 --- a/src/pages/main/index.tsx +++ b/src/pages/main/index.tsx @@ -21,199 +21,226 @@ import upArrow from '@/images/up-arrow.svg'; import downArrow from '@/images/down-arrow.svg'; const Main = () => { - const [isDocsOpen, setDocsOpen] = useState(false); - const [isUrlOpen, setUrlOpen] = useState(false); - - const schema = useSelector((state: RootState) => state.data.schema); - const schemaLoading = useSelector( - (state: RootState) => state.data.schemaLoading - ); - - const docsOpenHandler = () => { - if(schema === null){ - dispatch(fetchSchema()); - } - console.log(schema) - if (!isUrlOpen) { - setDocsOpen(!isDocsOpen); - } else { - setUrlOpen(false); - setDocsOpen(true); - } - }; - - const urlOpenHandler = () => { - if (!isDocsOpen) { - setUrlOpen(!isUrlOpen); - } else { - setDocsOpen(false); - setUrlOpen(true); - } - }; - - - const [isEditorOpen, setIsEditorOpen] = useState(false); - const [isVariablesOpen, setVariablesOpen] = useState(false); - const [isHeadersOpen, setHeadersOpen] = useState(false); - - const router = useRouter(); - - const [user, loading] = useAuthState(auth); - - - - const variables = useSelector((state: RootState) => state.data.variables); - const headers = useSelector((state: RootState) => state.data.headers); - - const apiUrl = useSelector((state: RootState) => state.data.apiUrl); - - - const dispatch = useDispatch(); - - - const propertyEditorChangeHandler = (value: string) => { - isVariablesOpen - ? dispatch(setVariables(value)) - : dispatch(setHeaders(value)); - }; - - useEffect(() => { - dispatch(fetchSchema()); - if (!loading && !user) { - router.push('/'); - } - }, [user, loading, router, apiUrl]); - - const propertyButtonHandler = () => { - setIsEditorOpen(!isEditorOpen); - if(isEditorOpen) { - setVariablesOpen(false); - setHeadersOpen(false) ; - } - }; - - const variablesButtonHandler = () => { - if (!isEditorOpen) { - setIsEditorOpen(true); - } - setHeadersOpen(false); - setVariablesOpen(true); - }; - - const headersButtonHandler = () => { - if (!isEditorOpen) { - setIsEditorOpen(true); - } - setHeadersOpen(true); - setVariablesOpen(false); - }; + const [isDocsOpen, setDocsOpen] = useState(false); + const [isUrlOpen, setUrlOpen] = useState(false); + + const schema = useSelector((state: RootState) => state.data.schema); + const schemaLoading = useSelector( + (state: RootState) => state.data.schemaLoading + ); + + const docsOpenHandler = () => { + if (schema === null) { + dispatch(fetchSchema()); + } + console.log(schema); + if (!isUrlOpen) { + setDocsOpen(!isDocsOpen); + } else { + setUrlOpen(false); + setDocsOpen(true); + } + }; + + const urlOpenHandler = () => { + if (!isDocsOpen) { + setUrlOpen(!isUrlOpen); + } else { + setDocsOpen(false); + setUrlOpen(true); + } + }; + + const [isEditorOpen, setIsEditorOpen] = useState(false); + const [isVariablesOpen, setVariablesOpen] = useState(false); + const [isHeadersOpen, setHeadersOpen] = useState(false); + + const router = useRouter(); + + const [user, loading] = useAuthState(auth); + + const variables = useSelector((state: RootState) => state.data.variables); + const headers = useSelector((state: RootState) => state.data.headers); + + const apiUrl = useSelector((state: RootState) => state.data.apiUrl); + + const dispatch = useDispatch(); + + const propertyEditorChangeHandler = (value: string) => { + isVariablesOpen + ? dispatch(setVariables(value)) + : dispatch(setHeaders(value)); + }; + + useEffect(() => { + dispatch(fetchSchema()); + if (!loading && !user) { + router.push('/'); + } + }, [user, loading, router, apiUrl]); + + const propertyButtonHandler = () => { + setIsEditorOpen(!isEditorOpen); + if (isEditorOpen) { + setVariablesOpen(false); + setHeadersOpen(false); + } + }; + + const variablesButtonHandler = () => { + if (!isEditorOpen) { + setIsEditorOpen(true); + } + setHeadersOpen(false); + setVariablesOpen(true); + }; + + const headersButtonHandler = () => { + if (!isEditorOpen) { + setIsEditorOpen(true); + } + setHeadersOpen(true); + setVariablesOpen(false); + }; return ( -
- -
-
- - -
-
- - -
- - {isDocsOpen?
- - {schemaLoading ? ( -
Loading...
- ) : ( - - )} -
:null} - {isUrlOpen?
-

URL

-
- -
-
: null} -
-
- -
-
-
- - -
- -
-
- -
-
-
-
-
-
+ className="flex flex-col self-stretch items-stretch grow h-[calc(100vh-160px)]" + data-testid="main" + > +
+
+
+
+ + {isDocsOpen ? ( +
+ {schemaLoading ? ( +
Loading...
+ ) : ( + + )} +
+ ) : null} + {isUrlOpen ? ( +
+

URL

+
+ +
+
+ ) : null} +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+
+
- {isEditorOpen && ( -
- -
- )} -
+ +
+ {isEditorOpen && ( +
+ +
+ )} +
- ) -} + ); +}; -export default Main \ No newline at end of file +export default Main; diff --git a/src/styles/globals.css b/src/styles/globals.css index 1989de4..291e487 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2,7 +2,6 @@ @tailwind components; @tailwind utilities; - @layer base { :root { --background: 0 0% 100%; @@ -50,7 +49,6 @@ } } - @layer base { * { @apply border-border; @@ -61,15 +59,15 @@ } @layer components { #__next { - @apply h-full bg-red-500; + @apply h-full bg-red-500; } html, body { - @apply h-full; + @apply h-full; } } input { - width: 100%; - min-width: 100%; } \ No newline at end of file + min-width: 100%; +} diff --git a/src/test/EndpointEditor.test.tsx b/src/test/EndpointEditor.test.tsx deleted file mode 100644 index 90d4080..0000000 --- a/src/test/EndpointEditor.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ - -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import EndpointEditor from '@/components/EndpointEditor/EndpointEditor'; -import store from '@/lib/store/store'; -import { Provider } from 'react-redux'; - -describe('Endpoint editor tests: ', () => { - test('Footer exists', () => { - render( - - - - ); - const endpointEditor = screen.getByTestId('endpoint'); - expect(endpointEditor).toBeInTheDocument(); - }); - - test('Footer renders correctly', () => { - render( - - ); - const button = screen.getAllByTestId('button'); - expect(button).toBeInTheDocument; - }); -}); diff --git a/src/test/endpoint.test.tsx b/src/test/endpoint.test.tsx new file mode 100644 index 0000000..83a8959 --- /dev/null +++ b/src/test/endpoint.test.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import EndpointEditor from '@/components/EndpointEditor/EndpointEditor'; +import store from '@/lib/store/store'; +import '@testing-library/jest-dom'; + +describe('EndpointEditor', () => { + test('renders endpoint editor correctly', () => { + render( + + + + ); + + const button = screen.getByTestId('button'); + fireEvent.click(button); + const input = screen.getByTestId('input'); + expect(input).toBeInTheDocument(); + }); + + test('saves changes correctly', () => { + render( + + + + ); + + const editButton = screen.getByTestId('button'); + fireEvent.click(editButton); + + const input = screen.getByTestId('input'); + fireEvent.change(input, { target: { value: 'https://test.test' } }); + + const saveButton = screen.getByTestId('button'); + fireEvent.click(saveButton); + + expect(store.getState().data.apiUrl).toBe('https://test.test'); + + const editedInput = screen.queryByTestId('input'); + expect(editedInput).toBeNull(); + }); +}); diff --git a/src/test/errorboundary.test.tsx b/src/test/errorboundary.test.tsx new file mode 100644 index 0000000..e60cf88 --- /dev/null +++ b/src/test/errorboundary.test.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import ErrorBoundary from '@/components/ErrorBoundary/ErrorBoundary'; +import '@testing-library/jest-dom'; + +const ErrorThrowingComponent = ({ error }: { error: Error }) => { + throw error; +}; + +describe('ErrorBoundary', () => { + test('renders children when there is no error', () => { + render( + +
Test Child
+
+ ); + + expect(screen.getByText('Test Child')).toBeInTheDocument(); + }); + + test('renders error message and reload button on error', () => { + const error = new Error('Test error'); + + render( + + + + ); + + expect( + screen.getByText('Oops... something went wrong...') + ).toBeInTheDocument(); + expect(screen.getByText('Reload app')).toBeInTheDocument(); + }); +}); diff --git a/src/test/main.test.tsx b/src/test/main.test.tsx index 9034cdf..bf5a347 100644 --- a/src/test/main.test.tsx +++ b/src/test/main.test.tsx @@ -1,5 +1,4 @@ - -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import Main from '@/pages/main'; import store from '@/lib/store/store'; @@ -9,22 +8,37 @@ jest.mock('next/router', () => require('next-router-mock')); describe('Main tests: ', () => { test('Main exists', () => { render( - - -
- - - ); + +
+ + ); const main = screen.getByTestId('main'); expect(main).toBeInTheDocument(); }); test('Main renders correctly', () => { - render( - + render( +
- ); + + ); const sections = screen.getAllByTestId('section'); expect(sections).toHaveLength(4); }); + + test('Toggle Docs and URL buttons', () => { + render( + +
+ + ); + + const docsButton = screen.getByTestId('docs-button'); + const urlButton = screen.getByTestId('url-button'); + + fireEvent.click(docsButton); + expect(screen.getByTestId('docs-container')).toBeInTheDocument(); + fireEvent.click(urlButton); + expect(screen.getByText('URL')).toBeInTheDocument(); + }); }); diff --git a/src/test/prettifier.test.tsx b/src/test/prettifier.test.tsx index 343e63a..876df32 100644 --- a/src/test/prettifier.test.tsx +++ b/src/test/prettifier.test.tsx @@ -1,5 +1,4 @@ -import { prettifyText } from "@/lib/utils"; - +import { prettifyText } from '@/lib/utils'; const text1 = '{hgfjhg : { fjhgkdh: ghjhgj } ,djfhg :fhgkdj}'; const text2 = `{ @@ -9,7 +8,7 @@ const text2 = `{ djfhg: fhgkdj }`; const text3 = `{ - field(arg: "value") { + field(arg: "value"){ subField } }`;