diff --git a/README.md b/README.md index 8992aad..b644077 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,8 @@ See the [example website source](examples/website/src/index.ts) for a more compl React Native's default engine, Hermes, does not expose a runtime `WebAssembly` global, so the WebAssembly build cannot run there. For React Native, the package -ships a [`wasm2js`](https://github.com/WebAssembly/binaryen) (pure-JavaScript) -build via its `react-native` entry point. +ships a pure-JavaScript build produced by [Binaryen's `wasm2js`](https://github.com/WebAssembly/binaryen) +via its `react-native` entry point. ### Python diff --git a/reconcile-js/package.json b/reconcile-js/package.json index 69a333f..2327a65 100644 --- a/reconcile-js/package.json +++ b/reconcile-js/package.json @@ -33,7 +33,7 @@ ], "scripts": { "build": "node scripts/build-rn.mjs && webpack --mode production", - "format": "prettier --write \"./**/*.(ts|scss|json|html)\"", + "format": "prettier --write \"./**/*.(ts|mjs|scss|json|html)\"", "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest" }, "devDependencies": { diff --git a/reconcile-js/src/core.ts b/reconcile-js/src/core.ts index 3dc78db..cf2d1ec 100644 --- a/reconcile-js/src/core.ts +++ b/reconcile-js/src/core.ts @@ -27,7 +27,7 @@ export interface WasmBackend { ensureReady(): void; } -// Define the enum values as const arrays to avoid duplication +// Define the enum values as a const array to avoid duplication const BUILTIN_TOKENIZERS = ['Character', 'Line', 'Markdown', 'Word'] as const; /** @@ -148,7 +148,8 @@ export interface ReconcileApi { * @param left - The left version of the text (either string or TextWithCursors with cursor positions) * @param right - The right version of the text (either string or TextWithCursors with cursor positions) * @param tokenizer - The tokenisation strategy: "Word" (default, recommended for prose), - * "Character" (fine-grained), or "Line" (similar to git merge) + * "Character" (fine-grained), "Line" (similar to git merge), or + * "Markdown" (splits on Markdown structure) * @returns The reconciled text with automatically repositioned cursor positions * * @example @@ -219,7 +220,8 @@ export interface ReconcileApi { * @param left - The left version of the text (either string or TextWithCursors with cursor positions) * @param right - The right version of the text (either string or TextWithCursors with cursor positions) * @param tokenizer - The tokenisation strategy: "Word" (default, recommended for prose), - * "Character" (fine-grained), or "Line" (similar to git merge) + * "Character" (fine-grained), "Line" (similar to git merge), or + * "Markdown" (splits on Markdown structure) * @returns The reconciled text with cursor positions and detailed change history * * @example @@ -351,7 +353,15 @@ export function makeReconcileApi(backend: WasmBackend): ReconcileApi { backend.ensureReady(); assertTokenizer(tokenizer); - return backend.undiff(original, diffValue, tokenizer); + // The real-WebAssembly backend's `diff` emits BigInt spans, whereas the + // wasm2js (React Native) backend rejects BigInt outright. Normalise to + // plain numbers - exactly as `diff` does on the way out - so a `diff` + // result round-trips through `undiff` identically on every platform. + return backend.undiff( + original, + diffValue.map((item) => (typeof item === 'bigint' ? Number(item) : item)), + tokenizer + ); } function reconcileWithHistory( diff --git a/reconcile-js/src/index.rn.test.ts b/reconcile-js/src/index.rn.test.ts index aea7831..682c494 100644 --- a/reconcile-js/src/index.rn.test.ts +++ b/reconcile-js/src/index.rn.test.ts @@ -60,6 +60,46 @@ describe('reconcile (wasm2js / React Native build)', () => { expect(result.text).toEqual('Hi world'); expect(result.history.length).toBeGreaterThan(0); }); + + it('undiff accepts bigint entries (per the Array type)', () => { + const original = 'Hello world'; + const changed = 'Hello cruel world'; + + // `diff` returns plain numbers; emulate a caller that supplies BigInt, which + // the public signature permits. The wasm2js (React Native) build rejects raw + // BigInt, so the wrapper must normalise it to keep both backends identical. + const withBigints = diff(original, changed).map((item) => + typeof item === 'number' ? BigInt(item) : item + ); + + expect(withBigints.some((item) => typeof item === 'bigint')).toBe(true); + expect(undiff(original, withBigints)).toEqual(changed); + }); + + it('runs every operation with no WebAssembly global (Hermes parity)', () => { + // Hermes exposes no `WebAssembly` global - the entire reason the React + // Native entry point links a wasm2js (pure-JS) build instead of the real + // module. Remove the global to prove the operations never reach for it. + const descriptor = Object.getOwnPropertyDescriptor(globalThis, 'WebAssembly'); + delete (globalThis as { WebAssembly?: unknown }).WebAssembly; + try { + expect((globalThis as { WebAssembly?: unknown }).WebAssembly).toBeUndefined(); + + expect(reconcile('Hello', 'Hello world', 'Hi world').text).toEqual('Hi world'); + + const changes = diff('Hello world', 'Hello cruel world'); + expect(undiff('Hello world', changes)).toEqual('Hello cruel world'); + + expect( + reconcileWithHistory('Hello', 'Hello world', 'Hi world').history.length + ).toBeGreaterThan(0); + } finally { + // Restore the global so the leak check and later suites are unaffected. + if (descriptor) { + Object.defineProperty(globalThis, 'WebAssembly', descriptor); + } + } + }); }); describe('test_diff_and_undiff_are_inverse (wasm2js / React Native build)', () => { diff --git a/reconcile-js/src/index.test.ts b/reconcile-js/src/index.test.ts index 0de924c..0fbea9a 100644 --- a/reconcile-js/src/index.test.ts +++ b/reconcile-js/src/index.test.ts @@ -60,6 +60,21 @@ describe('reconcile', () => { expect(result.text).toEqual('Hi world'); expect(result.history.length).toBeGreaterThan(0); }); + + it('undiff accepts bigint entries (per the Array type)', () => { + const original = 'Hello world'; + const changed = 'Hello cruel world'; + + // `diff` returns plain numbers; emulate a caller that supplies BigInt, which + // the public signature permits. The wasm2js (React Native) build rejects raw + // BigInt, so the wrapper must normalise it to keep both backends identical. + const withBigints = diff(original, changed).map((item) => + typeof item === 'number' ? BigInt(item) : item + ); + + expect(withBigints.some((item) => typeof item === 'bigint')).toBe(true); + expect(undiff(original, withBigints)).toEqual(changed); + }); }); describe('test_diff_and_undiff_are_inverse', () => {