tldraw
Version:
A tiny little drawing editor.
257 lines (222 loc) • 6.73 kB
text/typescript
import { TLBookmarkShape, createShapeId } from '@tldraw/editor'
import { vi } from 'vitest'
import { createBookmarkFromUrl, getHumanReadableAddress } from '../lib/shapes/bookmark/bookmarks'
import { TestEditor } from './TestEditor'
let editor: TestEditor
beforeEach(() => {
editor = new TestEditor()
})
afterEach(() => {
editor?.dispose()
})
describe('The URL formatter', () => {
it('Formats URLs as human-readable', () => {
const ids = {
a: createShapeId(),
b: createShapeId(),
c: createShapeId(),
d: createShapeId(),
e: createShapeId(),
f: createShapeId(),
}
editor.createShapes([
{
id: ids.a,
type: 'bookmark',
props: {
url: 'https://www.github.com',
},
},
{
id: ids.b,
type: 'bookmark',
props: {
url: 'https://www.github.com/',
},
},
{
id: ids.c,
type: 'bookmark',
props: {
url: 'https://www.github.com/TodePond',
},
},
{
id: ids.d,
type: 'bookmark',
props: {
url: 'https://www.github.com/TodePond/',
},
},
{
id: ids.e,
type: 'bookmark',
props: {
url: 'https://www.github.com//',
},
},
{
id: ids.f,
type: 'bookmark',
props: {
url: 'https://www.github.com/TodePond/DreamBerd//',
},
},
])
const a = editor.getShape<TLBookmarkShape>(ids.a)!
const b = editor.getShape<TLBookmarkShape>(ids.b)!
const c = editor.getShape<TLBookmarkShape>(ids.c)!
const d = editor.getShape<TLBookmarkShape>(ids.d)!
const e = editor.getShape<TLBookmarkShape>(ids.e)!
const f = editor.getShape<TLBookmarkShape>(ids.f)!
expect(getHumanReadableAddress(a.props.url)).toBe('github.com')
expect(getHumanReadableAddress(b.props.url)).toBe('github.com')
expect(getHumanReadableAddress(c.props.url)).toBe('github.com')
expect(getHumanReadableAddress(d.props.url)).toBe('github.com')
expect(getHumanReadableAddress(e.props.url)).toBe('github.com')
expect(getHumanReadableAddress(f.props.url)).toBe('github.com')
})
it("Doesn't resize bookmarks", () => {
const ids = {
bookmark: createShapeId(),
boxA: createShapeId(),
boxB: createShapeId(),
}
editor.createShapes([
{
id: ids.bookmark,
type: 'bookmark',
props: {
url: 'https://www.github.com/TodePond',
},
},
{
type: 'geo',
id: ids.boxA,
x: 0,
y: 0,
props: {
w: 10,
h: 10,
},
},
{
type: 'geo',
id: ids.boxB,
x: 20,
y: 20,
props: {
w: 10,
h: 10,
},
},
])
const oldBookmark = editor.getShape(ids.bookmark) as TLBookmarkShape
expect(oldBookmark.props.w).toBe(300)
expect(oldBookmark.props.h).toBe(320)
editor.select(ids.bookmark, ids.boxA, ids.boxB)
editor.pointerDown(20, 20, { target: 'selection', handle: 'bottom_right' })
editor.pointerMove(30, 30)
const newBookmark = editor.getShape(ids.bookmark) as TLBookmarkShape
expect(newBookmark.props.w).toBe(300)
expect(newBookmark.props.h).toBe(320)
})
})
describe('createBookmarkFromUrl', () => {
it('creates a bookmark shape with unfurled metadata', async () => {
const url = 'https://example.com'
const center = { x: 100, y: 200 }
// Mock the asset creation to return a test asset
const mockAsset = {
id: 'asset:test-asset-id' as any,
typeName: 'asset' as const,
type: 'bookmark' as const,
props: {
src: url,
title: 'Example Site',
description: 'An example website',
image: 'https://example.com/image.jpg',
favicon: 'https://example.com/favicon.ico',
},
meta: {},
}
// Mock the getAssetForExternalContent method
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(mockAsset)
const result = await createBookmarkFromUrl(editor, { url, center })
assert(result.ok, 'Failed to create bookmark')
const shape = result.value
expect(shape.type).toBe('bookmark')
expect(shape.props.url).toBe(url)
expect(shape.props.assetId).toBe('asset:test-asset-id')
expect(shape.props.w).toBe(300)
expect(shape.props.h).toBe(320)
expect(shape.x).toBe(center.x - 150) // BOOKMARK_WIDTH / 2
expect(shape.y).toBe(center.y - 160) // BOOKMARK_HEIGHT / 2
// Verify the shape was created in the editor
const createdShape = editor.getShape(result.value.id)
expect(createdShape).toBeDefined()
expect(createdShape?.type).toBe('bookmark')
// Verify the asset was created
const createdAsset = editor.getAsset('asset:test-asset-id' as any)
expect(createdAsset).toBeDefined()
expect(createdAsset?.type).toBe('bookmark')
})
it('creates a bookmark shape with default center when no center provided', async () => {
const url = 'https://example.com'
const viewportCenter = { x: 500, y: 300 }
// Mock getViewportPageBounds to return a known center
vi.spyOn(editor, 'getViewportPageBounds').mockReturnValue({
x: 0,
y: 0,
w: 1000,
h: 600,
center: viewportCenter,
} as any)
const mockAsset = {
id: 'asset:test-asset-id' as any,
typeName: 'asset' as const,
type: 'bookmark' as const,
props: {
src: url,
title: 'Example Site',
description: 'An example website',
image: '',
favicon: '',
},
meta: {},
}
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(mockAsset)
const result = await createBookmarkFromUrl(editor, { url })
assert(result.ok, 'Failed to create bookmark')
const shape = result.value
expect(shape.x).toBe(viewportCenter.x - 150)
expect(shape.y).toBe(viewportCenter.y - 160)
})
it('handles asset creation failure gracefully', async () => {
const url = 'https://invalid-url.com'
const center = { x: 100, y: 200 }
// Mock the asset creation to fail
vi.spyOn(editor, 'getAssetForExternalContent').mockRejectedValue(new Error('Failed to fetch'))
const result = await createBookmarkFromUrl(editor, { url, center })
assert(!result.ok, 'Failed to create bookmark')
expect(result.error).toBe('Failed to fetch')
// Verify no shape was created
const shapes = editor.getCurrentPageShapes()
expect(shapes).toHaveLength(0)
})
it('creates bookmark shape even when asset creation returns null', async () => {
const url = 'https://example.com'
const center = { x: 100, y: 200 }
// Mock the asset creation to return null
vi.spyOn(editor, 'getAssetForExternalContent').mockResolvedValue(null as any)
const result = await createBookmarkFromUrl(editor, { url, center })
assert(result.ok, 'Failed to create bookmark')
const shape = result.value
expect(shape.type).toBe('bookmark')
expect(shape.props.url).toBe(url)
expect(shape.props.assetId).toBe(null)
// Verify the shape was created
const createdShape = editor.getShape(result.value.id)
expect(createdShape).toBeDefined()
})
})