mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-24 02:00:33 +03:00
fix: resolving arrays (collections still wip)
This commit is contained in:
@@ -21,7 +21,16 @@ export function createHandler (context: MetaContext, pathSegments: PathSegments
|
|||||||
const keyPath: PathSegments = [...pathSegments, key]
|
const keyPath: PathSegments = [...pathSegments, key]
|
||||||
|
|
||||||
const handler = /* #__PURE__ */ createHandler(context, keyPath)
|
const handler = /* #__PURE__ */ createHandler(context, keyPath)
|
||||||
value.__vm_proxy = createProxy(value, handler)
|
Object.defineProperty(
|
||||||
|
value,
|
||||||
|
'__vm_proxy',
|
||||||
|
{
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
value: createProxy(value, handler)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.__vm_proxy
|
return value.__vm_proxy
|
||||||
@@ -31,17 +40,25 @@ export function createHandler (context: MetaContext, pathSegments: PathSegments
|
|||||||
key: string,
|
key: string,
|
||||||
value: any
|
value: any
|
||||||
): boolean {
|
): boolean {
|
||||||
update(context, pathSegments, key, value)
|
const success = Reflect.set(target, key, value)
|
||||||
// target[key] = value
|
|
||||||
return true
|
if (success) {
|
||||||
|
update(context, pathSegments, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
},
|
},
|
||||||
deleteProperty (
|
deleteProperty (
|
||||||
target: { [key: string]: any },
|
target: { [key: string]: any },
|
||||||
prop: string
|
prop: string
|
||||||
) {
|
) {
|
||||||
remove(context, pathSegments, prop)
|
const success = Reflect.deleteProperty(target, prop)
|
||||||
delete target[prop]
|
|
||||||
return true
|
if (success) {
|
||||||
|
remove(context, pathSegments, prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { hasOwn } from '@vue/shared'
|
import { hasOwn, isArray } from '@vue/shared'
|
||||||
import { clone } from '../utils'
|
import { clone } from '../utils'
|
||||||
import { ActiveNode, GetActiveNode, MetaContext, PathSegments, ShadowNode, GetShadowNodes } from '../types'
|
import { ActiveNode, GetActiveNode, MetaContext, PathSegments, ShadowNode, GetShadowNodes } from '../types'
|
||||||
|
|
||||||
@@ -11,12 +11,19 @@ export function resolveActive (
|
|||||||
) {
|
) {
|
||||||
let value
|
let value
|
||||||
|
|
||||||
const shadowLength = shadowParent[key] ? shadowParent[key].length : 0
|
const isUpdatingArrayKey = isArray(activeParent)
|
||||||
|
|
||||||
|
let shadowLength
|
||||||
|
if (isUpdatingArrayKey) {
|
||||||
|
shadowLength = shadowParent ? shadowParent.length : 0
|
||||||
|
} else {
|
||||||
|
shadowLength = shadowParent[key] ? shadowParent[key].length : 0
|
||||||
|
}
|
||||||
|
|
||||||
if (shadowLength > 1) {
|
if (shadowLength > 1) {
|
||||||
// Is using freeze useful? Idea is to prevent the user from messing with these options by mistake
|
// Is using freeze useful? Idea is to prevent the user from messing with these options by mistake
|
||||||
const getShadow: GetShadowNodes = () => Object.freeze(clone(shadowParent[key]))
|
const getShadow: GetShadowNodes = () => Object.freeze(clone(isUpdatingArrayKey ? shadowParent : shadowParent[key]))
|
||||||
const getActive: GetActiveNode = () => Object.freeze(clone(activeParent[key]))
|
const getActive: GetActiveNode = () => Object.freeze(clone(isUpdatingArrayKey ? activeParent : activeParent[key]))
|
||||||
|
|
||||||
value = context.resolve(
|
value = context.resolve(
|
||||||
key,
|
key,
|
||||||
@@ -30,6 +37,18 @@ export function resolveActive (
|
|||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
delete activeParent[key]
|
delete activeParent[key]
|
||||||
|
} else if (isUpdatingArrayKey) {
|
||||||
|
// set new values
|
||||||
|
for (const k in value) {
|
||||||
|
activeParent[k] = value[k]
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete old values
|
||||||
|
for (const k in activeParent) {
|
||||||
|
if (!(k in value)) {
|
||||||
|
delete activeParent[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (!hasOwn(activeParent, key) || activeParent[key] !== value) {
|
} else if (!hasOwn(activeParent, key) || activeParent[key] !== value) {
|
||||||
activeParent[key] = value
|
activeParent[key] = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ export function set (
|
|||||||
activeParent[key],
|
activeParent[key],
|
||||||
pathSegments
|
pathSegments
|
||||||
)
|
)
|
||||||
} else if (isArray(value)) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = -1
|
let idx = -1
|
||||||
@@ -83,7 +81,6 @@ export function set (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Update the active data
|
// Step 3: Update the active data
|
||||||
|
|
||||||
resolveActive(context, key, pathSegments, shadowParent, activeParent)
|
resolveActive(context, key, pathSegments, shadowParent, activeParent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+110
-38
@@ -1,3 +1,4 @@
|
|||||||
|
import { isArray, isPlainObject } from '@vue/shared'
|
||||||
import { createProxy, createHandler, setByObject, remove } from '../../src/continuous-object-merge'
|
import { createProxy, createHandler, setByObject, remove } from '../../src/continuous-object-merge'
|
||||||
|
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
@@ -8,9 +9,45 @@ describe('resolve', () => {
|
|||||||
active = {}
|
active = {}
|
||||||
shadow = {}
|
shadow = {}
|
||||||
|
|
||||||
const resolve = (key, pathSegments, getOptions, getCurrentValue) => {
|
const resolve = (_key, _pathSegments, getOptions, _getCurrentValue) => {
|
||||||
const options = getOptions()
|
const options = getOptions()
|
||||||
console.log('RESOLVE', options)
|
console.log('RESOLVE', options)
|
||||||
|
|
||||||
|
const hasArrayOption = options.some(option => isArray(option.value))
|
||||||
|
if (hasArrayOption) {
|
||||||
|
const groupedOptions = {}
|
||||||
|
for (const option of options) {
|
||||||
|
console.log('OPTION', option)
|
||||||
|
if (!isArray(option.value)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const value of option.value) {
|
||||||
|
if (isPlainObject(value) && 'vmid' in value) {
|
||||||
|
groupedOptions[value.vmid] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(groupedOptions)
|
||||||
|
const values = []
|
||||||
|
for (const option of options) {
|
||||||
|
if (!isArray(option.value)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const value of option.value) {
|
||||||
|
if (!isPlainObject(value) || !('vmid' in value)) {
|
||||||
|
values.push(value)
|
||||||
|
} else if (groupedOptions[value.vmid]) {
|
||||||
|
values.push(groupedOptions[value.vmid])
|
||||||
|
delete groupedOptions[value.vmid]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('VALUES', values)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
return options[options.length - 1].value
|
return options[options.length - 1].value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +69,7 @@ describe('resolve', () => {
|
|||||||
|
|
||||||
// Init proxy
|
// Init proxy
|
||||||
const handler1 = createHandler(context1)
|
const handler1 = createHandler(context1)
|
||||||
const proxy1 = createProxy(target1, handler1)
|
/* const proxy1 = */ createProxy(target1, handler1)
|
||||||
|
|
||||||
setByObject(context2, target2)
|
setByObject(context2, target2)
|
||||||
const handler2 = createHandler(context2)
|
const handler2 = createHandler(context2)
|
||||||
@@ -72,7 +109,7 @@ describe('resolve', () => {
|
|||||||
|
|
||||||
// Init proxy
|
// Init proxy
|
||||||
const handler1 = createHandler(context1)
|
const handler1 = createHandler(context1)
|
||||||
const proxy1 = createProxy(target1, handler1)
|
/* const proxy1 = */ createProxy(target1, handler1)
|
||||||
|
|
||||||
setByObject(context2, target2)
|
setByObject(context2, target2)
|
||||||
const handler2 = createHandler(context2)
|
const handler2 = createHandler(context2)
|
||||||
@@ -102,7 +139,7 @@ describe('resolve', () => {
|
|||||||
expect(shadow.obj.key.length).toBe(0)
|
expect(shadow.obj.key.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('resolve (array)', () => {
|
test('resolve (array)', () => {
|
||||||
const target1 = {
|
const target1 = {
|
||||||
arr: [
|
arr: [
|
||||||
'array value 1'
|
'array value 1'
|
||||||
@@ -115,12 +152,8 @@ describe('resolve', () => {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test this, is specifying a resolver enough or do we need to add an array-merge option
|
// Set initial value & init proxy
|
||||||
|
|
||||||
// Set initial value
|
|
||||||
setByObject(context1, target1)
|
setByObject(context1, target1)
|
||||||
|
|
||||||
// Init proxy
|
|
||||||
const handler1 = createHandler(context1)
|
const handler1 = createHandler(context1)
|
||||||
const proxy1 = createProxy(target1, handler1)
|
const proxy1 = createProxy(target1, handler1)
|
||||||
|
|
||||||
@@ -128,47 +161,52 @@ describe('resolve', () => {
|
|||||||
const handler2 = createHandler(context2)
|
const handler2 = createHandler(context2)
|
||||||
const proxy2 = createProxy(target2, handler2)
|
const proxy2 = createProxy(target2, handler2)
|
||||||
|
|
||||||
expect(active.arr).toBe('object value 2')
|
expect(active.arr).toEqual(['array value 1', 'array value 2'])
|
||||||
|
|
||||||
proxy2.obj.key = 'test'
|
proxy2.arr[0] = 'test 2'
|
||||||
|
|
||||||
expect(active.obj.key).toBe('test')
|
expect(active.arr).toEqual(['array value 1', 'test 2'])
|
||||||
|
|
||||||
proxy2.obj = { key: 'test again' }
|
proxy1.arr = ['test 1']
|
||||||
|
|
||||||
expect(active.obj.key).toBe('test again')
|
expect(active.arr).toEqual(['test 1', 'test 2'])
|
||||||
expect(shadow.obj.key.length).toBe(2)
|
expect(shadow.arr.length).toBe(2)
|
||||||
|
|
||||||
remove(context2)
|
|
||||||
|
|
||||||
expect(active.obj.key).toBe('object value 1')
|
|
||||||
|
|
||||||
remove(context1)
|
remove(context1)
|
||||||
|
|
||||||
// TODO: should we clean up the obj ref too?
|
expect(active.arr).toEqual(['test 2'])
|
||||||
expect(active.obj).toEqual({})
|
|
||||||
|
|
||||||
expect(active.obj.key).toBeUndefined()
|
delete proxy2.arr
|
||||||
expect(shadow.obj.key.length).toBe(0)
|
|
||||||
|
// TODO: should we clean up the obj ref too?
|
||||||
|
expect(active.arr).toBeUndefined()
|
||||||
|
expect(shadow.arr.length).toBe(0)
|
||||||
|
|
||||||
|
proxy1.arr = ['test again 1']
|
||||||
|
expect(active.arr).toEqual(['test again 1'])
|
||||||
|
|
||||||
|
proxy2.arr = []
|
||||||
|
proxy2.arr[0] = 'test again 2'
|
||||||
|
expect(active.arr).toEqual(['test again 1', 'test again 2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('resolve (collection)', () => {
|
test('resolve (collection)', () => {
|
||||||
const target1 = {
|
const target1 = {
|
||||||
arr: [
|
arr: [
|
||||||
{ key: 'collection value 1' }
|
{ key: 'collection value 1.1' },
|
||||||
|
{ vmid: 'a', key: 'collection value 1.2' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const target2 = {
|
const target2 = {
|
||||||
arr: [
|
arr: [
|
||||||
{ key: 'collection value 2' }
|
{ vmid: 'a', key: 'collection value 2.1' },
|
||||||
|
{ vmid: 'b', key: 'collection value 2.2' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value
|
// Set initial value & init proxy
|
||||||
setByObject(context1, target1)
|
setByObject(context1, target1)
|
||||||
|
|
||||||
// Init proxy
|
|
||||||
const handler1 = createHandler(context1)
|
const handler1 = createHandler(context1)
|
||||||
const proxy1 = createProxy(target1, handler1)
|
const proxy1 = createProxy(target1, handler1)
|
||||||
|
|
||||||
@@ -176,22 +214,56 @@ describe('resolve', () => {
|
|||||||
const handler2 = createHandler(context2)
|
const handler2 = createHandler(context2)
|
||||||
const proxy2 = createProxy(target2, handler2)
|
const proxy2 = createProxy(target2, handler2)
|
||||||
|
|
||||||
expect(active.str).toBe('string value 2')
|
expect(active.arr).toEqual([
|
||||||
|
{ key: 'collection value 1.1' },
|
||||||
|
{ vmid: 'a', key: 'collection value 2.1' },
|
||||||
|
{ vmid: 'b', key: 'collection value 2.2' }
|
||||||
|
])
|
||||||
|
|
||||||
proxy2.str = 'test'
|
proxy1.arr[0].key = 'test 1.1'
|
||||||
|
proxy1.arr[1].key = 'test 1.2'
|
||||||
|
|
||||||
expect(active.str).toBe('test')
|
expect(active.arr).toEqual([
|
||||||
|
{ key: 'test 1.1' },
|
||||||
|
{ vmid: 'a', key: 'test 1.2' }, // TODO: this is WRONG, should be collection value 2.1 => setting a prop in a collection needs to trigger the resolveActive for the parent array
|
||||||
|
{ vmid: 'b', key: 'collection value 2.2' }
|
||||||
|
])
|
||||||
|
|
||||||
expect(shadow.str).toBeInstanceOf(Array)
|
proxy2.arr = [
|
||||||
expect(shadow.str.length).toBe(2)
|
{ vmid: 'b', key: 'collection value 2.1' },
|
||||||
|
{ vmid: 'c', key: 'collection value 2.2' }
|
||||||
|
]
|
||||||
|
|
||||||
remove(context2)
|
expect(active.arr).toEqual([
|
||||||
|
{ key: 'test 1.1' },
|
||||||
|
{ vmid: 'a', key: 'test 1.2' },
|
||||||
|
{ vmid: 'b', key: 'collection value 2.1' },
|
||||||
|
{ vmid: 'c', key: 'collection value 2.2' }
|
||||||
|
])
|
||||||
|
|
||||||
expect(active.str).toBe('string value 1')
|
expect(shadow.arr.length).toBe(2)
|
||||||
|
|
||||||
remove(context1)
|
remove(context1)
|
||||||
|
|
||||||
expect(active.str).toBeUndefined()
|
expect(active.arr).toEqual([
|
||||||
expect(shadow.str.length).toBe(0)
|
{ vmid: 'b', key: 'collection value 2.1' },
|
||||||
|
{ vmid: 'c', key: 'collection value 2.2' }
|
||||||
|
])
|
||||||
|
|
||||||
|
delete proxy2.arr
|
||||||
|
|
||||||
|
// TODO: should we clean up the obj ref too?
|
||||||
|
expect(active.arr).toBeUndefined()
|
||||||
|
expect(shadow.arr.length).toBe(0)
|
||||||
|
|
||||||
|
proxy1.arr = [{ vmid: 'a', key: 'test again 1' }]
|
||||||
|
expect(active.arr).toEqual([{ vmid: 'a', key: 'test again 1' }])
|
||||||
|
|
||||||
|
// TODO: fix
|
||||||
|
proxy2.arr = []
|
||||||
|
proxy2.arr[0] = { vmid: 'a', value: 'test again 2' }
|
||||||
|
expect(active.arr).toEqual([
|
||||||
|
{ vmid: 'a', key: 'test again 2' }
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user