mirror of
https://github.com/ruvnet/RuVector.git
synced 2026-05-24 05:43:58 +00:00
Major additions: - Complete Next.js studio application with 1600+ components - Docker support (Dockerfile.combined, docker-compose.yml) - GCP deployment documentation and benchmarks - SQL benchmark scripts for performance testing - Sentry integration for monitoring - Comprehensive test suite and mocks Studio features: - Dashboard and admin interfaces - Data visualization components - Authentication and user management - API integration with RuVector backend - Static data and public assets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
511 lines
12 KiB
TypeScript
511 lines
12 KiB
TypeScript
import { v4 as _uuidV4 } from 'uuid'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import {
|
|
detectBrowser,
|
|
detectOS,
|
|
formatBytes,
|
|
formatCurrency,
|
|
getDatabaseMajorVersion,
|
|
getDistanceLatLonKM,
|
|
getSemanticVersion,
|
|
getURL,
|
|
isValidHttpUrl,
|
|
makeRandomString,
|
|
minifyJSON,
|
|
pluckObjectFields,
|
|
pluralize,
|
|
prettifyJSON,
|
|
propsAreEqual,
|
|
removeCommentsFromSql,
|
|
removeJSONTrailingComma,
|
|
snakeToCamel,
|
|
tablesToSQL,
|
|
timeout,
|
|
tryParseInt,
|
|
tryParseJson,
|
|
uuidv4,
|
|
} from './helpers'
|
|
|
|
import { copyToClipboard } from 'ui'
|
|
|
|
vi.mock('uuid', () => ({
|
|
v4: vi.fn(() => 'mocked-uuid'),
|
|
}))
|
|
|
|
describe('uuidv4', () => {
|
|
it('calls uuid.v4 and returns the result', () => {
|
|
const result = uuidv4()
|
|
expect(_uuidV4).toHaveBeenCalled()
|
|
expect(result).toBe('mocked-uuid')
|
|
})
|
|
})
|
|
|
|
describe('tryParseJson', () => {
|
|
it('should return the parsed JSON', () => {
|
|
const result = tryParseJson('{"test": "test"}')
|
|
|
|
expect(result).toEqual({ test: 'test' })
|
|
})
|
|
})
|
|
|
|
describe('minifyJSON', () => {
|
|
it('should return the minified JSON', () => {
|
|
const result = minifyJSON('{"test": "test"}')
|
|
|
|
expect(result).toEqual(`{"test":"test"}`)
|
|
})
|
|
})
|
|
|
|
describe('prettifyJSON', () => {
|
|
it('should return the prettified JSON', () => {
|
|
const result = prettifyJSON('{"test": "test"}')
|
|
|
|
expect(result).toEqual(`{
|
|
"test": "test"
|
|
}`)
|
|
})
|
|
})
|
|
|
|
describe('removeJSONTrailingComma', () => {
|
|
it('should return the JSON without a trailing comma', () => {
|
|
const result = removeJSONTrailingComma('{"test":"test",}')
|
|
|
|
expect(result).toEqual('{"test":"test"}')
|
|
})
|
|
})
|
|
|
|
describe('timeout', () => {
|
|
it('resolves after given ms', async () => {
|
|
vi.useFakeTimers()
|
|
const spy = vi.fn()
|
|
|
|
timeout(1000).then(spy)
|
|
|
|
expect(spy).not.toHaveBeenCalled()
|
|
|
|
vi.advanceTimersByTime(1000)
|
|
await vi.runAllTimersAsync()
|
|
|
|
expect(spy).toHaveBeenCalled()
|
|
vi.useRealTimers()
|
|
})
|
|
})
|
|
|
|
describe('getURL', () => {
|
|
it('should return prod url by default', () => {
|
|
const result = getURL()
|
|
|
|
expect(result).toEqual('https://supabase.com/dashboard')
|
|
})
|
|
})
|
|
|
|
describe('makeRandomString', () => {
|
|
it('should return a random string of the given length', () => {
|
|
const result = makeRandomString(10)
|
|
|
|
expect(result).toHaveLength(10)
|
|
})
|
|
})
|
|
|
|
describe('pluckObjectFields', () => {
|
|
it('should return a new object with the specified fields', () => {
|
|
const result = pluckObjectFields({ a: 1, b: 2, c: 3 }, ['a', 'c'])
|
|
|
|
expect(result).toEqual({ a: 1, c: 3 })
|
|
})
|
|
})
|
|
|
|
describe('tryParseInt', () => {
|
|
it('should return the parsed integer', () => {
|
|
const result = tryParseInt('123')
|
|
|
|
expect(result).toEqual(123)
|
|
})
|
|
|
|
it('should return undefined if the string is not a number', () => {
|
|
const result = tryParseInt('not a number')
|
|
|
|
expect(result).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('propsAreEqual', () => {
|
|
it('should return true if the props are equal', () => {
|
|
const result = propsAreEqual({ a: 1, b: 2 }, { a: 1, b: 2 })
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('should return false if the props are not equal', () => {
|
|
const result = propsAreEqual({ a: 1, b: 2 }, { a: 1, b: 3 })
|
|
})
|
|
})
|
|
|
|
describe('formatBytes', () => {
|
|
it('should return the formatted bytes', () => {
|
|
const result = formatBytes(1024)
|
|
|
|
expect(result).toEqual('1 KB')
|
|
})
|
|
|
|
it('should return the formatted bytes in MB', () => {
|
|
const result = formatBytes(1024 * 1024)
|
|
|
|
expect(result).toEqual('1 MB')
|
|
})
|
|
})
|
|
|
|
describe('snakeToCamel', () => {
|
|
it('should convert snake_case to camelCase', () => {
|
|
const result = snakeToCamel('snake_case')
|
|
|
|
expect(result).toEqual('snakeCase')
|
|
})
|
|
})
|
|
|
|
describe('copyToClipboard', () => {
|
|
let writeMock: any
|
|
let writeTextMock: any
|
|
let hasFocusMock: any
|
|
|
|
beforeEach(() => {
|
|
writeMock = vi.fn().mockResolvedValue(undefined)
|
|
writeTextMock = vi.fn().mockResolvedValue(undefined)
|
|
hasFocusMock = vi.fn().mockReturnValue(true)
|
|
|
|
vi.stubGlobal('navigator', {
|
|
clipboard: {
|
|
write: writeMock,
|
|
writeText: writeTextMock,
|
|
},
|
|
})
|
|
|
|
vi.stubGlobal('window', {
|
|
document: {
|
|
hasFocus: hasFocusMock,
|
|
},
|
|
})
|
|
|
|
// CopyToClipboard uses setTimeout to call the callback
|
|
vi.useFakeTimers()
|
|
|
|
// If ClipboardItem is used
|
|
vi.stubGlobal('ClipboardItem', function (items: any) {
|
|
return items
|
|
})
|
|
|
|
// Prevent toast errors
|
|
vi.stubGlobal('toast', { error: vi.fn() })
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('uses clipboard.write if available', async () => {
|
|
await copyToClipboard('hello')
|
|
vi.runAllTimers()
|
|
expect(writeMock).toHaveBeenCalled()
|
|
})
|
|
|
|
it('falls back to writeText if clipboard.write not available', async () => {
|
|
;(navigator.clipboard as any).write = undefined
|
|
await copyToClipboard('hello')
|
|
expect(writeTextMock).toHaveBeenCalledWith('hello')
|
|
})
|
|
})
|
|
|
|
describe('detectBrowser', () => {
|
|
const originalNavigator = global.navigator
|
|
|
|
const setUserAgent = (ua: string) => {
|
|
vi.stubGlobal('navigator', { userAgent: ua })
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
global.navigator = originalNavigator
|
|
})
|
|
|
|
it('detects Chrome', () => {
|
|
setUserAgent('Mozilla/5.0 Chrome/90.0.0.0 Safari/537.36')
|
|
expect(detectBrowser()).toBe('Chrome')
|
|
})
|
|
|
|
it('detects Firefox', () => {
|
|
setUserAgent('Mozilla/5.0 Firefox/88.0')
|
|
expect(detectBrowser()).toBe('Firefox')
|
|
})
|
|
|
|
it('detects Safari', () => {
|
|
setUserAgent('Mozilla/5.0 Version/14.0 Safari/605.1.15')
|
|
expect(detectBrowser()).toBe('Safari')
|
|
})
|
|
|
|
it('returns undefined when navigator is not defined', () => {
|
|
vi.stubGlobal('navigator', undefined)
|
|
expect(detectBrowser()).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('detectOS', () => {
|
|
const mockUserAgent = (ua: string) => {
|
|
vi.stubGlobal('window', {
|
|
navigator: { userAgent: ua },
|
|
})
|
|
vi.stubGlobal('navigator', { userAgent: ua }) // some code may use both
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
it('detects macOS', () => {
|
|
mockUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)')
|
|
expect(detectOS()).toBe('macos')
|
|
})
|
|
|
|
it('detects Windows', () => {
|
|
mockUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
|
|
expect(detectOS()).toBe('windows')
|
|
})
|
|
|
|
it('returns undefined for unknown OS', () => {
|
|
mockUserAgent('Mozilla/5.0 (X11; Linux x86_64)')
|
|
expect(detectOS()).toBeUndefined()
|
|
})
|
|
|
|
it('returns undefined if window is undefined', () => {
|
|
vi.stubGlobal('window', undefined)
|
|
expect(detectOS()).toBeUndefined()
|
|
})
|
|
|
|
it('returns undefined if navigator is undefined', () => {
|
|
vi.stubGlobal('window', {})
|
|
vi.stubGlobal('navigator', undefined)
|
|
expect(detectOS()).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('pluralize', () => {
|
|
it('should return the pluralized word', () => {
|
|
const result = pluralize(2, 'test', 'tests')
|
|
|
|
expect(result).toEqual('tests')
|
|
})
|
|
})
|
|
|
|
describe('isValidHttpUrl', () => {
|
|
it('should return true if the URL is valid', () => {
|
|
const result = isValidHttpUrl('https://supabase.com')
|
|
|
|
expect(result).toBe(true)
|
|
})
|
|
|
|
it('should return false if the URL is not valid', () => {
|
|
const result = isValidHttpUrl('not a url')
|
|
|
|
expect(result).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('removeCommentsFromSql', () => {
|
|
it('should remove comments from SQL', () => {
|
|
const result = removeCommentsFromSql(`-- This is a comment
|
|
SELECT * FROM users
|
|
`)
|
|
|
|
expect(result).toEqual(`
|
|
SELECT * FROM users
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('getSemanticVersion', () => {
|
|
it('should return the semantic version', () => {
|
|
const result = getSemanticVersion('supabase-postgres-14.1.0.88')
|
|
|
|
expect(result).toEqual(141088)
|
|
})
|
|
})
|
|
|
|
describe('getDatabaseMajorVersion', () => {
|
|
it('should return the database major version', () => {
|
|
const result = getDatabaseMajorVersion('supabase-postgres-14.1.0.88')
|
|
|
|
expect(result).toEqual(14)
|
|
})
|
|
})
|
|
|
|
describe('getDistanceLatLonKM', () => {
|
|
it('should return the distance in kilometers', () => {
|
|
const result = getDistanceLatLonKM(37.774929, -122.419418, 37.774929, -122.419418)
|
|
|
|
expect(result).toEqual(0)
|
|
})
|
|
})
|
|
|
|
describe('formatCurrency', () => {
|
|
it('should return the formatted currency', () => {
|
|
const result = formatCurrency(1000)
|
|
|
|
expect(result).toEqual('$1,000.00')
|
|
})
|
|
|
|
it('should return the formatted currency with small values', () => {
|
|
const result = formatCurrency(0.001)
|
|
|
|
expect(result).toEqual('$0')
|
|
})
|
|
|
|
it('should return null if the value is undefined', () => {
|
|
const result = formatCurrency(undefined)
|
|
|
|
expect(result).toEqual(null)
|
|
})
|
|
})
|
|
|
|
describe('tablesToSQL', () => {
|
|
it('should return warning message for empty array', () => {
|
|
const result = tablesToSQL([])
|
|
|
|
expect(result).toContain('-- WARNING: This schema is for context only')
|
|
})
|
|
|
|
it('should return empty string for non-array input', () => {
|
|
const result = tablesToSQL(null as any)
|
|
|
|
expect(result).toBe('')
|
|
})
|
|
|
|
it('should generate SQL for a simple table', () => {
|
|
const mockTables = [
|
|
{
|
|
name: 'users',
|
|
schema: 'public',
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
data_type: 'integer',
|
|
is_nullable: false,
|
|
is_identity: true,
|
|
default_value: null,
|
|
is_unique: false,
|
|
check: null,
|
|
},
|
|
{
|
|
name: 'name',
|
|
data_type: 'text',
|
|
is_nullable: false,
|
|
is_identity: false,
|
|
default_value: null,
|
|
is_unique: false,
|
|
check: null,
|
|
},
|
|
],
|
|
primary_keys: [{ name: 'id' }],
|
|
relationships: [],
|
|
},
|
|
] as any
|
|
|
|
const result = tablesToSQL(mockTables)
|
|
|
|
expect(result).toContain('-- WARNING: This schema is for context only')
|
|
expect(result).toContain('CREATE TABLE public.users (')
|
|
expect(result).toContain('id integer GENERATED ALWAYS AS IDENTITY NOT NULL')
|
|
expect(result).toContain('name text NOT NULL')
|
|
expect(result).toContain('CONSTRAINT users_pkey PRIMARY KEY (id)')
|
|
})
|
|
|
|
it('should handle tables with various column properties', () => {
|
|
const mockTables = [
|
|
{
|
|
name: 'products',
|
|
schema: 'public',
|
|
columns: [
|
|
{
|
|
name: 'id',
|
|
data_type: 'uuid',
|
|
is_nullable: false,
|
|
is_identity: false,
|
|
default_value: 'gen_random_uuid()',
|
|
is_unique: true,
|
|
check: null,
|
|
},
|
|
{
|
|
name: 'price',
|
|
data_type: 'numeric',
|
|
is_nullable: true,
|
|
is_identity: false,
|
|
default_value: '0.00',
|
|
is_unique: false,
|
|
check: 'price >= 0',
|
|
},
|
|
],
|
|
primary_keys: [],
|
|
relationships: [],
|
|
},
|
|
] as any
|
|
|
|
const result = tablesToSQL(mockTables)
|
|
|
|
expect(result).toContain('id uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE')
|
|
expect(result).toContain('price numeric DEFAULT 0.00 CHECK (price >= 0)')
|
|
})
|
|
|
|
it('should handle foreign key relationships', () => {
|
|
const mockTables = [
|
|
{
|
|
name: 'orders',
|
|
schema: 'public',
|
|
columns: [
|
|
{
|
|
name: 'user_id',
|
|
data_type: 'integer',
|
|
is_nullable: false,
|
|
is_identity: false,
|
|
default_value: null,
|
|
is_unique: false,
|
|
check: null,
|
|
},
|
|
],
|
|
primary_keys: [],
|
|
relationships: [
|
|
{
|
|
constraint_name: 'fk_orders_user_id',
|
|
source_table_name: 'orders',
|
|
source_column_name: 'user_id',
|
|
target_table_schema: 'public',
|
|
target_table_name: 'users',
|
|
target_column_name: 'id',
|
|
},
|
|
],
|
|
},
|
|
] as any
|
|
|
|
const result = tablesToSQL(mockTables)
|
|
|
|
expect(result).toContain(
|
|
'CONSTRAINT fk_orders_user_id FOREIGN KEY (user_id) REFERENCES public.users(id)'
|
|
)
|
|
})
|
|
|
|
it('should handle tables with no columns', () => {
|
|
const mockTables = [
|
|
{
|
|
name: 'empty_table',
|
|
schema: 'public',
|
|
columns: null,
|
|
primary_keys: [],
|
|
relationships: [],
|
|
},
|
|
] as any
|
|
|
|
const result = tablesToSQL(mockTables)
|
|
|
|
expect(result).toContain('-- WARNING: This schema is for context only')
|
|
expect(result).not.toContain('CREATE TABLE')
|
|
})
|
|
})
|