Skip to content

Commit 017a469

Browse files
committed
add tests, fix small issues
1 parent 29e5e24 commit 017a469

13 files changed

Lines changed: 2667 additions & 94 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { describe, expect, it } from 'vitest'
2+
import * as Y from 'yjs'
3+
import { createTestContext } from '../../_test/setup.helper'
4+
import { asDm, setupCampaignContext } from '../../_test/identities.helper'
5+
import { api } from '../../_generated/api'
6+
7+
describe('createNote YJS integration', () => {
8+
const t = createTestContext()
9+
10+
it('creates a yjsUpdates row when creating a note', async () => {
11+
const ctx = await setupCampaignContext(t)
12+
const dmAuth = asDm(ctx)
13+
14+
const { noteId } = await dmAuth.mutation(api.notes.mutations.createNote, {
15+
campaignId: ctx.campaignId,
16+
name: 'Test Note',
17+
parentId: null,
18+
})
19+
20+
const updates = await t.run(async (dbCtx) => {
21+
return await dbCtx.db
22+
.query('yjsUpdates')
23+
.withIndex('by_document_seq', (q) => q.eq('documentId', noteId))
24+
.collect()
25+
})
26+
27+
expect(updates).toHaveLength(1)
28+
expect(updates[0].seq).toBe(0)
29+
expect(updates[0].isSnapshot).toBe(true)
30+
})
31+
32+
it('creates valid YJS document for note without content', async () => {
33+
const ctx = await setupCampaignContext(t)
34+
const dmAuth = asDm(ctx)
35+
36+
const { noteId } = await dmAuth.mutation(api.notes.mutations.createNote, {
37+
campaignId: ctx.campaignId,
38+
name: 'Empty Note',
39+
parentId: null,
40+
})
41+
42+
const updates = await t.run(async (dbCtx) => {
43+
return await dbCtx.db
44+
.query('yjsUpdates')
45+
.withIndex('by_document_seq', (q) => q.eq('documentId', noteId))
46+
.collect()
47+
})
48+
49+
expect(updates).toHaveLength(1)
50+
51+
const doc = new Y.Doc()
52+
Y.applyUpdate(doc, new Uint8Array(updates[0].update))
53+
54+
const fragment = doc.getXmlFragment('document')
55+
expect(fragment).toBeDefined()
56+
57+
doc.destroy()
58+
})
59+
})
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { createTestContext } from '../../_test/setup.helper'
3+
import { asDm, setupCampaignContext } from '../../_test/identities.helper'
4+
import { createFolder } from '../../_test/factories.helper'
5+
import { api } from '../../_generated/api'
6+
7+
describe('hard delete YJS cleanup', () => {
8+
const t = createTestContext()
9+
10+
it('hard-deleting a note removes its yjsUpdates', async () => {
11+
const ctx = await setupCampaignContext(t)
12+
const dmAuth = asDm(ctx)
13+
14+
const { noteId } = await dmAuth.mutation(api.notes.mutations.createNote, {
15+
campaignId: ctx.campaignId,
16+
name: 'Doomed Note',
17+
parentId: null,
18+
})
19+
20+
const updatesBefore = await t.run(async (dbCtx) => {
21+
return await dbCtx.db
22+
.query('yjsUpdates')
23+
.withIndex('by_document_seq', (q) => q.eq('documentId', noteId))
24+
.collect()
25+
})
26+
expect(updatesBefore).toHaveLength(1)
27+
28+
await dmAuth.mutation(api.sidebarItems.mutations.moveSidebarItem, {
29+
itemId: noteId,
30+
location: 'trash',
31+
})
32+
33+
await dmAuth.mutation(api.sidebarItems.mutations.emptyTrashBin, {
34+
campaignId: ctx.campaignId,
35+
})
36+
37+
const updatesAfter = await t.run(async (dbCtx) => {
38+
return await dbCtx.db
39+
.query('yjsUpdates')
40+
.withIndex('by_document_seq', (q) => q.eq('documentId', noteId))
41+
.collect()
42+
})
43+
expect(updatesAfter).toHaveLength(0)
44+
})
45+
46+
it('hard-deleting a note removes its yjsAwareness entries', async () => {
47+
const ctx = await setupCampaignContext(t)
48+
const dmAuth = asDm(ctx)
49+
50+
const { noteId } = await dmAuth.mutation(api.notes.mutations.createNote, {
51+
campaignId: ctx.campaignId,
52+
name: 'Awareness Note',
53+
parentId: null,
54+
})
55+
56+
await dmAuth.mutation(api.yjsSync.mutations.pushAwareness, {
57+
documentId: noteId,
58+
clientId: 42,
59+
state: new ArrayBuffer(4),
60+
})
61+
62+
const awarenessBefore = await t.run(async (dbCtx) => {
63+
return await dbCtx.db
64+
.query('yjsAwareness')
65+
.withIndex('by_document', (q) => q.eq('documentId', noteId))
66+
.collect()
67+
})
68+
expect(awarenessBefore).toHaveLength(1)
69+
70+
await dmAuth.mutation(api.sidebarItems.mutations.moveSidebarItem, {
71+
itemId: noteId,
72+
location: 'trash',
73+
})
74+
75+
await dmAuth.mutation(api.sidebarItems.mutations.emptyTrashBin, {
76+
campaignId: ctx.campaignId,
77+
})
78+
79+
const awarenessAfter = await t.run(async (dbCtx) => {
80+
return await dbCtx.db
81+
.query('yjsAwareness')
82+
.withIndex('by_document', (q) => q.eq('documentId', noteId))
83+
.collect()
84+
})
85+
expect(awarenessAfter).toHaveLength(0)
86+
})
87+
88+
it('hard-deleting a folder cascades YJS cleanup for contained notes', async () => {
89+
const ctx = await setupCampaignContext(t)
90+
const dmAuth = asDm(ctx)
91+
92+
const { folderId } = await createFolder(
93+
t,
94+
ctx.campaignId,
95+
ctx.dm.profile._id,
96+
)
97+
98+
const { noteId } = await dmAuth.mutation(api.notes.mutations.createNote, {
99+
campaignId: ctx.campaignId,
100+
name: 'Nested Note',
101+
parentId: folderId,
102+
})
103+
104+
const updatesBefore = await t.run(async (dbCtx) => {
105+
return await dbCtx.db
106+
.query('yjsUpdates')
107+
.withIndex('by_document_seq', (q) => q.eq('documentId', noteId))
108+
.collect()
109+
})
110+
expect(updatesBefore).toHaveLength(1)
111+
112+
await dmAuth.mutation(api.sidebarItems.mutations.moveSidebarItem, {
113+
itemId: folderId,
114+
location: 'trash',
115+
})
116+
117+
await dmAuth.mutation(api.sidebarItems.mutations.emptyTrashBin, {
118+
campaignId: ctx.campaignId,
119+
})
120+
121+
const updatesAfter = await t.run(async (dbCtx) => {
122+
return await dbCtx.db
123+
.query('yjsUpdates')
124+
.withIndex('by_document_seq', (q) => q.eq('documentId', noteId))
125+
.collect()
126+
})
127+
expect(updatesAfter).toHaveLength(0)
128+
})
129+
})

0 commit comments

Comments
 (0)