Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions plugins/interface/components/primitives.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { renderToString } from 'hono/jsx/dom/server'
import { describe, expect, it } from 'vitest'

import { Avatar } from './avatar'
import { Card } from './card'
import { Input } from './input/Input'
import { Label } from './label/Label'
import { Loader } from './loader/Loader'
import { Toggle } from './toggle'

describe('interface primitive components', () => {
it('renders avatar links with fallback initials and custom classes', () => {
const html = renderToString(
<Avatar as="a" href="/profile" username="outerbase" class="extra" />
)

expect(html).toContain('<a')
expect(html).toContain('href="/profile"')
expect(html).toContain('extra')
expect(html).toContain('>O</p>')
})

it('renders avatar images with accessible alt text and selected state', () => {
const html = renderToString(
<Avatar
image="/avatar.png"
toggled
username="Ada"
data-testid="avatar"
/>
)

expect(html).toContain('<button')
expect(html).toContain('after:opacity-100')
expect(html).toContain('src="/avatar.png"')
expect(html).toContain('alt="Ada"')
expect(html).toContain('data-testid="avatar"')
})

it('renders cards as links or divs with variant classes', () => {
const link = renderToString(
<Card as="a" href="/docs" variant="primary">
Docs
</Card>
)
const panel = renderToString(
<Card variant="secondary" data-testid="card">
Panel
</Card>
)

expect(link).toContain('<a')
expect(link).toContain('href="/docs"')
expect(link).toContain('btn-primary')
expect(link).toContain('Docs')

expect(panel).toContain('<div')
expect(panel).toContain('btn-secondary')
expect(panel).toContain('data-testid="card"')
})

it('renders labels with validation messaging only when invalid', () => {
const invalid = renderToString(
<Label
title="Database"
required
requiredDescription="Required"
isValid={false}
>
<input />
</Label>
)
const valid = renderToString(
<Label
title="Database"
required
requiredDescription="Required"
isValid
/>
)

expect(invalid).toContain('Database')
expect(invalid).toContain('*')
expect(invalid).toContain('Required')
expect(valid).not.toContain('Required')
})

it('renders loader and toggle sizing/state classes', () => {
const loader = renderToString(<Loader size={18} class="spin" />)
const toggle = renderToString(
<Toggle onClick={() => undefined} size="lg" toggled />
)

expect(loader).toContain('class="spin"')
expect(loader).toContain('style="height: 18px; width: 18px"')
expect(toggle).toContain('h-7.5 w-12.5')
expect(toggle).toContain('translate-x-full')
})

it('renders input wrappers with prefix, suffix, and invalid state', () => {
const wrapped = renderToString(
<Input
initialValue="abc"
isValid={false}
onValueChange={() => undefined}
placeholder="Filter"
preText="$"
postText="USD"
size="sm"
/>
)
const plain = renderToString(
<Input
className="extra-input"
initialValue="plain"
onValueChange={() => undefined}
size="lg"
/>
)

expect(wrapped).toContain('<div')
expect(wrapped).toContain('ob-size-sm')
expect(wrapped).toContain('>$</span>')
expect(wrapped).toContain('>USD</span>')
expect(wrapped).toContain('placeholder="Filter"')
expect(wrapped).toContain('text-ob-destructive')

expect(plain).toContain('<input')
expect(plain).toContain('extra-input')
expect(plain).toContain('ob-size-lg')
expect(plain).toContain('value="plain"')
})
})
47 changes: 47 additions & 0 deletions plugins/interface/pages/template/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

const hydrateRoot = vi.hoisted(() => vi.fn())

vi.mock('hono/jsx/dom/client', () => ({
hydrateRoot,
}))

vi.mock('../../public/global.css', () => ({}))

describe('template page entrypoint', () => {
beforeEach(() => {
vi.resetModules()
hydrateRoot.mockClear()
})

afterEach(() => {
vi.unstubAllGlobals()
})

it('does not hydrate when the template root is missing', async () => {
const querySelector = vi.fn(() => null)
vi.stubGlobal('document', { querySelector })

await import('./index')

expect(querySelector).toHaveBeenCalledWith(
'#root[data-client="template"]'
)
expect(hydrateRoot).not.toHaveBeenCalled()
})

it('hydrates the template page when the server root is present', async () => {
const root = {
dataset: {
serverProps: '{}',
},
}
const querySelector = vi.fn(() => root)
vi.stubGlobal('document', { querySelector })

await import('./index')

expect(hydrateRoot).toHaveBeenCalledTimes(1)
expect(hydrateRoot).toHaveBeenCalledWith(root, expect.any(Object))
})
})
54 changes: 54 additions & 0 deletions src/public-entrypoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it, vi } from 'vitest'

import { ChangeDataCapturePlugin } from '../plugins/cdc'
import { ClerkPlugin } from '../plugins/clerk'
import { QueryLogPlugin } from '../plugins/query-log'
import { ResendPlugin } from '../plugins/resend'
import { SqlMacrosPlugin } from '../plugins/sql-macros'
import { StripeSubscriptionPlugin } from '../plugins/stripe'
import { StudioPlugin } from '../plugins/studio'
import { WebSocketPlugin } from '../plugins/websocket'
import * as publicApi from '../dist'
import * as pluginApi from '../dist/plugins'
import { StarbaseDBDurableObject } from './do'
import { StarbaseDB } from './handler'

vi.mock('cloudflare:workers', () => {
return {
DurableObject: class MockDurableObject {},
}
})

describe('public package entrypoints', () => {
it('exposes runtime APIs from the root package export', () => {
expect(publicApi.StarbaseDB).toBe(StarbaseDB)
expect(publicApi.StarbaseDBDurableObject).toBe(StarbaseDBDurableObject)
expect(Object.keys(publicApi).sort()).toEqual([
'StarbaseDB',
'StarbaseDBDurableObject',
])
})

it('exposes documented plugin constructors from the plugin export', () => {
expect(pluginApi.StudioPlugin).toBe(StudioPlugin)
expect(pluginApi.WebSocketPlugin).toBe(WebSocketPlugin)
expect(pluginApi.SqlMacrosPlugin).toBe(SqlMacrosPlugin)
expect(pluginApi.StripeSubscriptionPlugin).toBe(
StripeSubscriptionPlugin
)
expect(pluginApi.ChangeDataCapturePlugin).toBe(ChangeDataCapturePlugin)
expect(pluginApi.QueryLogPlugin).toBe(QueryLogPlugin)
expect(pluginApi.ResendPlugin).toBe(ResendPlugin)
expect(pluginApi.ClerkPlugin).toBe(ClerkPlugin)
expect(Object.keys(pluginApi).sort()).toEqual([
'ChangeDataCapturePlugin',
'ClerkPlugin',
'QueryLogPlugin',
'ResendPlugin',
'SqlMacrosPlugin',
'StripeSubscriptionPlugin',
'StudioPlugin',
'WebSocketPlugin',
])
})
})