File size: 4,534 Bytes
6f91ce5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// The server's schema knows the custom shapes and bindings (T01), so their records sync and
// rooms saved before them still open. Run with: node --test server/schema.test.ts
import { NodeSqliteWrapper, SQLiteSyncStorage, TLSocketRoom } from '@tldraw/sync-core'
import {
	createTLSchema,
	defaultBindingSchemas,
	defaultShapeSchemas,
	type TLRecord,
	type TLSchema,
	type TLShape,
} from '@tldraw/tlschema'
import type { IndexKey } from '@tldraw/utils'
import assert from 'node:assert/strict'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { DatabaseSync } from 'node:sqlite'
import { after, test } from 'node:test'
import { customBindingSchemas, NOTE_PIN_TYPE } from '../shared/bindings.ts'
import { paperShapeId } from '../shared/ids.ts'
import { customShapeSchemas, defaultPaperCardProps } from '../shared/shapes.ts'

// built exactly as server.ts builds it
const schema = createTLSchema({
	shapes: { ...defaultShapeSchemas, ...customShapeSchemas },
	bindings: { ...defaultBindingSchemas, ...customBindingSchemas },
})

const dir = mkdtempSync(join(tmpdir(), 'schema-test-'))
after(() => rmSync(dir, { recursive: true, force: true }))

const PAGE = 'page:page' as TLShape['parentId']
const PAPER = paperShapeId('pmid:34290406')

function paperCard(props: Record<string, unknown> = {}) {
	return schema.types.shape.create({
		id: PAPER,
		type: 'paper-card',
		parentId: PAGE,
		index: 'a1' as IndexKey,
		meta: { createdBy: 'agent', onBehalfOf: 'm_abcdefghij', runId: null, paperId: 'pmid:34290406' },
		props: defaultPaperCardProps('pmid:34290406', {
			title: 'Some title',
			authors: ['Ana Ibáñez-Molero', 'B. Smith'],
			year: 2021,
			type: 'article',
			typeRaw: 'journal-article',
			scores: { 'new-findings': { score: 0.82, fit: 'high', why: 'Same cells, new readout' } },
			figure: { imageUrl: 'https://example.org/f1.jpg', label: 'Fig. 1', caption: 'UMAP', captionByAi: false },
			quotes: [{ id: 'q1', text: 'We profiled…', source: 'abstract', verified: true, url: 'https://x#:~:text=We' }],
			claims: [{ text: 'new readout', evidence: ['q1', 'seeds'] }],
			glance: [{ label: 'Samples', text: '48 tumours', quoteId: 'q1', relation: 'same' }],
			provenance: { oneLine: { model: 'nvidia/Nemotron-3.5-Super-VL', at: 1760000000000 } },
			...props,
		} as never),
	})
}

test('a paper-card validates', () => {
	const shape = paperCard()
	assert.deepEqual(schema.types.shape.validate(shape), shape)
})

test('a paper-card with a bad fit or type is rejected', () => {
	const badFit = paperCard({ scores: { methods: { score: 0.1, fit: 'great', why: '' } } })
	assert.throws(() => schema.types.shape.validate(badFit), /fit/)
	const badType = paperCard({ type: 'journal-article' })
	assert.throws(() => schema.types.shape.validate(badType), /type/)
	const extra = paperCard({ facets: [] })
	assert.throws(() => schema.types.shape.validate(extra), /facets/)
})

test('a note-pin binding validates', () => {
	const binding = schema.types.binding.create({
		id: 'binding:pin1',
		type: NOTE_PIN_TYPE,
		fromId: 'shape:note1',
		toId: PAPER,
		props: { dx: 60, dy: -40 },
	})
	assert.deepEqual(schema.types.binding.validate(binding), binding)
	assert.throws(() => schema.types.binding.validate({ ...binding, props: { dx: 1 } }), /dy/)
})

function openRoom(file: string, roomSchema: TLSchema) {
	const db = new DatabaseSync(join(dir, file))
	const room = new TLSocketRoom<TLRecord, void>({
		schema: roomSchema,
		storage: new SQLiteSyncStorage<TLRecord>({ sql: new NodeSqliteWrapper(db) }),
	})
	return { room, close: () => (room.close(), db.close()) }
}

test('a room on SQLite opens with this schema and stores a paper-card', () => {
	const { room, close } = openRoom('new.db', schema)
	room.storage.transaction((txn) => txn.set(PAPER, schema.types.shape.validate(paperCard())))
	close()
	const reopened = openRoom('new.db', schema)
	const stored = reopened.room.getRecord(PAPER) as TLShape<'paper-card'>
	assert.equal(stored.props.paperId, 'pmid:34290406')
	reopened.close()
})

test('a room saved before the custom shapes still opens', () => {
	const oldSchema = createTLSchema({ shapes: defaultShapeSchemas, bindings: defaultBindingSchemas })
	const old = openRoom('old.db', oldSchema)
	const page = old.room.getCurrentSnapshot().documents.some((d) => d.state.typeName === 'page')
	assert.ok(page)
	old.close()
	const reopened = openRoom('old.db', schema)
	assert.ok(reopened.room.getCurrentSnapshot().documents.length > 0)
	reopened.close()
})