Skip to content

Commit

Permalink
feat: add tests (#25)
Browse files Browse the repository at this point in the history
* feat: add tests

* ci: add test job

* feat: add elements tests

* feat: add fonts tests

* fix: nextjs build
  • Loading branch information
QuiiBz authored Dec 29, 2023
1 parent c3be099 commit 76b2725
Show file tree
Hide file tree
Showing 20 changed files with 6,091 additions and 50 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,20 @@ jobs:
run: pnpm install
- name: Typecheck
run: pnpm typecheck

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- name: Use Node.js 20
uses: actions/setup-node@v3
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Test
run: pnpm test
10 changes: 10 additions & 0 deletions apps/dashboard/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ const nextConfig = {
ignoreBuildErrors: true,
},
reactStrictMode: false,
webpack: (config, { webpack }) => {
// define plugin
config.plugins.push(
new webpack.DefinePlugin({
'process.env.VITEST_POOL_ID': false,
})
)

return config
}
}

module.exports = nextConfig
7 changes: 5 additions & 2 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"test": "vitest"
},
"dependencies": {
"@dnd-kit/core": "^6.1.0",
Expand All @@ -30,7 +31,9 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"happy-dom": "^12.10.3",
"postcss": "^8",
"tailwindcss": "^3.3.0"
"tailwindcss": "^3.3.0",
"vitest": "^1.1.0"
}
}
2 changes: 1 addition & 1 deletion apps/dashboard/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function RootLayout({
<html lang="en">
<body className={inter.className}>
{children}
<Toaster />
<Toaster closeButton richColors />
</body>
</html>
)
Expand Down
81 changes: 59 additions & 22 deletions apps/dashboard/src/components/LeftPanel/ExportSection.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,91 @@
import { useState } from "react";
import { flushSync } from "react-dom";
import { toast } from "sonner";
import { Button } from "../forms/Button";
import { PngIcon } from "../icons/PngIcon";
import { SvgIcon } from "../icons/SvgIcon";
import { useOg } from "../OgEditor";
import { domToReactLike, exportToPng, exportToSvg } from "../../lib/export";
import { domToReactElements, exportToPng, exportToSvg } from "../../lib/export";
import type { FontData } from "../../lib/fonts";
import { loadFonts } from "../../lib/fonts";

export function ExportSection() {
const { rootRef, elements, setSelectedElement } = useOg()
const [isLoading, setIsLoading] = useState(false)

async function exportSvg(openInNewTab = true) {
if (!rootRef.current) {
return {}
}

async function exportSvg(showProgress = true) {
flushSync(() => {
setSelectedElement(null)
})

setIsLoading(true)
async function run() {
setIsLoading(true)

const reactLike = domToReactLike(rootRef.current, 'This is a dynamic text')
const fonts = await loadFonts(elements)
const svg = await exportToSvg(reactLike, fonts)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- rootRef is always defined
const reactElements = domToReactElements(rootRef.current!, 'This is a dynamic text')
const fonts = await loadFonts(elements)
const svg = await exportToSvg(reactElements, fonts)

setIsLoading(false)
setIsLoading(false)

if (openInNewTab) {
const blob = new Blob([svg], { type: 'image/svg+xml' })
const url = URL.createObjectURL(blob)
window.open(url)
return { fonts, svg }
}

return { fonts, svg }
return new Promise<{
fonts: FontData[];
svg: string;
}>((resolve, reject) => {
if (showProgress) {
let svg: string

toast.promise(run(), {
loading: 'Exporting to SVG...',
success: (data) => {
resolve(data)
svg = data.svg

return 'SVG exported!'
},
action: {
label: 'Download',
onClick: () => {
const blob = new Blob([svg], { type: 'image/svg+xml' })
const url = URL.createObjectURL(blob)
window.open(url)
}
}
});
}

run().then(resolve).catch(reject)
})
}

async function exportPng() {
const { svg, fonts } = await exportSvg(false)

setIsLoading(true)
let png: Uint8Array

const png = await exportToPng(svg ?? '', fonts?.map(font => new Uint8Array(font.data)) ?? [])
async function run() {
setIsLoading(true)

setIsLoading(false)
png = await exportToPng(svg, fonts.map(font => new Uint8Array(font.data)))

setIsLoading(false)
}

const blob = new Blob([png], { type: 'image/png' })
const url = URL.createObjectURL(blob)
window.open(url)
toast.promise(run(), {
loading: 'Exporting to PNG...',
success: 'PNG exported!',
action: {
label: 'Download',
onClick: () => {
const blob = new Blob([png], { type: 'image/png' })
const url = URL.createObjectURL(blob)
window.open(url)
}
}
});
}

return (
Expand Down
7 changes: 4 additions & 3 deletions apps/dashboard/src/components/OgImage.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import usePromise from "react-promise-suspense"
import { createElementStyle } from "../lib/elements"
import type { ReactElements } from "../lib/export";
import { exportToSvg } from "../lib/export"
import { loadFonts } from "../lib/fonts"
import type { OGElement } from "../lib/types"

async function loadOgImage(elements: OGElement[]) {
const fonts = await loadFonts(elements)
const reactLike = {
const reactElements: ReactElements = {
type: 'div',
props: {
style: {
Expand All @@ -18,13 +19,13 @@ async function loadOgImage(elements: OGElement[]) {
type: element.tag,
props: {
style: createElementStyle(element),
...(element.tag === 'p' ? { children: element.content } : {}),
...(element.tag === 'p' ? { children: [element.content] } : {}),
},
}))
}
}

const svg = await exportToSvg(reactLike, fonts)
const svg = await exportToSvg(reactElements, fonts)
return `data:image/svg+xml;base64,${btoa(svg)}`
}

Expand Down
18 changes: 16 additions & 2 deletions apps/dashboard/src/components/OgSplash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { MouseEvent, ReactNode } from "react";
import { Suspense, useEffect, useState } from "react";
import Link from "next/link";
import { useRouter, useSearchParams } from "next/navigation";
import { toast } from "sonner";
import { INITIAL_ELEMENTS, createElementId } from "../lib/elements";
import type { OGElement } from "../lib/types";
import type { Template } from "../lib/templates";
Expand Down Expand Up @@ -91,7 +92,7 @@ export function OgSplash({ route }: OgSplashProps) {
if (push) {
router.push(`/?i=${id}`)
} else {
setOgImages([{ id: key, content: template.elements }, ...ogImages])
setOgImages(images => [{ id: key, content: template.elements }, ...images])
}
}
}
Expand All @@ -104,6 +105,7 @@ export function OgSplash({ route }: OgSplashProps) {
event.stopPropagation();

copy()
toast('Image duplicated!')
}
}

Expand All @@ -113,7 +115,19 @@ export function OgSplash({ route }: OgSplashProps) {
event.stopPropagation();

localStorage.removeItem(ogImage.id)
setOgImages(ogImages.filter(({ id }) => id !== ogImage.id))
setOgImages(images => images.filter(({ id }) => id !== ogImage.id))

toast('Image deleted!', {
action: {
label: 'Undo',
onClick: () => {
localStorage.setItem(ogImage.id, JSON.stringify(ogImage.content))
setOgImages(images => [ogImage, ...images])

toast('Image restored!')
}
},
})
}
}

Expand Down
Loading

0 comments on commit 76b2725

Please sign in to comment.