@tldraw/sync-core
Version:
tldraw infinite canvas SDK (multiplayer sync).
170 lines (147 loc) • 4.61 kB
text/typescript
import { assert } from 'tldraw'
import { JsonChunkAssembler, chunk } from '../lib/chunk'
describe('chunk', () => {
it('chunks a string', () => {
expect(chunk('hello there my good world', 5)).toMatchInlineSnapshot(`
[
"8_h",
"7_ell",
"6_o t",
"5_her",
"4_e m",
"3_y g",
"2_ood",
"1_ wo",
"0_rld",
]
`)
expect(chunk('hello there my good world', 10)).toMatchInlineSnapshot(`
[
"3_h",
"2_ello the",
"1_re my go",
"0_od world",
]
`)
})
it('does not chunk the string if it is small enough', () => {
const chunks = chunk('hello', 100)
expect(chunks).toMatchInlineSnapshot(`
[
"hello",
]
`)
})
it('makes sure the chunk length does not exceed the given message size', () => {
const chunks = chunk('dark and stormy tonight', 4)
expect(chunks).toMatchInlineSnapshot(`
[
"12_d",
"11_a",
"10_r",
"9_k ",
"8_an",
"7_d ",
"6_st",
"5_or",
"4_my",
"3_ t",
"2_on",
"1_ig",
"0_ht",
]
`)
})
it('does its best if the chunk size is too small', () => {
const chunks = chunk('once upon a time', 1)
expect(chunks).toMatchInlineSnapshot(`
[
"15_o",
"14_n",
"13_c",
"12_e",
"11_ ",
"10_u",
"9_p",
"8_o",
"7_n",
"6_ ",
"5_a",
"4_ ",
"3_t",
"2_i",
"1_m",
"0_e",
]
`)
})
})
const testObject = {} as any
for (let i = 0; i < 1000; i++) {
testObject['key_' + i] = 'value_' + i
}
describe('json unchunker', () => {
it.each([1, 5, 20, 200])('unchunks a json string split at %s bytes', (size) => {
const chunks = chunk(JSON.stringify(testObject), size)
const unchunker = new JsonChunkAssembler()
for (const chunk of chunks.slice(0, -1)) {
const result = unchunker.handleMessage(chunk)
expect(result).toBeNull()
}
expect(unchunker.handleMessage(chunks[chunks.length - 1])).toMatchObject({ data: testObject })
// and the next one should be fine
expect(unchunker.handleMessage('{"ok": true}')).toMatchObject({ data: { ok: true } })
})
// todo: test error cases
it('returns an error if the json is whack', () => {
const chunks = chunk('{"hello": world"}', 5)
const unchunker = new JsonChunkAssembler()
for (const chunk of chunks.slice(0, -1)) {
const result = unchunker.handleMessage(chunk)
expect(result).toBeNull()
}
const node18Error = `Unexpected token w in JSON at position 10`
const node20Error = `Unexpected token 'w', "\\{"hello": world"}" is not valid JSON`
const res = unchunker.handleMessage(chunks[chunks.length - 1])
assert(res, 'expected a result')
assert('error' in res, 'expected an error')
expect(res.error?.message).toMatch(new RegExp(`${node18Error}|${node20Error}`))
// and the next one should be fine
expect(unchunker.handleMessage('{"ok": true}')).toMatchObject({ data: { ok: true } })
})
it('returns an error if one of the chunks was missing', () => {
const chunks = chunk('{"hello": world"}', 10)
expect(chunks).toHaveLength(3)
const unchunker = new JsonChunkAssembler()
expect(unchunker.handleMessage(chunks[0])).toBeNull()
const res = unchunker.handleMessage(chunks[2])
assert(res, 'expected a result')
assert('error' in res, 'expected an error')
expect(res.error?.message).toMatchInlineSnapshot(`"Chunks received in wrong order"`)
// and the next one should be fine
expect(unchunker.handleMessage('{"ok": true}')).toMatchObject({ data: { ok: true } })
})
it('returns an error if the chunk stream ends abruptly', () => {
const chunks = chunk('{"hello": world"}', 10)
expect(chunks).toHaveLength(3)
const unchunker = new JsonChunkAssembler()
expect(unchunker.handleMessage(chunks[0])).toBeNull()
expect(unchunker.handleMessage(chunks[1])).toBeNull()
const res = unchunker.handleMessage('{"hello": "world"}')
assert(res, 'expected a result')
assert('error' in res, 'expected an error')
expect(res?.error?.message).toMatchInlineSnapshot(`"Unexpected non-chunk message"`)
// and the next one should be fine
expect(unchunker.handleMessage('{"ok": true}')).toMatchObject({ data: { ok: true } })
})
it('returns an error if the chunk syntax is wrong', () => {
// it only likes json objects
const unchunker = new JsonChunkAssembler()
const res = unchunker.handleMessage('["yo"]')
assert(res, 'expected a result')
assert('error' in res, 'expected an error')
expect(res.error?.message).toMatchInlineSnapshot(`"Invalid chunk: "[\\"yo\\"]...""`)
// and the next one should be fine
expect(unchunker.handleMessage('{"ok": true}')).toMatchObject({ data: { ok: true } })
})
})