feat(editor): flat block data (#9854)
Flat block data.
A new block type to flatten the block data
```typescript
// For developers
type Model = {
blocks: Record<string, {
flavour: string;
cells: Record<string, {
rowId: string;
colId: string;
text: Text;
}>;
cols: Record<string, {
align: string;
}>
rows: Record<string, {
backgroundColor: string;
}>
}>
}
// How it's saved in yjs
const yData = {
blocks: {
'blockId1': {
flavour: 'affine:table',
'prop:rows:row1:backgroundColor': 'white',
'prop:cols:col1:align': 'left',
'prop:cells:cell1:rowId': 'row1',
'prop:cells:cell1:colId': 'col1',
'prop:cells:cell1:text': YText,
prop:children: []
},
}
}
```
This commit is contained in:
@@ -39,13 +39,29 @@ const tableSchema = defineBlockSchema({
|
||||
version: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const flatTableSchema = defineBlockSchema({
|
||||
flavour: 'flat-table',
|
||||
props: internal => ({
|
||||
title: internal.Text(),
|
||||
cols: { internal: { color: 'white' } } as Record<string, { color: string }>,
|
||||
rows: {} as Record<string, { color: string }>,
|
||||
labels: [] as Array<string>,
|
||||
}),
|
||||
metadata: {
|
||||
role: 'content',
|
||||
version: 1,
|
||||
isFlatData: true,
|
||||
},
|
||||
});
|
||||
type RootModel = SchemaToModel<typeof pageSchema>;
|
||||
type TableModel = SchemaToModel<typeof tableSchema>;
|
||||
type FlatTableModel = SchemaToModel<typeof flatTableSchema>;
|
||||
|
||||
function createTestOptions() {
|
||||
const idGenerator = createAutoIncrementIdGenerator();
|
||||
const schema = new Schema();
|
||||
schema.register([pageSchema, tableSchema]);
|
||||
schema.register([pageSchema, tableSchema, flatTableSchema]);
|
||||
return { id: 'test-collection', idGenerator, schema };
|
||||
}
|
||||
|
||||
@@ -181,24 +197,29 @@ describe('block model should has signal props', () => {
|
||||
yBlock.set('sys:flavour', 'page');
|
||||
yBlock.set('sys:children', new Y.Array());
|
||||
|
||||
const block = new Block(doc.schema, yBlock, doc);
|
||||
const onChange = vi.fn();
|
||||
const block = new Block(doc.schema, yBlock, doc, { onChange });
|
||||
const model = block.model as RootModel;
|
||||
|
||||
expect(model.count).toBe(0);
|
||||
model.stash('count');
|
||||
|
||||
onChange.mockClear();
|
||||
model.count = 1;
|
||||
expect(model.count$.value).toBe(1);
|
||||
expect(yBlock.get('prop:count')).toBe(0);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
model.count$.value = 2;
|
||||
expect(model.count).toBe(2);
|
||||
expect(yBlock.get('prop:count')).toBe(0);
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
|
||||
model.pop('count');
|
||||
expect(yBlock.get('prop:count')).toBe(2);
|
||||
expect(model.count).toBe(2);
|
||||
expect(model.count$.value).toBe(2);
|
||||
expect(onChange).toHaveBeenCalledTimes(3);
|
||||
|
||||
model.stash('count');
|
||||
yBlock.set('prop:count', 3);
|
||||
@@ -361,3 +382,147 @@ test('deep sync', () => {
|
||||
expect(onPropsUpdated).toHaveBeenCalledTimes(1);
|
||||
expect(onRowsUpdated).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('flat', () => {
|
||||
test('flat crud', async () => {
|
||||
const doc = createTestDoc();
|
||||
const yDoc = new Y.Doc();
|
||||
const yBlock = yDoc.getMap('yBlock') as YBlock;
|
||||
yBlock.set('sys:id', '0');
|
||||
yBlock.set('sys:flavour', 'flat-table');
|
||||
yBlock.set('sys:children', new Y.Array());
|
||||
|
||||
const onChange = vi.fn();
|
||||
const onColUpdated = vi.fn();
|
||||
|
||||
const block = new Block(doc.schema, yBlock, doc, { onChange });
|
||||
const model = block.model as FlatTableModel;
|
||||
model.props.title = internalPrimitives.Text();
|
||||
|
||||
model.props.cols$.subscribe(onColUpdated);
|
||||
onChange.mockClear();
|
||||
onColUpdated.mockClear();
|
||||
|
||||
model.props.cols = {
|
||||
...model.props.cols,
|
||||
a: { color: 'red' },
|
||||
};
|
||||
expect(onColUpdated).toHaveBeenCalledTimes(1);
|
||||
expect(yBlock.get('prop:cols.a.color')).toBe('red');
|
||||
expect(yBlock.get('prop:cols.internal.color')).toBe('white');
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
onChange.mockClear();
|
||||
onColUpdated.mockClear();
|
||||
model.props.cols.b = { color: 'blue' };
|
||||
expect(yBlock.get('prop:cols.b.color')).toBe('blue');
|
||||
expect(model.props.cols$.peek()).toEqual({
|
||||
a: { color: 'red' },
|
||||
b: { color: 'blue' },
|
||||
internal: { color: 'white' },
|
||||
});
|
||||
expect(onColUpdated).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'cols',
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
model.props.cols.a.color = 'black';
|
||||
expect(yBlock.get('prop:cols.a.color')).toBe('black');
|
||||
expect(model.props.cols$.value.a.color).toBe('black');
|
||||
|
||||
onChange.mockClear();
|
||||
onColUpdated.mockClear();
|
||||
model.props.cols$.value = {
|
||||
a: { color: 'red' },
|
||||
};
|
||||
expect(yBlock.get('prop:cols.a.color')).toBe('red');
|
||||
expect(yBlock.get('prop:cols.internal.color')).toBe(undefined);
|
||||
expect(yBlock.get('prop:cols.b.color')).toBe(undefined);
|
||||
expect(model.props.cols).toEqual({
|
||||
a: { color: 'red' },
|
||||
});
|
||||
expect(onColUpdated).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'cols',
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
onChange.mockClear();
|
||||
model.props.title.insert('test', 0);
|
||||
expect((yBlock.get('prop:title') as Y.Text).toJSON()).toBe('test');
|
||||
expect(model.props.title$.value.toDelta()).toEqual([{ insert: 'test' }]);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'title',
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
onChange.mockClear();
|
||||
model.props.labels.push('test');
|
||||
const getLabels = () => yBlock.get('prop:labels') as Y.Array<unknown>;
|
||||
expect(getLabels().toJSON()).toEqual(['test']);
|
||||
expect(model.props.labels$.value).toEqual(['test']);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'labels',
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
onChange.mockClear();
|
||||
model.props.labels$.value = ['test2'];
|
||||
expect(getLabels().toJSON()).toEqual(['test2']);
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
'labels',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
test('stash and pop', () => {
|
||||
const doc = createTestDoc();
|
||||
const yDoc = new Y.Doc();
|
||||
const yBlock = yDoc.getMap('yBlock') as YBlock;
|
||||
yBlock.set('sys:id', '0');
|
||||
yBlock.set('sys:flavour', 'flat-table');
|
||||
yBlock.set('sys:children', new Y.Array());
|
||||
const onColUpdated = vi.fn();
|
||||
|
||||
const onChange = vi.fn();
|
||||
const block = new Block(doc.schema, yBlock, doc, { onChange });
|
||||
const model = block.model as FlatTableModel;
|
||||
|
||||
model.props.cols$.subscribe(onColUpdated);
|
||||
|
||||
onChange.mockClear();
|
||||
onColUpdated.mockClear();
|
||||
|
||||
model.props.cols = {
|
||||
a: { color: 'red' },
|
||||
};
|
||||
expect(yBlock.get('prop:cols.a.color')).toBe('red');
|
||||
expect(model.props.cols$.value.a.color).toBe('red');
|
||||
expect(onColUpdated).toHaveBeenCalledTimes(1);
|
||||
|
||||
onChange.mockClear();
|
||||
onColUpdated.mockClear();
|
||||
|
||||
model.stash('cols');
|
||||
model.props.cols.a.color = 'blue';
|
||||
expect(yBlock.get('prop:cols.a.color')).toBe('red');
|
||||
expect(model.props.cols$.value.a.color).toBe('blue');
|
||||
expect(onColUpdated).toHaveBeenCalledTimes(1);
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
model.pop('cols');
|
||||
expect(yBlock.get('prop:cols.a.color')).toBe('blue');
|
||||
expect(onColUpdated).toHaveBeenCalledTimes(2);
|
||||
expect(onChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,177 +1,237 @@
|
||||
import { Slot } from '@blocksuite/global/utils';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { ReactiveFlatYMap } from '../reactive/flat-native-y.js';
|
||||
import type { Text } from '../reactive/index.js';
|
||||
import { Boxed, createYProxy, popProp, stashProp } from '../reactive/index.js';
|
||||
|
||||
describe('blocksuite yjs', () => {
|
||||
describe('array', () => {
|
||||
test('proxy', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const arr = ydoc.getArray('arr');
|
||||
arr.push([0]);
|
||||
describe('array', () => {
|
||||
test('proxy', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const arr = ydoc.getArray('arr');
|
||||
arr.push([0]);
|
||||
|
||||
const proxy = createYProxy(arr) as unknown[];
|
||||
expect(arr.get(0)).toBe(0);
|
||||
const proxy = createYProxy(arr) as unknown[];
|
||||
expect(arr.get(0)).toBe(0);
|
||||
|
||||
proxy.push(1);
|
||||
expect(arr.get(1)).toBe(1);
|
||||
expect(arr.length).toBe(2);
|
||||
proxy.push(1);
|
||||
expect(arr.get(1)).toBe(1);
|
||||
expect(arr.length).toBe(2);
|
||||
|
||||
proxy.splice(1, 1);
|
||||
expect(arr.length).toBe(1);
|
||||
proxy.splice(1, 1);
|
||||
expect(arr.length).toBe(1);
|
||||
|
||||
proxy[0] = 2;
|
||||
expect(arr.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('object', () => {
|
||||
test('deep', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const obj = new Y.Map();
|
||||
obj.set('foo', 1);
|
||||
map.set('obj', obj);
|
||||
map.set('num', 0);
|
||||
const map2 = new Y.Map();
|
||||
obj.set('map', map2);
|
||||
map2.set('foo', 40);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(map);
|
||||
|
||||
expect(proxy.num).toBe(0);
|
||||
expect(proxy.obj.foo).toBe(1);
|
||||
expect(proxy.obj.map.foo).toBe(40);
|
||||
|
||||
proxy.obj.bar = 100;
|
||||
expect(obj.get('bar')).toBe(100);
|
||||
|
||||
proxy.obj2 = { foo: 2, bar: { num: 3 } };
|
||||
expect(map.get('obj2')).toBeInstanceOf(Y.Map);
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('obj2').get('bar').get('num')).toBe(3);
|
||||
|
||||
proxy.obj2.bar.str = 'hello';
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('obj2').get('bar').get('str')).toBe('hello');
|
||||
|
||||
proxy.obj3 = {};
|
||||
const { obj3 } = proxy;
|
||||
obj3.id = 'obj3';
|
||||
expect((map.get('obj3') as Y.Map<string>).get('id')).toBe('obj3');
|
||||
|
||||
proxy.arr = [];
|
||||
expect(map.get('arr')).toBeInstanceOf(Y.Array);
|
||||
proxy.arr.push({ counter: 1 });
|
||||
expect((map.get('arr') as Y.Array<Y.Map<number>>).get(0)).toBeInstanceOf(
|
||||
Y.Map
|
||||
);
|
||||
expect(
|
||||
(map.get('arr') as Y.Array<Y.Map<number>>).get(0).get('counter')
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('with y text', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const inner = new Y.Map();
|
||||
map.set('inner', inner);
|
||||
const text = new Y.Text('hello');
|
||||
inner.set('text', text);
|
||||
|
||||
const proxy = createYProxy<{ inner: { text: Text } }>(map);
|
||||
proxy.inner = { ...proxy.inner };
|
||||
expect(proxy.inner.text.yText).toBeInstanceOf(Y.Text);
|
||||
expect(proxy.inner.text.yText.toJSON()).toBe('hello');
|
||||
});
|
||||
|
||||
test('with native wrapper', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const inner = new Y.Map();
|
||||
map.set('inner', inner);
|
||||
const native = new Boxed(['hello', 'world']);
|
||||
inner.set('native', native.yMap);
|
||||
|
||||
const proxy = createYProxy<{
|
||||
inner: {
|
||||
native: Boxed<string[]>;
|
||||
native2: Boxed<number>;
|
||||
};
|
||||
}>(map);
|
||||
|
||||
expect(proxy.inner.native.getValue()).toEqual(['hello', 'world']);
|
||||
|
||||
proxy.inner.native.setValue(['hello', 'world', 'foo']);
|
||||
expect(native.getValue()).toEqual(['hello', 'world', 'foo']);
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('inner').get('native').get('value')).toEqual([
|
||||
'hello',
|
||||
'world',
|
||||
'foo',
|
||||
]);
|
||||
|
||||
const native2 = new Boxed(0);
|
||||
proxy.inner.native2 = native2;
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('inner').get('native2').get('value')).toBe(0);
|
||||
native2.setValue(1);
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('inner').get('native2').get('value')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stash and pop', () => {
|
||||
test('object', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
map.set('num', 0);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(map);
|
||||
|
||||
expect(proxy.num).toBe(0);
|
||||
stashProp(map, 'num');
|
||||
proxy.num = 1;
|
||||
expect(proxy.num).toBe(1);
|
||||
expect(map.get('num')).toBe(0);
|
||||
proxy.num = 2;
|
||||
popProp(map, 'num');
|
||||
expect(map.get('num')).toBe(2);
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const arr = ydoc.getArray('arr');
|
||||
arr.push([0]);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(arr);
|
||||
|
||||
expect(proxy[0]).toBe(0);
|
||||
stashProp(arr, 0);
|
||||
proxy[0] = 1;
|
||||
expect(proxy[0]).toBe(1);
|
||||
expect(arr.get(0)).toBe(0);
|
||||
popProp(arr, 0);
|
||||
expect(arr.get(0)).toBe(1);
|
||||
});
|
||||
|
||||
test('nested', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const arr = new Y.Array();
|
||||
map.set('arr', arr);
|
||||
arr.push([0]);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(map);
|
||||
|
||||
expect(proxy.arr[0]).toBe(0);
|
||||
stashProp(arr, 0);
|
||||
proxy.arr[0] = 1;
|
||||
expect(proxy.arr[0]).toBe(1);
|
||||
expect(arr.get(0)).toBe(0);
|
||||
popProp(arr, 0);
|
||||
expect(arr.get(0)).toBe(1);
|
||||
});
|
||||
proxy[0] = 2;
|
||||
expect(arr.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('object', () => {
|
||||
test('deep', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const obj = new Y.Map();
|
||||
obj.set('foo', 1);
|
||||
map.set('obj', obj);
|
||||
map.set('num', 0);
|
||||
const map2 = new Y.Map();
|
||||
obj.set('map', map2);
|
||||
map2.set('foo', 40);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(map);
|
||||
|
||||
expect(proxy.num).toBe(0);
|
||||
expect(proxy.obj.foo).toBe(1);
|
||||
expect(proxy.obj.map.foo).toBe(40);
|
||||
|
||||
proxy.obj.bar = 100;
|
||||
expect(obj.get('bar')).toBe(100);
|
||||
|
||||
proxy.obj2 = { foo: 2, bar: { num: 3 } };
|
||||
expect(map.get('obj2')).toBeInstanceOf(Y.Map);
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('obj2').get('bar').get('num')).toBe(3);
|
||||
|
||||
proxy.obj2.bar.str = 'hello';
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('obj2').get('bar').get('str')).toBe('hello');
|
||||
|
||||
proxy.obj3 = {};
|
||||
const { obj3 } = proxy;
|
||||
obj3.id = 'obj3';
|
||||
expect((map.get('obj3') as Y.Map<string>).get('id')).toBe('obj3');
|
||||
|
||||
proxy.arr = [];
|
||||
expect(map.get('arr')).toBeInstanceOf(Y.Array);
|
||||
proxy.arr.push({ counter: 1 });
|
||||
expect((map.get('arr') as Y.Array<Y.Map<number>>).get(0)).toBeInstanceOf(
|
||||
Y.Map
|
||||
);
|
||||
expect(
|
||||
(map.get('arr') as Y.Array<Y.Map<number>>).get(0).get('counter')
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('with y text', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const inner = new Y.Map();
|
||||
map.set('inner', inner);
|
||||
const text = new Y.Text('hello');
|
||||
inner.set('text', text);
|
||||
|
||||
const proxy = createYProxy<{ inner: { text: Text } }>(map);
|
||||
proxy.inner = { ...proxy.inner };
|
||||
expect(proxy.inner.text.yText).toBeInstanceOf(Y.Text);
|
||||
expect(proxy.inner.text.yText.toJSON()).toBe('hello');
|
||||
});
|
||||
|
||||
test('with native wrapper', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const inner = new Y.Map();
|
||||
map.set('inner', inner);
|
||||
const native = new Boxed(['hello', 'world']);
|
||||
inner.set('native', native.yMap);
|
||||
|
||||
const proxy = createYProxy<{
|
||||
inner: {
|
||||
native: Boxed<string[]>;
|
||||
native2: Boxed<number>;
|
||||
};
|
||||
}>(map);
|
||||
|
||||
expect(proxy.inner.native.getValue()).toEqual(['hello', 'world']);
|
||||
|
||||
proxy.inner.native.setValue(['hello', 'world', 'foo']);
|
||||
expect(native.getValue()).toEqual(['hello', 'world', 'foo']);
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('inner').get('native').get('value')).toEqual([
|
||||
'hello',
|
||||
'world',
|
||||
'foo',
|
||||
]);
|
||||
|
||||
const native2 = new Boxed(0);
|
||||
proxy.inner.native2 = native2;
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('inner').get('native2').get('value')).toBe(0);
|
||||
native2.setValue(1);
|
||||
// @ts-expect-error ignore
|
||||
expect(map.get('inner').get('native2').get('value')).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stash and pop', () => {
|
||||
test('object', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
map.set('num', 0);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(map);
|
||||
|
||||
expect(proxy.num).toBe(0);
|
||||
stashProp(map, 'num');
|
||||
proxy.num = 1;
|
||||
expect(proxy.num).toBe(1);
|
||||
expect(map.get('num')).toBe(0);
|
||||
proxy.num = 2;
|
||||
popProp(map, 'num');
|
||||
expect(map.get('num')).toBe(2);
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const arr = ydoc.getArray('arr');
|
||||
arr.push([0]);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(arr);
|
||||
|
||||
expect(proxy[0]).toBe(0);
|
||||
stashProp(arr, 0);
|
||||
proxy[0] = 1;
|
||||
expect(proxy[0]).toBe(1);
|
||||
expect(arr.get(0)).toBe(0);
|
||||
popProp(arr, 0);
|
||||
expect(arr.get(0)).toBe(1);
|
||||
});
|
||||
|
||||
test('nested', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
const arr = new Y.Array();
|
||||
map.set('arr', arr);
|
||||
arr.push([0]);
|
||||
|
||||
const proxy = createYProxy<Record<string, any>>(map);
|
||||
|
||||
expect(proxy.arr[0]).toBe(0);
|
||||
stashProp(arr, 0);
|
||||
proxy.arr[0] = 1;
|
||||
expect(proxy.arr[0]).toBe(1);
|
||||
expect(arr.get(0)).toBe(0);
|
||||
popProp(arr, 0);
|
||||
expect(arr.get(0)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('flat', () => {
|
||||
const ydoc = new Y.Doc();
|
||||
const map = ydoc.getMap('map');
|
||||
map.set('prop:col.a', 0);
|
||||
map.set('prop:col.b', 1);
|
||||
map.set('prop:col.c.d', 3);
|
||||
map.set('prop:col.c.e', 4);
|
||||
|
||||
const reactive = new ReactiveFlatYMap(map, new Slot());
|
||||
const proxy = reactive.proxy as Record<string, any>;
|
||||
proxy.col.c.d = 200;
|
||||
expect(map.get('prop:col.c.d')).toBe(200);
|
||||
|
||||
proxy.col.a = 200;
|
||||
expect(map.get('prop:col.a')).toBe(200);
|
||||
|
||||
proxy.col.f = { a: 1 };
|
||||
expect(map.get('prop:col.f.a')).toBe(1);
|
||||
|
||||
proxy.foo = 'foo';
|
||||
expect(map.get('prop:foo')).toBe('foo');
|
||||
|
||||
proxy.col.c = {
|
||||
d: 500,
|
||||
};
|
||||
expect(map.get('prop:col.c.d')).toBe(500);
|
||||
expect(map.get('prop:col.c.e')).toBe(undefined);
|
||||
|
||||
proxy.col.a = 100;
|
||||
expect(map.get('prop:col.a')).toBe(100);
|
||||
proxy.col.c.e = 100;
|
||||
expect(map.get('prop:col.c.e')).toBe(100);
|
||||
|
||||
const undoManager = new Y.UndoManager([map]);
|
||||
|
||||
ydoc.transact(() => {
|
||||
map.set('prop:col.c.e', 200);
|
||||
}, undoManager);
|
||||
expect(proxy.col.c.e).toBe(200);
|
||||
|
||||
ydoc.transact(() => {
|
||||
map.delete('prop:col.c.d');
|
||||
}, undoManager);
|
||||
expect(proxy.col.c.d).toBe(undefined);
|
||||
|
||||
proxy.foo$.value = 'foo2';
|
||||
expect(map.get('prop:foo')).toBe('foo2');
|
||||
|
||||
proxy.col$.value = {
|
||||
a: 10,
|
||||
b: 20,
|
||||
c: {
|
||||
d: 30,
|
||||
},
|
||||
};
|
||||
expect(map.get('prop:col.a')).toBe(10);
|
||||
expect(map.get('prop:col.b')).toBe(20);
|
||||
expect(map.get('prop:col.c.d')).toBe(30);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user