fixtures: Yjs wire fixtures generated by real JS yjs
These are the contract for the Rust core. Never edited to make a test pass; a deliberate rule change regenerates them in the same commit. The unicode fixture is the one that matters most: Yjs indexes UTF-16 code units and Rust indexes UTF-8 bytes, so an index-conversion bug shows there and nowhere else.
12 files changed,
+137
−0
fixtures/.gitignore+1 −0fixtures/data/concurrent.expected.json+3 −0fixtures/data/concurrent.update.binbinaryfixtures/data/formatted.expected.json+3 −0fixtures/data/formatted.update.binbinaryfixtures/data/hello.expected.json+3 −0fixtures/data/hello.update.binbinaryfixtures/data/unicode.expected.json+3 −0fixtures/data/unicode.update.binbinaryfixtures/generate.mjs+54 −0fixtures/package-lock.json+61 −0fixtures/package.json+9 −0
diff --git a/fixtures/.gitignore b/fixtures/.gitignore| new file mode 100644 |
| index 0000000..c2658d7 |
| --- /dev/null |
| +++ b/fixtures/.gitignore |
| @@ -0,0 +1 @@ |
| +node_modules/ |
diff --git a/fixtures/data/concurrent.expected.json b/fixtures/data/concurrent.expected.json| new file mode 100644 |
| index 0000000..ac35f31 |
| --- /dev/null |
| +++ b/fixtures/data/concurrent.expected.json |
| @@ -0,0 +1,3 @@ |
| +{ |
| + "text": "AAABBB" |
| +} |
diff --git a/fixtures/data/concurrent.update.bin b/fixtures/data/concurrent.update.bin| new file mode 100644 |
| index 0000000..f77be1c |
| Binary files /dev/null and b/fixtures/data/concurrent.update.bin differ |
diff --git a/fixtures/data/formatted.expected.json b/fixtures/data/formatted.expected.json| new file mode 100644 |
| index 0000000..e763dbd |
| --- /dev/null |
| +++ b/fixtures/data/formatted.expected.json |
| @@ -0,0 +1,3 @@ |
| +{ |
| + "text": "plain bold plain" |
| +} |
diff --git a/fixtures/data/formatted.update.bin b/fixtures/data/formatted.update.bin| new file mode 100644 |
| index 0000000..7773e5a |
| Binary files /dev/null and b/fixtures/data/formatted.update.bin differ |
diff --git a/fixtures/data/hello.expected.json b/fixtures/data/hello.expected.json| new file mode 100644 |
| index 0000000..da7d1ad |
| --- /dev/null |
| +++ b/fixtures/data/hello.expected.json |
| @@ -0,0 +1,3 @@ |
| +{ |
| + "text": "hello world" |
| +} |
diff --git a/fixtures/data/hello.update.bin b/fixtures/data/hello.update.bin| new file mode 100644 |
| index 0000000..554e670 |
| Binary files /dev/null and b/fixtures/data/hello.update.bin differ |
diff --git a/fixtures/data/unicode.expected.json b/fixtures/data/unicode.expected.json| new file mode 100644 |
| index 0000000..4320fb0 |
| --- /dev/null |
| +++ b/fixtures/data/unicode.expected.json |
| @@ -0,0 +1,3 @@ |
| +{ |
| + "text": "héllo 🌍 café" |
| +} |
diff --git a/fixtures/data/unicode.update.bin b/fixtures/data/unicode.update.bin| new file mode 100644 |
| index 0000000..f261e97 |
| Binary files /dev/null and b/fixtures/data/unicode.update.bin differ |
diff --git a/fixtures/generate.mjs b/fixtures/generate.mjs| new file mode 100644 |
| index 0000000..a1cc897 |
| --- /dev/null |
| +++ b/fixtures/generate.mjs |
| @@ -0,0 +1,54 @@ |
| +// Generates the Yjs wire fixtures that define compatibility for the Rust |
| +// core. These are the contract: never edit a fixture to make a test pass. |
| +// A deliberate rule change regenerates them in the same commit. |
| +import * as Y from 'yjs' |
| +import { mkdirSync, writeFileSync } from 'node:fs' |
| +import { join, dirname } from 'node:path' |
| +import { fileURLToPath } from 'node:url' |
| + |
| +const dataDir = join(dirname(fileURLToPath(import.meta.url)), 'data') |
| +mkdirSync(dataDir, { recursive: true }) |
| + |
| +function emit(name, build) { |
| + const doc = new Y.Doc() |
| + doc.clientID = 1 |
| + build(doc) |
| + const update = Y.encodeStateAsUpdate(doc) |
| + writeFileSync(join(dataDir, `${name}.update.bin`), Buffer.from(update)) |
| + writeFileSync( |
| + join(dataDir, `${name}.expected.json`), |
| + JSON.stringify({ text: doc.getText('content').toString() }, null, 2) + '\n' |
| + ) |
| + console.log(`${name}: ${update.length} bytes`) |
| +} |
| + |
| +emit('hello', (doc) => { |
| + doc.getText('content').insert(0, 'hello world') |
| +}) |
| + |
| +emit('unicode', (doc) => { |
| + // Surrogate pairs and combining marks: Yjs indexes UTF-16 code units and |
| + // Rust indexes UTF-8 bytes, so this is where an index-conversion bug shows. |
| + doc.getText('content').insert(0, 'héllo 🌍 café') |
| +}) |
| + |
| +emit('formatted', (doc) => { |
| + const t = doc.getText('content') |
| + t.insert(0, 'plain bold plain') |
| + t.format(6, 4, { bold: true }) |
| +}) |
| + |
| +emit('concurrent', (doc) => { |
| + // Two docs diverge and merge. The merged state is order-independent, so |
| + // Rust must arrive at the same bytes from the same inputs. |
| + const a = new Y.Doc() |
| + a.clientID = 1 |
| + const b = new Y.Doc() |
| + b.clientID = 2 |
| + doc.clientID = 3 |
| + a.getText('content').insert(0, 'AAA') |
| + b.getText('content').insert(0, 'BBB') |
| + Y.applyUpdate(a, Y.encodeStateAsUpdate(b)) |
| + Y.applyUpdate(b, Y.encodeStateAsUpdate(a)) |
| + Y.applyUpdate(doc, Y.encodeStateAsUpdate(a)) |
| +}) |
diff --git a/fixtures/package-lock.json b/fixtures/package-lock.json| new file mode 100644 |
| index 0000000..4aaa8ec |
| --- /dev/null |
| +++ b/fixtures/package-lock.json |
| @@ -0,0 +1,61 @@ |
| +{ |
| + "name": "docs-fixtures", |
| + "lockfileVersion": 3, |
| + "requires": true, |
| + "packages": { |
| + "": { |
| + "name": "docs-fixtures", |
| + "dependencies": { |
| + "yjs": "13.6.27" |
| + } |
| + }, |
| + "node_modules/isomorphic.js": { |
| + "version": "0.2.5", |
| + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", |
| + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", |
| + "license": "MIT", |
| + "funding": { |
| + "type": "GitHub Sponsors ❤", |
| + "url": "https://github.com/sponsors/dmonad" |
| + } |
| + }, |
| + "node_modules/lib0": { |
| + "version": "0.2.117", |
| + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", |
| + "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", |
| + "license": "MIT", |
| + "dependencies": { |
| + "isomorphic.js": "^0.2.4" |
| + }, |
| + "bin": { |
| + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", |
| + "0gentesthtml": "bin/gentesthtml.js", |
| + "0serve": "bin/0serve.js" |
| + }, |
| + "engines": { |
| + "node": ">=16" |
| + }, |
| + "funding": { |
| + "type": "GitHub Sponsors ❤", |
| + "url": "https://github.com/sponsors/dmonad" |
| + } |
| + }, |
| + "node_modules/yjs": { |
| + "version": "13.6.27", |
| + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", |
| + "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", |
| + "license": "MIT", |
| + "dependencies": { |
| + "lib0": "^0.2.99" |
| + }, |
| + "engines": { |
| + "node": ">=16.0.0", |
| + "npm": ">=8.0.0" |
| + }, |
| + "funding": { |
| + "type": "GitHub Sponsors ❤", |
| + "url": "https://github.com/sponsors/dmonad" |
| + } |
| + } |
| + } |
| +} |
diff --git a/fixtures/package.json b/fixtures/package.json| new file mode 100644 |
| index 0000000..8782e50 |
| --- /dev/null |
| +++ b/fixtures/package.json |
| @@ -0,0 +1,9 @@ |
| +{ |
| + "name": "docs-fixtures", |
| + "private": true, |
| + "type": "module", |
| + "description": "Generates Yjs wire fixtures. The JS side is the contract; Rust must reproduce these.", |
| + "dependencies": { |
| + "yjs": "13.6.27" |
| + } |
| +} |