Compare commits

...

27 Commits

Author SHA1 Message Date
Kevin Jahns
89dddc2a95 13.6.21 2024-12-21 00:55:05 +01:00
Kevin Jahns
f583d2a211 fix #657 - relative positions issue when using followUndoneDeletions=false 2024-12-21 00:52:48 +01:00
Kevin Jahns
1b0f2e5463 lint 2024-12-18 14:35:13 +01:00
Kevin Jahns
4404d090e4 add nodejs specific tests 2024-12-18 14:34:33 +01:00
Kevin Jahns
d4d4ae5f53 Merge pull request #679 from hoangqwe159/main
Add PSPDFKit binding to README.md
2024-12-14 21:24:57 +01:00
Viet Hoang Do
4ffd3709f8 Add PSPDFKit binding to README.md 2024-12-06 09:58:19 +10:00
Kevin Jahns
0419b74315 Merge pull request #676 from himself65/patch-1
docs: remove `@toeverything/y-indexeddb`
2024-12-04 22:37:32 +01:00
Kevin Jahns
c951f2b7ea add Open Collaboration Tools as a user 2024-11-28 01:08:37 +01:00
Alex Yang
4e2d3c8ac6 docs: remove @toeverything/y-indexeddb 2024-11-27 15:42:50 -08:00
Kevin Jahns
8dc1296a0b update readme 2024-10-24 18:07:52 +02:00
Kevin Jahns
4329997350 add stars to providers that sponsor yjs 2024-10-24 18:05:42 +02:00
Kevin Jahns
2b7ea8a2af Merge pull request #671 from carlossantos74/main
Added SuperViz Provider to the list of providers
2024-10-24 17:38:00 +02:00
Carlos
4f47355893 add SuperViz Provider in yjs README 2024-10-22 16:26:33 -03:00
Kevin Jahns
6074f80257 [funding.json] fix some validation issues 2024-10-19 17:43:48 +02:00
Kevin Jahns
42bbb44bfc fix errors in funding.json 2024-10-19 04:50:46 +02:00
Kevin Jahns
cc2d7320aa add funding.json 2024-10-19 04:40:13 +02:00
Kevin Jahns
e804dd7573 add y-crdt elexir bindings 2024-10-19 04:40:11 +02:00
Kevin Jahns
a304024a76 13.6.20 2024-10-14 01:41:22 +02:00
Kevin Jahns
487465d701 lint 2024-10-14 01:39:15 +02:00
Kevin Jahns
345fd31b10 add yjs-inspector 2024-10-07 09:45:27 +02:00
Kevin Jahns
4ff65b5dc3 add devtools 2024-10-07 09:43:13 +02:00
Kevin Jahns
8152cf81cb [#667] sanity checks for Yjs caveats. In dev_mode, objects inserted into Yjs can't be manipulated. 2024-10-04 21:23:59 +02:00
Kevin Jahns
3bf44b9850 #667 - add sanity messages when data is read before type is added to a document. 2024-10-04 21:07:19 +02:00
Kevin Jahns
8cd1a482bb Y.Array.length should be 0 before it is integrated - #666 2024-09-26 19:30:34 +02:00
Kevin Jahns
9e9f294009 Merge pull request #665 from batchor/main
add ScienHub as a user.
2024-09-19 23:02:50 +02:00
Batchor
4fb7789cdd add ScienHub as a user. 2024-09-05 15:23:58 -07:00
Batchor
c1ef9a12b9 add ScienHub as a user. 2024-09-05 15:22:40 -07:00
14 changed files with 294 additions and 30 deletions

View File

@@ -120,12 +120,16 @@ Showcase](https://yjs-diagram.synergy.codes/).
* [Kanbert](https://kanbert.com) - Project management software
* [Eclipse Theia](https://github.com/eclipse-theia/theia) - A cloud & desktop
IDE that runs in the browser.
* [ScienHub](https://scienhub.com) - Collaborative LaTeX editor in the browser.
* [Open Collaboration Tools](https://www.open-collab.tools/) - Collaborative
editing for your IDE or custom editor
## Table of Contents
* [Overview](#overview)
* [Bindings](#bindings)
* [Providers](#providers)
* [Tooling](#tooling)
* [Ports](#ports)
* [Getting Started](#getting-started)
* [API](#api)
@@ -159,6 +163,7 @@ are implemented in separate modules.
| React | | [react-yjs](https://github.com/nikgraf/react-yjs) | [demo](https://react-yjs-example.vercel.app/) |
| React / Vue / Svelte / MobX | | [SyncedStore](https://syncedstore.org) | [demo](https://syncedstore.org/docs/react) |
| [mobx-keystone](https://mobx-keystone.js.org/) | | [mobx-keystone-yjs](https://github.com/xaviergonz/mobx-keystone/tree/master/packages/mobx-keystone-yjs) | [demo](https://mobx-keystone.js.org/examples/yjs-binding) |
| [PSPDFKit](https://www.nutrient.io/) | | [yjs-pspdfkit](https://github.com/hoangqwe159/yjs-pspdfkit) | [demo](https://github.com/hoangqwe159/yjs-pspdfkit) |
### Providers
@@ -189,7 +194,7 @@ are available. Communication over the signaling servers can be encrypted by
providing a shared secret, keeping the connection information and the shared
document private.
</dd>
<dt><a href="https://github.com/liveblocks/liveblocks">@liveblocks/yjs</a></dt>
<dt><a href="https://github.com/liveblocks/liveblocks">@liveblocks/yjs </a> 🌟</dt>
<dd>
<a href="https://liveblocks.io/document/yjs">Liveblocks Yjs</a> provides a fully
hosted WebSocket infrastructure and persisted data store for Yjs
@@ -197,15 +202,23 @@ documents. No configuration or maintenance is required. It also features
Yjs webhook events, REST API to read and update Yjs documents, and a
browser DevTools extension.
</dd>
<dt><a href="https://github.com/drifting-in-space/y-sweet">y-sweet</a></dt>
<dt><a href="https://github.com/drifting-in-space/y-sweet">y-sweet</a></dt>
<dd>
A standalone yjs server with persistence to S3 or filesystem. They offer a
<a href="https://y-sweet.cloud">cloud service</a> as well.
</dd>
<dt><a href="https://github.com/ueberdosis/hocuspocus">Hocuspocus</a></dt>
<dt><a href="https://github.com/ueberdosis/hocuspocus">Hocuspocus</a></dt>
<dd>
A standalone extensible yjs server with sqlite persistence, webhooks, auth and more.
</dd>
<dt><a href="https://docs.superviz.com/collaboration/integrations/YJS/overview">@superviz/yjs</a></dt>
<dd>
SuperViz Yjs Provider comes with a secure, scalable real-time infrastructure
for Yjs documents, fully compatible with a set of real-time
collaboration components offered by SuperViz. This solution ensures
synchronization, offline editing, and real-time updates, enabling
multiple users to collaborate effectively within shared workspaces.
</dd>
<dt><a href="https://docs.partykit.io/reference/y-partykit-api/">PartyKit</a></dt>
<dd>
Cloud service for building multiplayer apps.
@@ -270,11 +283,6 @@ network provider.
<dd>
Adds persistent storage to a server with MongoDB. Can be used with the
y-websocket provider.
</dd>
<dt><a href="https://github.com/toeverything/AFFiNE/tree/master/packages/y-indexeddb">
@toeverything/y-indexeddb</a></dt>
<dd>
Like y-indexeddb, but with sub-documents support and fully TypeScript.
</dd>
<dt><a href="https://github.com/podraven/y-fire">y-fire</a></dt>
<dd>
@@ -293,7 +301,13 @@ A database and connection provider for Yjs based on Firestore.
</dd>
</dl>
# Ports
### Tooling
* [y-sweet debugger](https://docs.jamsocket.com/y-sweet/advanced/debugger)
* [liveblocks devtools](https://liveblocks.io/devtools)
* [Yjs inspector](https://inspector.yjs.dev)
### Ports
There are several Yjs-compatible ports to other programming languages.
@@ -307,6 +321,7 @@ language bindings to other languages
* [yswift](https://github.com/y-crdt/yswift) - Swift binding
* [yffi](https://github.com/y-crdt/y-crdt/tree/main/yffi) - C-FFI
* [ywasm](https://github.com/y-crdt/y-crdt/tree/main/ywasm) - WASM binding
* [y_ex](https://github.com/satoren/y_ex) - Elixir bindings
* [ycs](https://github.com/yjs/ycs) - .Net compatible C# implementation.
## Getting Started

143
funding.json Normal file
View File

@@ -0,0 +1,143 @@
{
"version": "v1.0.0",
"entity": {
"type": "group",
"role": "steward",
"name": "Kevin Jahns",
"email": "kevin.jahns@protonmail.com",
"phone": "",
"description": "OSS Developer",
"webpageUrl": {
"url": "https://github.com/yjs"
}
},
"projects": [
{
"guid": "yjs",
"name": "Yjs",
"description": "A library for building collaborative applications. #p2p #local-first #CRDT Funding this project will also enable me to maintain the other Yjs-related technologies.",
"webpageUrl": {
"url": "https://github.com/yjs/yjs"
},
"repositoryUrl": {
"url": "https://github.com/yjs/yjs"
},
"licenses": [
"spdx:MIT"
],
"tags": [
"collaboration",
"p2p",
"CRDT",
"rich-text",
"real-time"
]
},
{
"guid": "ystream",
"name": "Y/Stream",
"description": "A provider for syncing millions of docs efficiently with other peers. This will become the foundation for building real local-first apps with Yjs.",
"webpageUrl": {
"url": "https://github.com/yjs/ystream",
"wellKnown": "https://github.com/yjs/ystream/blob/main/.well-known/funding-manifest-urls"
},
"repositoryUrl": {
"url": "https://github.com/yjs/ystream",
"wellKnown": "https://github.com/yjs/ystream/blob/main/.well-known/funding-manifest-urls"
},
"licenses": [
"spdx:MIT",
"spdx:GPL-3.0"
],
"tags": [
"privacy",
"collaboration",
"p2p",
"CRDT",
"rich-text",
"real-time",
"web-development"
]
}
],
"funding": {
"channels": [
{
"guid": "github-sponsors",
"type": "payment-provider",
"address": "",
"description": "For funding of the Yjs project"
},
{
"guid": "y-collective",
"type": "payment-provider",
"address": "https://opencollective.com/y-collective",
"description": "For funding the Y-CRDT - the Rust implementation of Yjs and other listed projects."
}
],
"plans": [
{
"guid": "supporter",
"status": "active",
"name": "Supporter",
"description": "",
"amount": 0,
"currency": "USD",
"frequency": "monthly",
"channels": [
"github-sponsors",
"y-collective"
]
},
{
"guid": "ystream-funding",
"status": "active",
"name": "YStream Funding",
"description": "Fund the next generation of local-first providers.",
"amount": 30000,
"currency": "USD",
"frequency": "one-time",
"channels": [
"github-sponsors"
]
},
{
"guid": "bronze-sponsor",
"status": "active",
"name": "Bronze Sponsor",
"description": "This is the recommended plan for companies that use Yjs.",
"amount": 500,
"currency": "USD",
"frequency": "monthly",
"channels": [
"github-sponsors"
]
},
{
"guid": "silver-sponsor",
"status": "active",
"name": "Silver Sponsor",
"description": "This is the recommended plan for large/successfull companies that use Yjs.",
"amount": 1000,
"currency": "USD",
"frequency": "monthly",
"channels": [
"github-sponsors"
]
},
{
"guid": "gold-sponsor",
"status": "active",
"name": "Gold Sponsor",
"description": "This is the recommended plan for successful companies that build their entire product around Yjs-related technologies.",
"amount": 3000,
"currency": "USD",
"frequency": "monthly",
"channels": [
"github-sponsors"
]
}
],
"history": null
}
}

13
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{
"name": "yjs",
"version": "13.6.19",
"version": "13.6.21",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "yjs",
"version": "13.6.19",
"version": "13.6.21",
"license": "MIT",
"dependencies": {
"lib0": "^0.2.86"
"lib0": "^0.2.98"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1",
@@ -2785,13 +2785,14 @@
}
},
"node_modules/lib0": {
"version": "0.2.88",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz",
"integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==",
"version": "0.2.98",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.98.tgz",
"integrity": "sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==",
"dependencies": {
"isomorphic.js": "^0.2.4"
},
"bin": {
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
"0gentesthtml": "bin/gentesthtml.js",
"0serve": "bin/0serve.js"
},

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.6.19",
"version": "13.6.21",
"description": "Shared Editing Library",
"main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs",
@@ -13,7 +13,7 @@
},
"scripts": {
"clean": "rm -rf dist docs",
"test": "npm run dist && node ./dist/tests.cjs --repetition-time 50",
"test": "npm run dist && NODE_ENV=development node ./dist/tests.cjs --repetition-time 50",
"test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000",
"dist": "npm run clean && rollup -c && tsc",
"watch": "rollup -wc",
@@ -76,7 +76,7 @@
},
"homepage": "https://docs.yjs.dev",
"dependencies": {
"lib0": "^0.2.86"
"lib0": "^0.2.98"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1",

View File

@@ -2,6 +2,11 @@ import {
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js'
import * as env from 'lib0/environment'
import * as object from 'lib0/object'
const isDevMode = env.getVariable('node_env') === 'development'
export class ContentAny {
/**
* @param {Array<any>} arr
@@ -11,6 +16,7 @@ export class ContentAny {
* @type {Array<any>}
*/
this.arr = arr
isDevMode && object.deepFreeze(arr)
}
/**

View File

@@ -17,6 +17,12 @@ import * as map from 'lib0/map'
import * as iterator from 'lib0/iterator'
import * as error from 'lib0/error'
import * as math from 'lib0/math'
import * as log from 'lib0/logging'
/**
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
*/
export const warnPrematureAccess = () => { log.warn('Invalid access: Add Yjs type to a document before reading data.') }
const maxSearchMarker = 80
@@ -215,6 +221,7 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
* @return {Array<Item>}
*/
export const getTypeChildren = t => {
t.doc ?? warnPrematureAccess()
let s = t._start
const arr = []
while (s) {
@@ -408,6 +415,7 @@ export class AbstractType {
* @function
*/
export const typeListSlice = (type, start, end) => {
type.doc ?? warnPrematureAccess()
if (start < 0) {
start = type._length + start
}
@@ -443,6 +451,7 @@ export const typeListSlice = (type, start, end) => {
* @function
*/
export const typeListToArray = type => {
type.doc ?? warnPrematureAccess()
const cs = []
let n = type._start
while (n !== null) {
@@ -492,6 +501,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
export const typeListForEach = (type, f) => {
let index = 0
let n = type._start
type.doc ?? warnPrematureAccess()
while (n !== null) {
if (n.countable && !n.deleted) {
const c = n.content.getContent()
@@ -606,6 +616,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
* @function
*/
export const typeListGet = (type, index) => {
type.doc ?? warnPrematureAccess()
const marker = findMarker(type, index)
let n = type._start
if (marker !== null) {
@@ -874,6 +885,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
* @function
*/
export const typeMapGet = (parent, key) => {
parent.doc ?? warnPrematureAccess()
const val = parent._map.get(key)
return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
}
@@ -890,6 +902,7 @@ export const typeMapGetAll = (parent) => {
* @type {Object<string,any>}
*/
const res = {}
parent.doc ?? warnPrematureAccess()
parent._map.forEach((value, key) => {
if (!value.deleted) {
res[key] = value.content.getContent()[value.length - 1]
@@ -907,6 +920,7 @@ export const typeMapGetAll = (parent) => {
* @function
*/
export const typeMapHas = (parent, key) => {
parent.doc ?? warnPrematureAccess()
const val = parent._map.get(key)
return val !== undefined && !val.deleted
}
@@ -957,10 +971,13 @@ export const typeMapGetAllSnapshot = (parent, snapshot) => {
}
/**
* @param {Map<string,Item>} map
* @param {AbstractType<any> & { _map: Map<string, Item> }} type
* @return {IterableIterator<Array<any>>}
*
* @private
* @function
*/
export const createMapIterator = map => iterator.iteratorFilter(map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
export const createMapIterator = type => {
type.doc ?? warnPrematureAccess()
return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
}

View File

@@ -16,6 +16,7 @@ import {
YArrayRefID,
callTypeObservers,
transact,
warnPrematureAccess,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js'
import { typeListSlice } from './AbstractType.js'
@@ -104,7 +105,8 @@ export class YArray extends AbstractType {
}
get length () {
return this._prelimContent === null ? this._length : this._prelimContent.length
this.doc ?? warnPrematureAccess()
return this._length
}
/**

View File

@@ -13,6 +13,7 @@ import {
YMapRefID,
callTypeObservers,
transact,
warnPrematureAccess,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js'
@@ -121,6 +122,7 @@ export class YMap extends AbstractType {
* @return {Object<string,any>}
*/
toJSON () {
this.doc ?? warnPrematureAccess()
/**
* @type {Object<string,MapType>}
*/
@@ -140,7 +142,7 @@ export class YMap extends AbstractType {
* @return {number}
*/
get size () {
return [...createMapIterator(this._map)].length
return [...createMapIterator(this)].length
}
/**
@@ -149,7 +151,7 @@ export class YMap extends AbstractType {
* @return {IterableIterator<string>}
*/
keys () {
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[0])
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[0])
}
/**
@@ -158,7 +160,7 @@ export class YMap extends AbstractType {
* @return {IterableIterator<MapType>}
*/
values () {
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
}
/**
@@ -167,7 +169,7 @@ export class YMap extends AbstractType {
* @return {IterableIterator<[string, MapType]>}
*/
entries () {
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]]))
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]]))
}
/**
@@ -176,6 +178,7 @@ export class YMap extends AbstractType {
* @param {function(MapType,string,YMap<MapType>):void} f A function to execute on every element of this YArray.
*/
forEach (f) {
this.doc ?? warnPrematureAccess()
this._map.forEach((item, key) => {
if (!item.deleted) {
f(item.content.getContent()[item.length - 1], key, this)

View File

@@ -26,6 +26,7 @@ import {
typeMapGetAll,
updateMarkerChanges,
ContentType,
warnPrematureAccess,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
} from '../internals.js'
@@ -875,6 +876,7 @@ export class YText extends AbstractType {
* @type {number}
*/
get length () {
this.doc ?? warnPrematureAccess()
return this._length
}
@@ -931,6 +933,7 @@ export class YText extends AbstractType {
* @public
*/
toString () {
this.doc ?? warnPrematureAccess()
let str = ''
/**
* @type {Item|null}
@@ -1004,6 +1007,7 @@ export class YText extends AbstractType {
* @public
*/
toDelta (snapshot, prevSnapshot, computeYChange) {
this.doc ?? warnPrematureAccess()
/**
* @type{Array<any>}
*/

View File

@@ -17,6 +17,7 @@ import {
transact,
typeListGet,
typeListSlice,
warnPrematureAccess,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook // eslint-disable-line
} from '../internals.js'
@@ -66,6 +67,7 @@ export class YXmlTreeWalker {
*/
this._currentNode = /** @type {Item} */ (root._start)
this._firstCall = true
root.doc ?? warnPrematureAccess()
}
[Symbol.iterator] () {
@@ -177,6 +179,7 @@ export class YXmlFragment extends AbstractType {
}
get length () {
this.doc ?? warnPrematureAccess()
return this._prelimContent === null ? this._length : this._prelimContent.length
}

View File

@@ -9,7 +9,7 @@ import {
ContentType,
followRedone,
getItem,
ID, Doc, AbstractType // eslint-disable-line
StructStore, ID, Doc, AbstractType, // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding'
@@ -256,6 +256,18 @@ export const readRelativePosition = decoder => {
*/
export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array))
/**
* @param {StructStore} store
* @param {ID} id
*/
const getItemWithOffset = (store, id) => {
const item = getItem(store, id)
const diff = id.clock - item.id.clock
return {
item, diff
}
}
/**
* Transform a relative position to an absolute position.
*
@@ -286,7 +298,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndo
if (getState(store, rightID.client) <= rightID.clock) {
return null
}
const res = followUndoneDeletions ? followRedone(store, rightID) : { item: getItem(store, rightID), diff: 0 }
const res = followUndoneDeletions ? followRedone(store, rightID) : getItemWithOffset(store, rightID)
const right = res.item
if (!(right instanceof Item)) {
return null

View File

@@ -15,15 +15,28 @@ import * as relativePositions from './relativePositions.tests.js'
import { runTests } from 'lib0/testing'
import { isBrowser, isNode } from 'lib0/environment'
import * as log from 'lib0/logging'
import { environment } from 'lib0'
if (isBrowser) {
log.createVConsole(document.body)
}
runTests({
/**
* @type {any}
*/
const tests = {
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions
}).then(success => {
}
const run = async () => {
if (environment.isNode) {
// tests.nodejs = await import('./node.tests.js')
}
const success = await runTests(tests)
/* istanbul ignore next */
if (isNode) {
process.exit(success ? 0 : 1)
}
})
}
run()

View File

@@ -85,6 +85,26 @@ export const testRelativePositionCase6 = tc => {
checkRelativePositions(ytext)
}
/**
* Testing https://github.com/yjs/yjs/issues/657
*
* @param {t.TestCase} tc
*/
export const testRelativePositionCase7 = tc => {
const docA = new Y.Doc()
const textA = docA.getText('text')
textA.insert(0, 'abcde')
// Create a relative position at index 2 in 'textA'
const relativePosition = Y.createRelativePositionFromTypeIndex(textA, 2)
// Verify that the absolutes positions on 'docA' are the same
const absolutePositionWithFollow =
Y.createAbsolutePositionFromRelativePosition(relativePosition, docA, true)
const absolutePositionWithoutFollow =
Y.createAbsolutePositionFromRelativePosition(relativePosition, docA, false)
t.assert(absolutePositionWithFollow?.index === 2)
t.assert(absolutePositionWithoutFollow?.index === 2)
}
/**
* @param {t.TestCase} tc
*/

View File

@@ -4,6 +4,9 @@ import * as Y from '../src/index.js'
import * as t from 'lib0/testing'
import * as prng from 'lib0/prng'
import * as math from 'lib0/math'
import * as env from 'lib0/environment'
const isDevMode = env.getVariable('node_env') === 'development'
/**
* @param {t.TestCase} tc
@@ -17,6 +20,28 @@ export const testBasicUpdate = tc => {
t.compare(doc2.getArray('array').toArray(), ['hi'])
}
/**
* @param {t.TestCase} tc
*/
export const testFailsObjectManipulationInDevMode = tc => {
if (isDevMode) {
t.info('running in dev mode')
const doc = new Y.Doc()
const a = [1, 2, 3]
const b = { o: 1 }
doc.getArray('test').insert(0, [a])
doc.getMap('map').set('k', b)
t.fails(() => {
a[0] = 42
})
t.fails(() => {
b.o = 42
})
} else {
t.info('not in dev mode')
}
}
/**
* @param {t.TestCase} tc
*/