diff --git a/src/ClientApp.test.tsx b/src/ClientApp.test.tsx new file mode 100644 index 0000000..0291c28 --- /dev/null +++ b/src/ClientApp.test.tsx @@ -0,0 +1,27 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it } from 'vitest'; +import resume from '../resume.json'; +import { ClientApp } from './ClientApp'; + +describe('', () => { + it('renders errors', () => { + render( + + ); + expect(screen.getByText('resume.json is invalid')).toBeInTheDocument(); + expect(screen.getByText(/invalid_string/)).toBeInTheDocument(); + }); + + it('renders resume', () => { + render(); + expect(screen.getByText('Pässi Villanen')).toBeInTheDocument(); + }); +}); diff --git a/src/ClientApp.tsx b/src/ClientApp.tsx new file mode 100644 index 0000000..442b83d --- /dev/null +++ b/src/ClientApp.tsx @@ -0,0 +1,17 @@ +import { FC, StrictMode } from 'react'; +import { Resume } from './Resume'; +import { Error } from './components/Error'; +import { resumeSchema } from './schema'; + +export const ClientApp: FC<{ resume: unknown }> = ({ resume }) => { + const parsedResume = resumeSchema.safeParse(resume); + return ( + + {parsedResume.success ? ( + + ) : ( + + )} + + ); +}; diff --git a/src/Resume.tsx b/src/Resume.tsx index a8e7588..d7fa5ef 100644 --- a/src/Resume.tsx +++ b/src/Resume.tsx @@ -7,7 +7,6 @@ import { Projects } from './components/Projects'; import { SkillsAndLanguages } from './components/SkillsAndLanguages'; import { Work } from './components/Work'; import { ResumeSchema } from './schema'; -import { GlobalStyle } from './styles/GlobalStyle'; interface Props { resume: ResumeSchema; @@ -16,7 +15,6 @@ interface Props { export const Resume: FC = ({ resume }) => { return ( -
diff --git a/src/client.tsx b/src/client.tsx index 5e86ff0..05a082d 100644 --- a/src/client.tsx +++ b/src/client.tsx @@ -1,18 +1,7 @@ -import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; import resume from '../resume.json'; -import { Resume } from './Resume'; -import { resumeSchema } from './schema'; - -const App = () => { - const parsedResume = resumeSchema.parse(resume); - return ( - - - - ); -}; +import { ClientApp } from './ClientApp'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - + ); diff --git a/src/components/Error.tsx b/src/components/Error.tsx new file mode 100644 index 0000000..c1e336f --- /dev/null +++ b/src/components/Error.tsx @@ -0,0 +1,36 @@ +import { ZodError } from 'zod'; +import { Layout } from './Layout'; +import styled from 'styled-components'; +import { FC } from 'react'; + +export const Error: FC<{ errors: ZodError }> = ({ errors }) => { + return ( + +

resume.json is invalid

+ + {errors.errors.map((error) => ( + +

{error.message}

+
+ {error.code} at{' '} + {error.path.map((error) => `"${error}"`).join(' > ')} +
+
+ ))} +
+
+ ); +}; + +const List = styled.ul` + list-style: none; + padding: 0; + margin: 0; +`; + +const ListItem = styled.li` + margin-top: 48px; + div { + margin-left: 16px; + } +`; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 35da7a5..496af55 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,8 +1,14 @@ import { FC, PropsWithChildren } from 'react'; import styled from 'styled-components'; +import { GlobalStyle } from '../styles/GlobalStyle'; export const Layout: FC = ({ children }) => { - return {children}; + return ( + + + {children} + + ); }; const Container = styled.div` diff --git a/vitest.config.ts b/vitest.config.ts index a0d89ad..b6f485d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ plugins: [react()], test: { globals: true, - exclude: [...configDefaults.exclude], + exclude: [...configDefaults.exclude, 'index.js'], environment: 'jsdom', setupFiles: './test/setup.ts', coverage: { @@ -16,6 +16,7 @@ export default defineConfig({ '**/*.test.*', '**/*/coverage', '**/*/client.tsx', + 'index.js', ], provider: 'c8', },