diff --git a/.gitignore b/.gitignore
index 6a8271a..2611925 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ dist
dist-ssr
*.local
_*
+!__tests__
**/worker/*
pnpm-lock.yaml
\ No newline at end of file
diff --git a/jest.config.ts b/jest.config.ts
new file mode 100644
index 0000000..42752d5
--- /dev/null
+++ b/jest.config.ts
@@ -0,0 +1,15 @@
+import type { Config } from '@jest/types'
+
+const config: Config.InitialOptions = {
+ testMatch: ['**/*.spec.[jt]s?(x)'],
+ transform: {
+ '^.+\\.tsx?$': [
+ 'esbuild-jest',
+ {
+ sourcemap: true,
+ },
+ ],
+ },
+}
+
+export default config
diff --git a/src/__tests__/utils/html.spec.ts b/src/__tests__/utils/html.spec.ts
new file mode 100644
index 0000000..2669abd
--- /dev/null
+++ b/src/__tests__/utils/html.spec.ts
@@ -0,0 +1,15 @@
+import { renderPreloadLinks } from '../../utils/html'
+
+describe('renderPreloadLinks', () => {
+ test('should render .js and .css files', () => {
+ const links = renderPreloadLinks(['foo.js', 'bar.css'])
+ expect(links).toMatchInlineSnapshot(
+ `""`
+ )
+ })
+
+ test('should return empty string if not .js or .css file', () => {
+ const links = renderPreloadLinks(['foo.svg'])
+ expect(links.length).toEqual(0)
+ })
+})
diff --git a/src/__tests__/utils/route.spec.ts b/src/__tests__/utils/route.spec.ts
new file mode 100644
index 0000000..271924e
--- /dev/null
+++ b/src/__tests__/utils/route.spec.ts
@@ -0,0 +1,85 @@
+import {
+ withPrefix,
+ withoutPrefix,
+ withSuffix,
+ withoutSuffix,
+ createUrl,
+ joinPaths,
+ getFullPath,
+} from '../../utils/route'
+
+describe('withPrefix', () => {
+ test('should return original string', () => {
+ expect(withPrefix('/foo/bar', '/foo')).toEqual('/foo/bar')
+ })
+
+ test('should return prefixed string', () => {
+ expect(withPrefix('/bar/baz', '/foo')).toEqual('/foo/bar/baz')
+ })
+})
+
+describe('withoutPrefix', () => {
+ test('should return original string', () => {
+ expect(withoutPrefix('/foo/bar', '/baz')).toEqual('/foo/bar')
+ })
+
+ test('should return string without prefix', () => {
+ expect(withoutPrefix('/foo/bar', '/foo')).toEqual('/bar')
+ })
+})
+
+describe('withSuffix', () => {
+ test('should return original string', () => {
+ expect(withSuffix('/foo/bar', '/bar')).toEqual('/foo/bar')
+ })
+
+ test('should return string with suffix', () => {
+ expect(withSuffix('/foo/bar', '/baz')).toEqual('/foo/bar/baz')
+ })
+})
+
+describe('withoutSuffix', () => {
+ test('should return string with suffix', () => {
+ expect(withoutSuffix('/foo/bar', '/baz')).toEqual('/foo/bar/baz')
+ })
+
+ test('should return string without suffix', () => {
+ expect(withoutSuffix('/foo/bar', '/bar')).toEqual('/foo')
+ })
+})
+
+describe('createUrl', () => {
+ const url = 'https://www.foo.com'
+
+ test('should return URL instance', () => {
+ expect(createUrl(url)).toBeInstanceOf(URL)
+ })
+
+ test('should return original URL', () => {
+ expect(createUrl(url)).toEqual(new URL(url))
+ expect(createUrl(new URL(url))).toEqual(new URL(url))
+ })
+
+ test('should return example URL if passed URL is not valid', () => {
+ expect(createUrl('invalid-url')).toEqual(new URL(`http://e.g/invalid-url`))
+ })
+})
+
+describe('joinPaths', () => {
+ test('should join paths to string', () => {
+ const path = joinPaths('foo', '//bar', '/baz', '/boo')
+ expect(path).toEqual('foo/bar/baz/boo')
+ })
+})
+
+describe('getFullPath', () => {
+ test('should return full path', () => {
+ const path = getFullPath('https://foo.com/some/path')
+ expect(path).toEqual('/some/path')
+ })
+
+ test('should return path without base', () => {
+ const path = getFullPath('https://foo.com/some/path', '/some')
+ expect(path).toEqual('/path')
+ })
+})
diff --git a/src/__tests__/utils/state.spec.ts b/src/__tests__/utils/state.spec.ts
new file mode 100644
index 0000000..0d5b964
--- /dev/null
+++ b/src/__tests__/utils/state.spec.ts
@@ -0,0 +1,17 @@
+import { deserializeState, serializeState } from '../../utils/state'
+
+describe('state:', () => {
+ const state = { foo: 'baz', bar: 5, boo: [], bee: {} }
+ const serializedState = serializeState(state)
+
+ test('serializeState', () => {
+ expect(serializedState).toMatchInlineSnapshot(
+ `"\\"{\\\\\\"foo\\\\\\":\\\\\\"baz\\\\\\",\\\\\\"bar\\\\\\":5,\\\\\\"boo\\\\\\":[],\\\\\\"bee\\\\\\":{}}\\""`
+ )
+ })
+
+ test('deserializeState', () => {
+ // @ TODO
+ //expect(deserializeState(serializedState)).toEqual(state)
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index 7b4fa45..48aa086 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1018,9 +1018,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.793:
- version "1.3.793"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.793.tgz#c10dff5f3126238004de344db458f1da3641d554"
- integrity sha512-l9NrGV6Mr4ov5mayYPvIWcwklNw5ROmy6rllzz9dCACw9nKE5y+s5uQk+CBJMetxrWZ6QJFsvEfG6WDcH2IGUg==
+ version "1.3.795"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.795.tgz#91f09b4c77f8dab562dd592ce929b009201c24ff"
+ integrity sha512-4TPxrLf9Fzsi4rVgTlDm+ubxoXm3/TN67/LGHx/a4UkVubKILa6L26O6eTnHewixG/knzU9L3lLmfL39eElwlQ==
emojis-list@^3.0.0:
version "3.0.0"