mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-23 14:10:33 +03:00
feat: refactor of object merge & make vue-router example work
This commit is contained in:
+21
-86
@@ -7,7 +7,7 @@ import {
|
|||||||
watch
|
watch
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { defaultConfig, createManager, useMeta, useMetainfo } from 'vue-meta'
|
import { defaultConfig, createManager, useMeta, useMetainfo, resolveOption } from 'vue-meta'
|
||||||
// import About from './about.vue'
|
// import About from './about.vue'
|
||||||
|
|
||||||
const metaUpdated = 'no'
|
const metaUpdated = 'no'
|
||||||
@@ -31,18 +31,17 @@ const ChildComponent = defineComponent({
|
|||||||
|
|
||||||
const title = props.page[0].toUpperCase() + props.page.slice(1)
|
const title = props.page[0].toUpperCase() + props.page.slice(1)
|
||||||
console.log('ChildComponent Setup')
|
console.log('ChildComponent Setup')
|
||||||
/* useMeta({
|
|
||||||
|
useMeta({
|
||||||
charset: 'utf16',
|
charset: 'utf16',
|
||||||
title,
|
title,
|
||||||
description: 'Description ' + props.page,
|
description: 'Description ' + props.page,
|
||||||
og: {
|
og: {
|
||||||
title: 'Og Title ' + props.page,
|
title: 'Og Title ' + props.page,
|
||||||
},
|
},
|
||||||
}) */
|
})
|
||||||
|
|
||||||
return {
|
return toRefs(state)
|
||||||
...toRefs(state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ const App = {
|
|||||||
{ src: 'body-script2.js', to: 'body' },
|
{ src: 'body-script2.js', to: 'body' },
|
||||||
{ src: 'body-script3.js', to: '#put-it-here' }
|
{ src: 'body-script3.js', to: '#put-it-here' }
|
||||||
],
|
],
|
||||||
esi: {
|
/* esi: {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
tag: 'choose',
|
tag: 'choose',
|
||||||
@@ -138,7 +137,7 @@ const App = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
} */
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => (meta.title = 'My Updated Title'), 2000)
|
setTimeout(() => (meta.title = 'My Updated Title'), 2000)
|
||||||
@@ -206,33 +205,24 @@ const App = {
|
|||||||
<h1>vue-router</h1>
|
<h1>vue-router</h1>
|
||||||
<router-link to="/">Home</router-link>
|
<router-link to="/">Home</router-link>
|
||||||
<router-link to="/about">About</router-link>
|
<router-link to="/about">About</router-link>
|
||||||
<transition name="page" mode="out-in">
|
|
||||||
<router-view></router-view>
|
<router-view v-slot="{ Component }">
|
||||||
</transition>
|
<transition name="page" mode="out-in">
|
||||||
|
<component :is="Component" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
|
|
||||||
<p>Inspect Element to see the meta info</p>
|
<p>Inspect Element to see the meta info</p>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
function decisionMaker5000000 (key, pathSegments, getOptions, getCurrentValue) {
|
const decisionMaker5000000 = resolveOption((prevValue, context) => {
|
||||||
let theChosenOne
|
const { uid = 0 } = context.vm || {}
|
||||||
|
if (!prevValue || prevValue < uid) {
|
||||||
const options = getOptions()
|
return uid
|
||||||
|
|
||||||
for (const option of options) {
|
|
||||||
if (!theChosenOne || theChosenOne.context.vm.uid < option.context.vm.uid) {
|
|
||||||
theChosenOne = option
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
console.log(
|
|
||||||
key,
|
|
||||||
getCurrentValue(),
|
|
||||||
options.map(({ value }) => value)
|
|
||||||
)
|
|
||||||
console.log(theChosenOne.value)
|
|
||||||
return theChosenOne.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const metaManager = createManager({
|
const metaManager = createManager({
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
@@ -243,14 +233,14 @@ const metaManager = createManager({
|
|||||||
}
|
}
|
||||||
}, decisionMaker5000000)
|
}, decisionMaker5000000)
|
||||||
|
|
||||||
/* useMeta(
|
useMeta(
|
||||||
{
|
{
|
||||||
og: {
|
og: {
|
||||||
something: 'test',
|
something: 'test',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
metaManager
|
metaManager
|
||||||
) */
|
) /**/
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory('/vue-router'),
|
history: createWebHistory('/vue-router'),
|
||||||
@@ -264,58 +254,3 @@ const app = createApp(App)
|
|||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(metaManager)
|
app.use(metaManager)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|
||||||
// old stuff:
|
|
||||||
/*
|
|
||||||
const { set, remove } = app.$meta().addApp('custom')
|
|
||||||
|
|
||||||
set({
|
|
||||||
bodyAttrs: {
|
|
||||||
class: 'custom-app'
|
|
||||||
},
|
|
||||||
meta: [
|
|
||||||
{ charset: 'utf=8' }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
setTimeout(() => remove(), 3000)
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
const waitFor = time => new Promise(r => setTimeout(r, time || 1000))
|
|
||||||
const o = {
|
|
||||||
meta: [{ a: 1 }]
|
|
||||||
}
|
|
||||||
const ob = Vue.observable(o)
|
|
||||||
|
|
||||||
const root = new Vue({
|
|
||||||
beforeCreate() {
|
|
||||||
this.meta = ob.meta
|
|
||||||
|
|
||||||
this.$options.computed = this.$options.computed || {}
|
|
||||||
this.$options.computed['$ob'] = () => {
|
|
||||||
return { meta: this.meta }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
console.log('HERE')
|
|
||||||
this.$watch('$ob', (a, b) => {
|
|
||||||
console.log('WATCHER', this.$ob.meta[0].a, a.meta[0].a, b.meta[0].a, diff(a, b))
|
|
||||||
}, { deep: true })
|
|
||||||
},
|
|
||||||
render(h) {
|
|
||||||
return h('div', null, 'test')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function main () {
|
|
||||||
root.$mount('#app')
|
|
||||||
console.log(root)
|
|
||||||
await waitFor(500)
|
|
||||||
|
|
||||||
root.meta[0].a = 2
|
|
||||||
await waitFor(100)
|
|
||||||
|
|
||||||
ob.meta[0].a = 3
|
|
||||||
await waitFor(100)
|
|
||||||
}
|
|
||||||
main()
|
|
||||||
/**/
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { VueLoaderPlugin } from 'vue-loader'
|
|||||||
// const srcDir = path.join(__dirname, '..', 'src')
|
// const srcDir = path.join(__dirname, '..', 'src')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
devtool: 'inline-source-map',
|
devtool: 'source-map',
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
entry: fs.readdirSync(__dirname)
|
entry: fs.readdirSync(__dirname)
|
||||||
.reduce((entries, dir) => {
|
.reduce((entries, dir) => {
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ module.exports = {
|
|||||||
coverageDirectory: './coverage',
|
coverageDirectory: './coverage',
|
||||||
|
|
||||||
collectCoverageFrom: [
|
collectCoverageFrom: [
|
||||||
'**/src/**/*.[tj]s'
|
'src/**/*.[tj]s'
|
||||||
],
|
],
|
||||||
|
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
|
|||||||
+36
-36
@@ -48,40 +48,40 @@
|
|||||||
"vue": "next"
|
"vue": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.3",
|
"@babel/core": "^7.12.10",
|
||||||
"@babel/node": "^7.12.1",
|
"@babel/node": "^7.12.10",
|
||||||
"@babel/preset-env": "^7.12.1",
|
"@babel/preset-env": "^7.12.11",
|
||||||
"@babel/preset-typescript": "^7.12.1",
|
"@babel/preset-typescript": "^7.12.7",
|
||||||
"@nuxtjs/eslint-config-typescript": "^4.0.0",
|
"@nuxtjs/eslint-config-typescript": "^5.0.0",
|
||||||
"@types/webpack": "^4.41.24",
|
"@types/webpack": "^4.41.26",
|
||||||
"@types/webpack-env": "^1.15.3",
|
"@types/webpack-env": "^1.16.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||||
"@typescript-eslint/parser": "^4.6.0",
|
"@typescript-eslint/parser": "^4.13.0",
|
||||||
"@vue/compiler-sfc": "^3.0.2",
|
"@vue/compiler-sfc": "^3.0.5",
|
||||||
"@vue/server-renderer": "^3.0.2",
|
"@vue/server-renderer": "^3.0.5",
|
||||||
"@vue/server-test-utils": "^1.1.1",
|
"@vue/server-test-utils": "^1.1.2",
|
||||||
"@vue/test-utils": "^1.1.1",
|
"@vue/test-utils": "^1.1.2",
|
||||||
"@wishy-gift/html-include-chunks-webpack-plugin": "^0.1.5",
|
"@wishy-gift/html-include-chunks-webpack-plugin": "^0.1.5",
|
||||||
"babel-jest": "^26.6.1",
|
"babel-jest": "^26.6.3",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.2.2",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"browserstack-local": "^1.4.8",
|
"browserstack-local": "^1.4.8",
|
||||||
"chromedriver": "^86.0.0",
|
"chromedriver": "^87.0.5",
|
||||||
"codecov": "^3.8.0",
|
"codecov": "^3.8.1",
|
||||||
"consola": "^2.15.0",
|
"consola": "^2.15.0",
|
||||||
"eslint": "^7.12.1",
|
"eslint": "^7.18.0",
|
||||||
"express-urlrewrite": "^1.3.0",
|
"express-urlrewrite": "^1.4.0",
|
||||||
"geckodriver": "^1.20.0",
|
"geckodriver": "^1.21.1",
|
||||||
"html-webpack-plugin": "^4.5.0",
|
"html-webpack-plugin": "^4.5.1",
|
||||||
"jest": "^26.6.1",
|
"jest": "^26.6.3",
|
||||||
"jest-environment-jsdom": "^26.6.1",
|
"jest-environment-jsdom": "^26.6.2",
|
||||||
"jest-environment-jsdom-global": "^2.0.4",
|
"jest-environment-jsdom-global": "^2.0.4",
|
||||||
"jsdom": "^16.4.0",
|
"jsdom": "^16.4.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"node-env-file": "^0.1.8",
|
"node-env-file": "^0.1.8",
|
||||||
"puppeteer-core": "^5.4.1",
|
"puppeteer-core": "^5.5.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.33.0",
|
"rollup": "^2.36.2",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"rollup-plugin-json": "^4.0.0",
|
"rollup-plugin-json": "^4.0.0",
|
||||||
@@ -89,21 +89,21 @@
|
|||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-typescript2": "^0.29.0",
|
"rollup-plugin-typescript2": "^0.29.0",
|
||||||
"selenium-webdriver": "^4.0.0-alpha.7",
|
"selenium-webdriver": "^4.0.0-alpha.8",
|
||||||
"standard-version": "^9.0.0",
|
"standard-version": "^9.1.0",
|
||||||
"tib": "^0.7.4",
|
"tib": "^0.7.5",
|
||||||
"ts-jest": "^26.4.3",
|
"ts-jest": "^26.4.4",
|
||||||
"ts-loader": "^8.0.7",
|
"ts-loader": "^8.0.14",
|
||||||
"ts-node": "^9.0.0",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.0.5",
|
"typescript": "^4.1.3",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-jest": "^3.0.7",
|
"vue-jest": "^3.0.7",
|
||||||
"vue-loader": "^16.0.0",
|
"vue-loader": "^16.0.0",
|
||||||
"vue-router": "next",
|
"vue-router": "next",
|
||||||
"webpack": "^5.3.2",
|
"webpack": "^5.15.0",
|
||||||
"webpack-bundle-analyzer": "^3.9.0",
|
"webpack-bundle-analyzer": "^4.3.0",
|
||||||
"webpack-cli": "^4.1.0",
|
"webpack-cli": "^4.3.1",
|
||||||
"webpack-dev-server": "^3.11.0",
|
"webpack-dev-server": "^3.11.2",
|
||||||
"webpackbar": "^4.0.0"
|
"webpackbar": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export * from './proxy'
|
|
||||||
export * from './remove'
|
|
||||||
export * from './set'
|
|
||||||
export * from './update'
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { markRaw } from 'vue'
|
|
||||||
import { isObject } from '@vue/shared'
|
|
||||||
import { MetaContext, MetainfoInput, MetainfoProxy, PathSegments } from '../types'
|
|
||||||
import { update } from './update'
|
|
||||||
import { remove } from './remove'
|
|
||||||
|
|
||||||
export function createProxy (target: MetainfoInput, handler: ProxyHandler<object>): MetainfoProxy {
|
|
||||||
return markRaw(new Proxy(target, handler))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createHandler (context: MetaContext, pathSegments: PathSegments = []): ProxyHandler<object> {
|
|
||||||
return {
|
|
||||||
get (target: object, key: string, receiver: object) {
|
|
||||||
const value = Reflect.get(target, key, receiver)
|
|
||||||
|
|
||||||
if (!isObject(value) || key === '__vm_proxy') {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value.__vm_proxy) {
|
|
||||||
const keyPath: PathSegments = [...pathSegments, key]
|
|
||||||
|
|
||||||
const handler = /* #__PURE__ */ createHandler(context, keyPath)
|
|
||||||
Object.defineProperty(
|
|
||||||
value,
|
|
||||||
'__vm_proxy',
|
|
||||||
{
|
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
|
||||||
writable: false,
|
|
||||||
value: createProxy(value, handler)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value.__vm_proxy
|
|
||||||
},
|
|
||||||
set (
|
|
||||||
target: { [key: string]: any }, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
||||||
key: string,
|
|
||||||
value: any
|
|
||||||
): boolean {
|
|
||||||
const success = Reflect.set(target, key, value)
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
update(context, pathSegments, key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return success
|
|
||||||
},
|
|
||||||
deleteProperty (
|
|
||||||
target: { [key: string]: any },
|
|
||||||
prop: string
|
|
||||||
) {
|
|
||||||
const success = Reflect.deleteProperty(target, prop)
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
remove(context, pathSegments, prop)
|
|
||||||
}
|
|
||||||
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { MetaContext, PathSegments } from '../types'
|
|
||||||
import { setByObject } from './set'
|
|
||||||
import { update } from './update'
|
|
||||||
|
|
||||||
export function remove (context: MetaContext, pathSegments?: PathSegments, key?: string) {
|
|
||||||
if (!key && (!pathSegments || !pathSegments.length)) {
|
|
||||||
setByObject(context, {})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
update(context, pathSegments || [], key || '', undefined)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { hasOwn, isArray } from '@vue/shared'
|
|
||||||
import { clone } from '../utils'
|
|
||||||
import { ActiveNode, GetActiveNode, MetaContext, PathSegments, ShadowNode, GetShadowNodes } from '../types'
|
|
||||||
|
|
||||||
export function resolveActive (
|
|
||||||
context: MetaContext,
|
|
||||||
key: string,
|
|
||||||
pathSegments: PathSegments,
|
|
||||||
shadowParent: ShadowNode,
|
|
||||||
activeParent: ActiveNode
|
|
||||||
) {
|
|
||||||
let value
|
|
||||||
|
|
||||||
const isUpdatingArrayKey = isArray(activeParent)
|
|
||||||
|
|
||||||
let shadowLength
|
|
||||||
if (isUpdatingArrayKey) {
|
|
||||||
shadowLength = shadowParent ? shadowParent.length : 0
|
|
||||||
} else {
|
|
||||||
shadowLength = shadowParent[key] ? shadowParent[key].length : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shadowLength > 1) {
|
|
||||||
// Is using freeze useful? Idea is to prevent the user from messing with these options by mistake
|
|
||||||
const getShadow: GetShadowNodes = () => Object.freeze(clone(isUpdatingArrayKey ? shadowParent : shadowParent[key]))
|
|
||||||
const getActive: GetActiveNode = () => Object.freeze(clone(isUpdatingArrayKey ? activeParent : activeParent[key]))
|
|
||||||
|
|
||||||
value = context.resolve(
|
|
||||||
key,
|
|
||||||
pathSegments,
|
|
||||||
getShadow,
|
|
||||||
getActive
|
|
||||||
)
|
|
||||||
} else if (shadowLength) {
|
|
||||||
value = shadowParent[key][0].value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === undefined) {
|
|
||||||
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) {
|
|
||||||
activeParent[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { isArray, isPlainObject, /**/ hasOwn } from '@vue/shared'
|
|
||||||
import { ActiveNode, MetaContext, PathSegments, ShadowNode } from '../types'
|
|
||||||
import { resolveActive } from './resolve'
|
|
||||||
|
|
||||||
export function set (
|
|
||||||
context: MetaContext,
|
|
||||||
key: string,
|
|
||||||
value: any,
|
|
||||||
shadowParent?: ShadowNode,
|
|
||||||
activeParent?: ActiveNode,
|
|
||||||
pathSegments: PathSegments = []
|
|
||||||
) {
|
|
||||||
if (!shadowParent) {
|
|
||||||
shadowParent = context.shadow
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeParent) {
|
|
||||||
activeParent = context.active
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPlainObject(value)) {
|
|
||||||
// shadow & active should always be in sync
|
|
||||||
// if not we have bigger fish to fry
|
|
||||||
if (!shadowParent[key]) {
|
|
||||||
shadowParent[key] = {}
|
|
||||||
activeParent[key] = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return setByObject(
|
|
||||||
context,
|
|
||||||
value,
|
|
||||||
shadowParent[key],
|
|
||||||
activeParent[key],
|
|
||||||
pathSegments
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = -1
|
|
||||||
|
|
||||||
// Step 1: First find the current data for this key
|
|
||||||
|
|
||||||
// If the activeParent is an array itself, then we are setting the key
|
|
||||||
// for an array, so no need to use [key] on the active/shadow parents
|
|
||||||
// ie this is to support proxy.myArrayValue[1] = 'value' (instead of proxy.myArrayValue = ['value'])
|
|
||||||
if (isArray(activeParent)) {
|
|
||||||
idx = shadowParent.findIndex(({ context: shadowContext }: { context: MetaContext }) => shadowContext === context)
|
|
||||||
// Check if we already have values from other components
|
|
||||||
} else if (!shadowParent[key]) {
|
|
||||||
shadowParent[key] = []
|
|
||||||
// If we already have values listed, try to find the one for the current context
|
|
||||||
} else if (isArray(shadowParent[key])) {
|
|
||||||
// check if we already have a value listed for this element for this context
|
|
||||||
idx = shadowParent[key].findIndex(({ context: shadowContext }: { context: MetaContext }) => shadowContext === context)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Set/update the data for this key
|
|
||||||
|
|
||||||
if (isArray(activeParent) && idx < 0) {
|
|
||||||
// TODO: what now?
|
|
||||||
console.warn('shadowParent not found')
|
|
||||||
|
|
||||||
// Change the array/key element in the shadowParent
|
|
||||||
} else if (isArray(activeParent)) {
|
|
||||||
if (value === undefined) {
|
|
||||||
shadowParent[idx].value.splice(key, 1)
|
|
||||||
} else {
|
|
||||||
shadowParent[idx].value[key] = value
|
|
||||||
}
|
|
||||||
// if this context/key combo exists but value is undefined, remove it
|
|
||||||
} else if (idx > -1 && value === undefined) {
|
|
||||||
shadowParent[key].splice(idx, 1)
|
|
||||||
// overwrite current value for context/key combo
|
|
||||||
} else if (idx > -1) {
|
|
||||||
shadowParent[key][idx].value = value
|
|
||||||
|
|
||||||
// new context/key combo so just add value
|
|
||||||
} else if (idx === -1 && value) {
|
|
||||||
shadowParent[key].push({ context, value })
|
|
||||||
} else if (value === undefined) {
|
|
||||||
delete shadowParent[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Update the active data
|
|
||||||
resolveActive(context, key, pathSegments, shadowParent, activeParent)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setByObject (
|
|
||||||
context: MetaContext,
|
|
||||||
value: any,
|
|
||||||
shadowParent? : ShadowNode,
|
|
||||||
activeParent?: ActiveNode,
|
|
||||||
pathSegments: PathSegments = []
|
|
||||||
) {
|
|
||||||
if (!shadowParent) {
|
|
||||||
shadowParent = context.shadow
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!activeParent) {
|
|
||||||
activeParent = context.active
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup properties that no longer exists
|
|
||||||
for (const key in shadowParent) {
|
|
||||||
if (hasOwn(value, key)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPlainObject(shadowParent[key])) {
|
|
||||||
setByObject(context, {}, shadowParent[key], activeParent[key], [
|
|
||||||
...pathSegments,
|
|
||||||
key
|
|
||||||
])
|
|
||||||
continue
|
|
||||||
} /**/
|
|
||||||
|
|
||||||
set(context, key, undefined, shadowParent, activeParent, [
|
|
||||||
...pathSegments,
|
|
||||||
key
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
// set new values
|
|
||||||
for (const key in value) {
|
|
||||||
set(context, key, value[key], shadowParent, activeParent, [
|
|
||||||
...pathSegments,
|
|
||||||
key
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { MetaContext, PathSegments, ShadowNode, ActiveNode } from '../types'
|
|
||||||
import { set } from './set'
|
|
||||||
|
|
||||||
export function update (
|
|
||||||
context: MetaContext,
|
|
||||||
pathSegments: PathSegments,
|
|
||||||
key: string,
|
|
||||||
value: any
|
|
||||||
) {
|
|
||||||
const { active, shadow } = context
|
|
||||||
|
|
||||||
let activeParent: ActiveNode = active
|
|
||||||
let shadowParent: ShadowNode = shadow
|
|
||||||
|
|
||||||
for (const segment of pathSegments) {
|
|
||||||
activeParent = activeParent[segment]
|
|
||||||
shadowParent = shadowParent[segment]
|
|
||||||
}
|
|
||||||
|
|
||||||
set(context, key, value, shadowParent, activeParent)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export { defaultConfig } from './config'
|
export { defaultConfig } from './config'
|
||||||
export { createManager } from './manager'
|
export { createManager } from './manager'
|
||||||
|
export { resolveOption } from './resolvers'
|
||||||
export * from './useApi'
|
export * from './useApi'
|
||||||
export * from './types'
|
export * from './types'
|
||||||
|
|||||||
+19
-33
@@ -1,36 +1,31 @@
|
|||||||
import { App, markRaw, reactive, onUnmounted, ComponentInternalInstance } from 'vue'
|
import { App, reactive, onUnmounted, ComponentInternalInstance } from 'vue'
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { createProxy, createHandler, setByObject, remove } from './continuous-object-merge'
|
import { createMergedObject } from './object-merge'
|
||||||
import { PolySymbol } from './symbols'
|
|
||||||
import { applyMetaPlugin } from './install'
|
import { applyMetaPlugin } from './install'
|
||||||
// import * as deepestResolver from './resolvers/deepest'
|
// import * as deepestResolver from './resolvers/deepest'
|
||||||
import { Config, ActiveResolverObject, ActiveResolverMethod, MetaContext, MetainfoInput, MetaProxy } from './types'
|
import { Config, Resolver, MetainfoInput, MetaContext, MetaProxy } from './types'
|
||||||
|
import type { ResolveMethod } from './object-merge'
|
||||||
let contextCounter: number = 0
|
|
||||||
|
|
||||||
export type Manager = {
|
export type Manager = {
|
||||||
readonly config: Config
|
readonly config: Config
|
||||||
|
|
||||||
install(app: App): void
|
install(app: App): void
|
||||||
createMetaProxy(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
|
addMeta(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shadow = markRaw({})
|
|
||||||
export const active = reactive({})
|
export const active = reactive({})
|
||||||
|
|
||||||
export function createManager (config: Config, resolver: ActiveResolverObject): Manager {
|
export function createManager (config: Config, resolver: Resolver | ResolveMethod): Manager {
|
||||||
const resolve: ActiveResolverMethod = (key, pathSegments, getShadow, getActive) => {
|
const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => {
|
||||||
if (!resolver) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFunction(resolver)) {
|
if (isFunction(resolver)) {
|
||||||
return resolver(key, pathSegments, getShadow, getActive)
|
return resolver(options, contexts, active, key, pathSegments)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolver.resolve(key, pathSegments, getShadow, getActive)
|
return resolver.resolve(options, contexts, active, key, pathSegments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { addSource, delSource } = createMergedObject(resolve, active)
|
||||||
|
|
||||||
// TODO: validate resolver
|
// TODO: validate resolver
|
||||||
const manager: Manager = {
|
const manager: Manager = {
|
||||||
config,
|
config,
|
||||||
@@ -39,29 +34,20 @@ export function createManager (config: Config, resolver: ActiveResolverObject):
|
|||||||
applyMetaPlugin(app, this, active)
|
applyMetaPlugin(app, this, active)
|
||||||
},
|
},
|
||||||
|
|
||||||
createMetaProxy (obj, vm) {
|
addMeta (metaObj, vm) {
|
||||||
const context: MetaContext = {
|
const resolveContext: MetaContext = { vm }
|
||||||
id: PolySymbol(`ctx${contextCounter++}`),
|
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||||
vm: vm || undefined,
|
resolver.setup(resolveContext)
|
||||||
resolve,
|
|
||||||
shadow,
|
|
||||||
active
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const unmount = <T extends Function = () => any>() => remove(context)
|
// TODO: optimize initial compute
|
||||||
|
const meta = addSource(metaObj, resolveContext, true)
|
||||||
|
|
||||||
|
const unmount = () => delSource(meta)
|
||||||
if (vm) {
|
if (vm) {
|
||||||
onUnmounted(unmount)
|
onUnmounted(unmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resolver && resolver.setup && isFunction(resolver)) {
|
|
||||||
resolver.setup(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
setByObject(context, obj)
|
|
||||||
|
|
||||||
const handler = /* #__PURE__ */ createHandler(context)
|
|
||||||
const meta = createProxy(obj, handler)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
meta,
|
meta,
|
||||||
unmount
|
unmount
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// https://github.com/microsoft/TypeScript/issues/1863
|
||||||
|
export const IS_PROXY = Symbol('kIsProxy') as unknown as string
|
||||||
|
export const PROXY_SOURCES = Symbol('kProxySources') as unknown as string
|
||||||
|
export const PROXY_TARGET = Symbol('kProxyTarget') as unknown as string
|
||||||
|
export const RESOLVE_CONTEXT = Symbol('kResolveContext') as unknown as string
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { PROXY_TARGET } from './constants'
|
||||||
|
import { createProxy } from './proxy'
|
||||||
|
import { recompute } from './recompute'
|
||||||
|
|
||||||
|
export type MergeSource = {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MergedObjectValue = boolean | number | string | MergedObject | any
|
||||||
|
|
||||||
|
export type MergedObject = {
|
||||||
|
[key: string]: MergedObjectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PathSegments = Array<string>
|
||||||
|
|
||||||
|
export type ResolveContext = {}
|
||||||
|
|
||||||
|
export type ResolveMethod = (
|
||||||
|
options: Array<any>,
|
||||||
|
contexts: Array<ResolveContext>,
|
||||||
|
active: MergedObjectValue,
|
||||||
|
key: string | number | symbol,
|
||||||
|
pathSegments: PathSegments,
|
||||||
|
) => MergedObjectValue
|
||||||
|
|
||||||
|
export type MergeContext = {
|
||||||
|
resolve: ResolveMethod
|
||||||
|
active: MergedObject
|
||||||
|
sources: Array<MergeSource>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createMergedObject = (resolve: ResolveMethod, active: MergedObject = {}) => {
|
||||||
|
const sources: Array<MergeSource> = []
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
active = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const context: MergeContext = {
|
||||||
|
active,
|
||||||
|
resolve,
|
||||||
|
sources
|
||||||
|
}
|
||||||
|
|
||||||
|
const compute = () => recompute(context)
|
||||||
|
|
||||||
|
const addSource = (source: MergeSource, resolveContext: ResolveContext | undefined, recompute: Boolean = false) => {
|
||||||
|
const proxy = createProxy(context, source, resolveContext || {})
|
||||||
|
|
||||||
|
if (recompute) {
|
||||||
|
compute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
const delSource = (sourceOrProxy: MergeSource, recompute: boolean = true): boolean => {
|
||||||
|
const index = sources.findIndex(src => src === sourceOrProxy || src[PROXY_TARGET] === sourceOrProxy)
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
sources.splice(index, 1)
|
||||||
|
|
||||||
|
if (recompute) {
|
||||||
|
compute()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
active,
|
||||||
|
resolve,
|
||||||
|
sources,
|
||||||
|
addSource,
|
||||||
|
delSource,
|
||||||
|
compute
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import { markRaw } from 'vue'
|
||||||
|
import { isArray, isObject, isPlainObject } from '@vue/shared'
|
||||||
|
import { clone, pluck } from '../utils'
|
||||||
|
import { IS_PROXY, PROXY_SOURCES, PROXY_TARGET, RESOLVE_CONTEXT } from './constants'
|
||||||
|
import { recompute } from './recompute'
|
||||||
|
import type { MergeContext, MergeSource, MergedObjectValue, PathSegments, ResolveContext } from '.'
|
||||||
|
|
||||||
|
export const createProxy = (context: MergeContext, target: MergeSource, resolveContext: ResolveContext, pathSegments: PathSegments = []) => {
|
||||||
|
const handler = createHandler(context, resolveContext, pathSegments)
|
||||||
|
const proxy = markRaw(new Proxy(target, handler))
|
||||||
|
|
||||||
|
if (!pathSegments.length && context.sources) {
|
||||||
|
context.sources.push(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createHandler: (context: MergeContext, resolveContext: ResolveContext, pathSegments: PathSegments) => ProxyHandler<any> = (context, resolveContext, pathSegments = []) => ({
|
||||||
|
get: (target, key, receiver) => {
|
||||||
|
if (key === IS_PROXY) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === PROXY_SOURCES) {
|
||||||
|
return context.sources
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === PROXY_TARGET) {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === RESOLVE_CONTEXT) {
|
||||||
|
return resolveContext
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = Reflect.get(target, key, receiver)
|
||||||
|
|
||||||
|
if (!isObject(value)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value[IS_PROXY]) {
|
||||||
|
const keyPath: PathSegments = [...pathSegments, (key as string)]
|
||||||
|
|
||||||
|
value = createProxy(context, value, resolveContext, keyPath)
|
||||||
|
target[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
},
|
||||||
|
set: (target, key, value) => {
|
||||||
|
const success = Reflect.set(target, key, value)
|
||||||
|
console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
const isArrayItem = isArray(target)
|
||||||
|
let hasArrayParent = false
|
||||||
|
|
||||||
|
let { sources: proxies, active } = context
|
||||||
|
let activeSegmentKey
|
||||||
|
|
||||||
|
let index = 0
|
||||||
|
for (const segment of pathSegments) {
|
||||||
|
proxies = pluck(proxies, segment)
|
||||||
|
|
||||||
|
if (isArrayItem && index === pathSegments.length - 1) {
|
||||||
|
activeSegmentKey = segment
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(active)) {
|
||||||
|
hasArrayParent = true
|
||||||
|
}
|
||||||
|
|
||||||
|
active = active[segment]
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasArrayParent) {
|
||||||
|
// TODO: fix that we dont have to recompute the full merged object
|
||||||
|
// we should only have to recompute the branch that has changed
|
||||||
|
// but there is an issue here with supporting both arrays of strings
|
||||||
|
// as collections (parent vs parent of parent we need to trigger the
|
||||||
|
// update from)
|
||||||
|
recompute(context)
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
let keyContexts: Array<ResolveContext> = []
|
||||||
|
let keySources
|
||||||
|
|
||||||
|
if (isArrayItem) {
|
||||||
|
keySources = proxies
|
||||||
|
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT])
|
||||||
|
} else {
|
||||||
|
keySources = pluck(proxies, key as string, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]))
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolved = context.resolve(
|
||||||
|
keySources,
|
||||||
|
keyContexts,
|
||||||
|
active,
|
||||||
|
key,
|
||||||
|
pathSegments
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure to clone if value is an object, cause sources is an array of
|
||||||
|
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||||
|
// updating a prop on an obj as the prop on the active object refers to
|
||||||
|
// a prop on a proxy
|
||||||
|
if (isPlainObject(resolved)) {
|
||||||
|
resolved = clone(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||||
|
|
||||||
|
if (isArrayItem && activeSegmentKey) {
|
||||||
|
active[activeSegmentKey] = resolved
|
||||||
|
} else {
|
||||||
|
active[(key as string)] = resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
|
||||||
|
return success
|
||||||
|
},
|
||||||
|
deleteProperty: (target, key) => {
|
||||||
|
const success = Reflect.deleteProperty(target, key)
|
||||||
|
console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
const isArrayItem = isArray(target)
|
||||||
|
let activeSegmentKey
|
||||||
|
|
||||||
|
let proxies = context.sources
|
||||||
|
let active: MergedObjectValue = context.active as MergedObjectValue
|
||||||
|
|
||||||
|
let index = 0
|
||||||
|
for (const segment of pathSegments) {
|
||||||
|
proxies = proxies.map(proxy => proxy[segment])
|
||||||
|
|
||||||
|
if (isArrayItem && index === pathSegments.length - 1) {
|
||||||
|
activeSegmentKey = segment
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
active = active[segment]
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the key still exists in one of the sourceProxies,
|
||||||
|
// if so resolve the new value, if not remove the key
|
||||||
|
if (proxies.some(proxy => (key in proxy))) {
|
||||||
|
let keyContexts: Array<ResolveContext> = []
|
||||||
|
let keySources
|
||||||
|
|
||||||
|
if (isArrayItem) {
|
||||||
|
keySources = proxies
|
||||||
|
keyContexts = proxies.map(proxy => proxy[RESOLVE_CONTEXT])
|
||||||
|
} else {
|
||||||
|
keySources = pluck(proxies, key as string, proxy => keyContexts.push(proxy[RESOLVE_CONTEXT]))
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolved = context.resolve(
|
||||||
|
keySources,
|
||||||
|
keyContexts,
|
||||||
|
active,
|
||||||
|
key,
|
||||||
|
pathSegments
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isPlainObject(resolved)) {
|
||||||
|
resolved = clone(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('SET VALUE', resolved)
|
||||||
|
if (isArrayItem && activeSegmentKey) {
|
||||||
|
active[activeSegmentKey] = resolved
|
||||||
|
} else {
|
||||||
|
active[(key as string)] = resolved
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete active[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { isArray, isObject, isPlainObject } from '@vue/shared'
|
||||||
|
import { clone, pluck } from '../utils'
|
||||||
|
import { RESOLVE_CONTEXT } from './constants'
|
||||||
|
import type { MergeContext, MergeSource, MergedObject, PathSegments, ResolveContext } from '.'
|
||||||
|
|
||||||
|
export const allKeys = (source?: MergeSource, ...sources: Array<MergeSource>): Array<string> => {
|
||||||
|
const keys = source ? Object.keys(source) : []
|
||||||
|
|
||||||
|
if (sources) {
|
||||||
|
for (const source of sources) {
|
||||||
|
if (!source || !isObject(source)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in source) {
|
||||||
|
if (!keys.includes(key)) {
|
||||||
|
keys.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add check for consistent types for each key (dev only)
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
export const recompute = (context: MergeContext, sources?: Array<MergeSource>, target?: MergedObject, path: PathSegments = []): void => {
|
||||||
|
if (!path.length) {
|
||||||
|
if (!target) {
|
||||||
|
target = context.active
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sources) {
|
||||||
|
sources = context.sources
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target || !sources) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = allKeys(...sources)
|
||||||
|
|
||||||
|
// Clean up properties that dont exists anymore
|
||||||
|
const targetKeys = Object.keys(target)
|
||||||
|
for (const key of targetKeys) {
|
||||||
|
if (!keys.includes(key)) {
|
||||||
|
delete target[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
// This assumes consistent types usages for keys across sources
|
||||||
|
if (isPlainObject(sources[0][key])) {
|
||||||
|
if (!target[key]) {
|
||||||
|
target[key] = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const keySources = []
|
||||||
|
for (const source of sources) {
|
||||||
|
if (key in source) {
|
||||||
|
keySources.push(source[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recompute(context, keySources, target[key], [...path, key])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the target is an array if source is an array and target is empty
|
||||||
|
if (!target[key] && isArray(sources[0][key])) {
|
||||||
|
target[key] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyContexts: Array<ResolveContext> = []
|
||||||
|
const keySources = pluck(sources, key, source => keyContexts.push(source[RESOLVE_CONTEXT]))
|
||||||
|
|
||||||
|
let resolved = context.resolve(
|
||||||
|
keySources,
|
||||||
|
keyContexts,
|
||||||
|
target[key],
|
||||||
|
key,
|
||||||
|
path
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isPlainObject(resolved)) {
|
||||||
|
resolved = clone(resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||||
|
target[key] = resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-37
@@ -1,17 +1,12 @@
|
|||||||
import {
|
import { ResolveContext, ResolveMethod } from '../object-merge'
|
||||||
// ActiveNode,
|
import { MetaContext } from '../types'
|
||||||
/* ActiveResolverSetup, ActiveResolverMethod, */ MetaContext,
|
import { resolveOption } from '.'
|
||||||
PathSegments,
|
|
||||||
ShadowNode,
|
|
||||||
GetActiveNode,
|
|
||||||
GetShadowNodes
|
|
||||||
} from '../types'
|
|
||||||
|
|
||||||
interface DeepestResolverMetaContext extends MetaContext {
|
type MergeContextDeepest = MetaContext & {
|
||||||
depth?: number
|
depth: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setup (context: DeepestResolverMetaContext): void {
|
export function setup (context: MergeContextDeepest): void {
|
||||||
let depth: number = 0
|
let depth: number = 0
|
||||||
|
|
||||||
if (context.vm) {
|
if (context.vm) {
|
||||||
@@ -29,30 +24,9 @@ export function setup (context: DeepestResolverMetaContext): void {
|
|||||||
context.depth = depth
|
context.depth = depth
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolve (
|
export const resolve: ResolveMethod = resolveOption((acc: any, context: ResolveContext) => {
|
||||||
key: string,
|
const { depth } = (context as unknown as MergeContextDeepest)
|
||||||
_pathSegments: PathSegments,
|
if (!acc || depth > acc) {
|
||||||
getOptions: GetShadowNodes,
|
return acc
|
||||||
getCurrentValue: GetActiveNode
|
|
||||||
): any {
|
|
||||||
let resolvedOption: ShadowNode | void
|
|
||||||
|
|
||||||
const options = getOptions()
|
|
||||||
|
|
||||||
for (const option of options) {
|
|
||||||
if (!resolvedOption || resolvedOption.context.depth < option.context.depth) {
|
|
||||||
resolvedOption = option
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
console.log(
|
|
||||||
'DEEPEST.RESOLVE',
|
|
||||||
key,
|
|
||||||
getCurrentValue(),
|
|
||||||
options.map(({ value }) => value)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (resolvedOption) {
|
|
||||||
return resolvedOption.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+22
-1
@@ -1 +1,22 @@
|
|||||||
export interface Resolver {}
|
import { ResolveContext, ResolveMethod } from '../object-merge'
|
||||||
|
|
||||||
|
export type ResolveOptionReducer = (accumulator: any, context: ResolveContext) => ResolveMethod
|
||||||
|
|
||||||
|
export const resolveOption: (predicament: ResolveOptionReducer) => ResolveMethod = predicament => (options, contexts) => {
|
||||||
|
let resolvedIndex = -1
|
||||||
|
|
||||||
|
contexts.reduce((acc: ResolveContext | undefined, context, index) => {
|
||||||
|
const retval = predicament(acc, context)
|
||||||
|
|
||||||
|
if (retval !== acc) {
|
||||||
|
resolvedIndex = index
|
||||||
|
return retval
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, undefined)
|
||||||
|
|
||||||
|
if (resolvedIndex > -1) {
|
||||||
|
return options[resolvedIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+14
-46
@@ -1,11 +1,19 @@
|
|||||||
import { ComponentInternalInstance } from 'vue'
|
import { ComponentInternalInstance } from 'vue'
|
||||||
|
import type { MergedObject, ResolveContext, ResolveMethod } from '../object-merge'
|
||||||
|
|
||||||
export type Immutable<T> = {
|
export type Immutable<T> = {
|
||||||
readonly [P in keyof T]: Immutable<T[P]>
|
readonly [P in keyof T]: Immutable<T[P]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TODO = any
|
export type TODO = any
|
||||||
export type PathSegments = Array<string>
|
|
||||||
|
export type MetainfoInput = {
|
||||||
|
[key: string]: TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetaContext = ResolveContext & {
|
||||||
|
vm: ComponentInternalInstance | undefined
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfigOption {
|
export interface ConfigOption {
|
||||||
tag?: string
|
tag?: string
|
||||||
@@ -23,62 +31,22 @@ export interface Config {
|
|||||||
[key: string]: ConfigOption
|
[key: string]: ConfigOption
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetainfoInput {
|
export interface MetainfoProxy extends MergedObject {
|
||||||
[key: string]: TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MetainfoProxy extends MetainfoInput {
|
|
||||||
// Should be a symbol, but: https://github.com/microsoft/TypeScript/issues/1863
|
|
||||||
__vm_proxy?: any // eslint-disable-line camelcase
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetainfoActive {
|
export interface MetainfoActive {
|
||||||
[key: string]: TODO
|
[key: string]: TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MetaContext = {
|
|
||||||
id: string | symbol
|
|
||||||
vm?: ComponentInternalInstance
|
|
||||||
resolve: ActiveResolverMethod
|
|
||||||
active: Object
|
|
||||||
shadow: Object
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MetaProxy = {
|
export type MetaProxy = {
|
||||||
meta: MetainfoProxy
|
meta: MetainfoProxy
|
||||||
unmount: TODO
|
unmount: TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ActiveResolverSetup = (context: MetaContext) => void
|
export type ResolveSetup = (context: MetaContext) => void
|
||||||
export type ActiveResolverMethod = (
|
|
||||||
key: string,
|
|
||||||
pathSegments: PathSegments,
|
|
||||||
shadow: GetShadowNodes,
|
|
||||||
active: GetActiveNode
|
|
||||||
) => any
|
|
||||||
|
|
||||||
export interface ActiveResolverObject {
|
export type Resolver = {
|
||||||
setup?: ActiveResolverSetup
|
setup?: ResolveSetup
|
||||||
resolve: ActiveResolverMethod
|
resolve: ResolveMethod
|
||||||
}
|
|
||||||
|
|
||||||
export interface ManagerResolverObject {
|
|
||||||
setup: ActiveResolverSetup
|
|
||||||
resolve: ActiveResolverMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShadowNode {
|
|
||||||
[key: string]: TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ActiveNode {
|
|
||||||
[key: string]: TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetShadowNodes {
|
|
||||||
(): Array<ShadowNode>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetActiveNode {
|
|
||||||
(): ActiveNode
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -3,6 +3,14 @@ import { Manager } from './manager'
|
|||||||
import { metaInfoKey } from './symbols'
|
import { metaInfoKey } from './symbols'
|
||||||
import { MetainfoActive, MetainfoInput, MetaProxy } from './types'
|
import { MetainfoActive, MetainfoInput, MetaProxy } from './types'
|
||||||
|
|
||||||
|
export function getCurrentManager (vm?: ComponentInternalInstance): Manager {
|
||||||
|
if (!vm) {
|
||||||
|
vm = getCurrentInstance()!
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm.appContext.config.globalProperties.$metaManager
|
||||||
|
}
|
||||||
|
|
||||||
export function useMeta (obj: MetainfoInput, manager?: Manager): MetaProxy {
|
export function useMeta (obj: MetainfoInput, manager?: Manager): MetaProxy {
|
||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
|
|
||||||
@@ -15,17 +23,9 @@ export function useMeta (obj: MetainfoInput, manager?: Manager): MetaProxy {
|
|||||||
throw new Error('No manager or current instance')
|
throw new Error('No manager or current instance')
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.createMetaProxy(obj, vm || undefined)
|
return manager.addMeta(obj, vm || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMetainfo (): MetainfoActive {
|
export function useMetainfo (): MetainfoActive {
|
||||||
return inject(metaInfoKey)!
|
return inject(metaInfoKey)!
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentManager (vm?: ComponentInternalInstance): Manager {
|
|
||||||
if (!vm) {
|
|
||||||
vm = getCurrentInstance()!
|
|
||||||
}
|
|
||||||
|
|
||||||
return vm.appContext.config.globalProperties.$metaManager
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
export const pluck = (collection: Array<any>, key: string, callback?: (row: any) => void) => {
|
||||||
|
const plucked: Array<any> = []
|
||||||
|
|
||||||
|
for (const row of collection) {
|
||||||
|
if (key in row) {
|
||||||
|
plucked.push(row[key])
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return plucked
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
interface DebugInterface {
|
||||||
|
(...args: any[]): void
|
||||||
|
warn: Function
|
||||||
|
error: Function
|
||||||
|
}
|
||||||
|
|
||||||
|
export const debugFn = (logFn: Function, setChildFns: boolean = false) => {
|
||||||
|
const fn = (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
throw new Error('DEBUG')
|
||||||
|
} catch (err) {
|
||||||
|
logFn(...args, '\n', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setChildFns) {
|
||||||
|
fn.warn = debugFn(console.warn) // eslint-disable-line no-console
|
||||||
|
fn.error = debugFn(console.error) // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
export const debug: DebugInterface = debugFn(console.log, true) // eslint-disable-line no-console
|
||||||
@@ -1 +1,3 @@
|
|||||||
export * from './clone'
|
export * from './clone'
|
||||||
|
export * from './collection'
|
||||||
|
export * from './debug'
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { createProxy, createHandler } from '../../src/continuous-object-merge'
|
import { createProxy } from '../../src/object-merge/proxy'
|
||||||
|
|
||||||
describe('proxy', () => {
|
describe('proxy', () => {
|
||||||
let context
|
let context
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
context = {
|
context = {
|
||||||
|
sources: [],
|
||||||
active: {},
|
active: {},
|
||||||
shadow: {}
|
resolve: ([option]) => option
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -17,8 +18,7 @@ describe('proxy', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handler = createHandler(context)
|
const proxy = createProxy(context, target)
|
||||||
const proxy = createProxy(target, handler)
|
|
||||||
|
|
||||||
expect(proxy.str).toBe('test')
|
expect(proxy.str).toBe('test')
|
||||||
expect(proxy.obj.str).toBe('test')
|
expect(proxy.obj.str).toBe('test')
|
||||||
@@ -27,99 +27,59 @@ describe('proxy', () => {
|
|||||||
test('string (set, update, delete)', () => {
|
test('string (set, update, delete)', () => {
|
||||||
const target = {}
|
const target = {}
|
||||||
|
|
||||||
const handler = createHandler(context)
|
const proxy = createProxy(context, target)
|
||||||
const proxy = createProxy(target, handler)
|
|
||||||
|
|
||||||
proxy.str = 'test'
|
proxy.str = 'test'
|
||||||
|
|
||||||
expect(context.active.str).toBe('test')
|
expect(context.active.str).toBe('test')
|
||||||
|
|
||||||
expect(context.shadow.str).toBeInstanceOf(Array)
|
|
||||||
expect(context.shadow.str.length).toBe(1)
|
|
||||||
expect(context.shadow.str[0]).toMatchObject({
|
|
||||||
context,
|
|
||||||
value: 'test'
|
|
||||||
})
|
|
||||||
|
|
||||||
proxy.str = 'update'
|
proxy.str = 'update'
|
||||||
|
|
||||||
expect(context.active.str).toBe('update')
|
expect(context.active.str).toBe('update')
|
||||||
expect(context.shadow.str.length).toBe(1)
|
|
||||||
expect(context.shadow.str[0]).toMatchObject({
|
|
||||||
context,
|
|
||||||
value: 'update'
|
|
||||||
})
|
|
||||||
|
|
||||||
proxy.str = undefined
|
proxy.str = undefined
|
||||||
|
|
||||||
expect(context.active.str).toBeUndefined()
|
expect(context.active.str).toBeUndefined()
|
||||||
expect(context.shadow.str.length).toBe(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('array (set, update, delete)', () => {
|
test('array (set, update, delete)', () => {
|
||||||
const target = {}
|
const target = {}
|
||||||
|
|
||||||
const handler = createHandler(context)
|
const proxy = createProxy(context, target)
|
||||||
const proxy = createProxy(target, handler)
|
|
||||||
|
|
||||||
proxy.arr = [0, 1]
|
proxy.arr = [0, 1]
|
||||||
|
|
||||||
expect(context.active.arr).toEqual([0, 1])
|
expect(context.active.arr).toEqual([0, 1])
|
||||||
|
|
||||||
expect(context.shadow.arr).toBeInstanceOf(Array)
|
|
||||||
expect(context.shadow.arr.length).toBe(1)
|
|
||||||
expect(context.shadow.arr[0]).toMatchObject({
|
|
||||||
context,
|
|
||||||
value: [0, 1]
|
|
||||||
})
|
|
||||||
|
|
||||||
proxy.arr[1] = 2
|
proxy.arr[1] = 2
|
||||||
|
|
||||||
expect(context.active.arr).toEqual(expect.arrayContaining([0, 2]))
|
expect(context.active.arr).toEqual(expect.arrayContaining([0, 2]))
|
||||||
expect(context.active.arr).toEqual(expect.not.arrayContaining([1]))
|
expect(context.active.arr).toEqual(expect.not.arrayContaining([1]))
|
||||||
expect(context.shadow.arr.length).toBe(1)
|
|
||||||
expect(context.shadow.arr[0]).toMatchObject({
|
|
||||||
context,
|
|
||||||
value: expect.arrayContaining([0, 2])
|
|
||||||
})
|
|
||||||
|
|
||||||
delete proxy.arr[1]
|
delete proxy.arr[1]
|
||||||
|
|
||||||
expect(context.active.arr).toEqual(expect.not.arrayContaining([2]))
|
expect(context.active.arr).toEqual(expect.not.arrayContaining([2]))
|
||||||
expect(context.shadow.arr[0]).toMatchObject({
|
|
||||||
context,
|
|
||||||
value: expect.not.arrayContaining([2])
|
|
||||||
})
|
|
||||||
|
|
||||||
delete proxy.arr
|
delete proxy.arr
|
||||||
|
|
||||||
expect(context.active.arr).toBeUndefined()
|
expect(context.active.arr).toBeUndefined()
|
||||||
expect(context.shadow.arr).toEqual([])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('proxy (set object)', () => {
|
test('proxy (set object)', () => {
|
||||||
const target = {}
|
const target = {}
|
||||||
|
|
||||||
const handler = createHandler(context)
|
const proxy = createProxy(context, target)
|
||||||
const proxy = createProxy(target, handler)
|
|
||||||
|
|
||||||
proxy.obj = { str: 'test' }
|
proxy.obj = { str: 'test' }
|
||||||
|
|
||||||
expect(context.active.obj).toBeDefined()
|
expect(context.active.obj).toBeDefined()
|
||||||
expect(context.active.obj.str).toBe('test')
|
expect(context.active.obj.str).toBe('test')
|
||||||
|
|
||||||
expect(context.shadow.obj).toBeDefined()
|
|
||||||
expect(context.shadow.obj.str).toMatchObject([{
|
|
||||||
context: expect.any(Object),
|
|
||||||
value: 'test'
|
|
||||||
}])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('proxy (remove)', () => {
|
test('proxy (remove)', () => {
|
||||||
const target = {}
|
const target = {}
|
||||||
|
|
||||||
const handler = createHandler(context)
|
const proxy = createProxy(context, target)
|
||||||
const proxy = createProxy(target, handler)
|
|
||||||
|
|
||||||
proxy.obj = { str: 'test' }
|
proxy.obj = { str: 'test' }
|
||||||
|
|
||||||
@@ -128,14 +88,12 @@ describe('proxy', () => {
|
|||||||
delete proxy.obj
|
delete proxy.obj
|
||||||
|
|
||||||
expect(context.active.obj).not.toBeDefined()
|
expect(context.active.obj).not.toBeDefined()
|
||||||
expect(context.shadow.obj).not.toBeDefined()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('proxy (remove child)', () => {
|
test('proxy (remove child)', () => {
|
||||||
const target = {}
|
const target = {}
|
||||||
|
|
||||||
const handler = createHandler(context)
|
const proxy = createProxy(context, target)
|
||||||
const proxy = createProxy(target, handler)
|
|
||||||
|
|
||||||
proxy.obj = { str: 'test' }
|
proxy.obj = { str: 'test' }
|
||||||
|
|
||||||
@@ -144,6 +102,5 @@ describe('proxy', () => {
|
|||||||
delete proxy.obj.str
|
delete proxy.obj.str
|
||||||
|
|
||||||
expect(context.active.obj).toEqual({})
|
expect(context.active.obj).toEqual({})
|
||||||
expect(context.shadow.obj).toEqual({ str: [] })
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+94
-119
@@ -1,79 +1,63 @@
|
|||||||
import { isArray, isPlainObject } from '@vue/shared'
|
import { isArray, isPlainObject } from '@vue/shared'
|
||||||
import { createProxy, createHandler, setByObject, remove } from '../../src/continuous-object-merge'
|
import { createMergedObject } from '../../src/object-merge'
|
||||||
|
|
||||||
describe('resolve', () => {
|
const resolve = (options) => {
|
||||||
let active, shadow
|
// console.log('RESOLVE\n', options)
|
||||||
let context1, context2
|
|
||||||
|
|
||||||
beforeEach(() => {
|
const hasArrayOption = options.some(option => isArray(option))
|
||||||
active = {}
|
if (hasArrayOption) {
|
||||||
shadow = {}
|
const groupedOptions = {}
|
||||||
|
for (const option of options) {
|
||||||
const resolve = (_key, _pathSegments, getOptions, _getCurrentValue) => {
|
if (!isArray(option)) {
|
||||||
const options = getOptions()
|
continue
|
||||||
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
|
for (const value of option) {
|
||||||
|
if (isPlainObject(value) && 'vmid' in value) {
|
||||||
|
groupedOptions[value.vmid] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context1 = { active, shadow, resolve }
|
// console.log('GROUPED OPTIONS', groupedOptions)
|
||||||
context2 = { active, shadow, resolve }
|
const values = []
|
||||||
})
|
for (const option of options) {
|
||||||
|
if (!isArray(option)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const value of option) {
|
||||||
|
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('WILL USE THESE VALUES', values)
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
return options[options.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('resolve', () => {
|
||||||
test('resolve (string)', () => {
|
test('resolve (string)', () => {
|
||||||
const target1 = {
|
const source1 = {
|
||||||
str: 'string value 1'
|
str: 'string value 1'
|
||||||
}
|
}
|
||||||
|
|
||||||
const target2 = {
|
const source2 = {
|
||||||
str: 'string value 2'
|
str: 'string value 2'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value
|
const { active, addSource, delSource } = createMergedObject(resolve)
|
||||||
setByObject(context1, target1)
|
|
||||||
|
|
||||||
// Init proxy
|
// Set initial value & init proxy
|
||||||
const handler1 = createHandler(context1)
|
addSource(source1)
|
||||||
/* const proxy1 = */ createProxy(target1, handler1)
|
const proxy2 = addSource(source2, null, true /* do an initial compute/walk of all sources */)
|
||||||
|
|
||||||
setByObject(context2, target2)
|
|
||||||
const handler2 = createHandler(context2)
|
|
||||||
const proxy2 = createProxy(target2, handler2)
|
|
||||||
|
|
||||||
expect(active.str).toBe('string value 2')
|
expect(active.str).toBe('string value 2')
|
||||||
|
|
||||||
@@ -81,39 +65,33 @@ describe('resolve', () => {
|
|||||||
|
|
||||||
expect(active.str).toBe('test')
|
expect(active.str).toBe('test')
|
||||||
|
|
||||||
remove(context2)
|
delSource(proxy2)
|
||||||
|
|
||||||
expect(active.str).toBe('string value 1')
|
expect(active.str).toBe('string value 1')
|
||||||
|
|
||||||
remove(context1)
|
delSource(source1)
|
||||||
|
|
||||||
expect(active.str).toBeUndefined()
|
expect(active.str).toBeUndefined()
|
||||||
expect(shadow.str.length).toBe(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolve (object)', () => {
|
test('resolve (object)', () => {
|
||||||
const target1 = {
|
const source1 = {
|
||||||
obj: {
|
obj: {
|
||||||
key: 'object value 1'
|
key: 'object value 1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const target2 = {
|
const source2 = {
|
||||||
obj: {
|
obj: {
|
||||||
key: 'object value 2'
|
key: 'object value 2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value
|
const { active, addSource, delSource } = createMergedObject(resolve)
|
||||||
setByObject(context1, target1)
|
|
||||||
|
|
||||||
// Init proxy
|
// Set initial value & init proxy
|
||||||
const handler1 = createHandler(context1)
|
const proxy1 = addSource(source1)
|
||||||
/* const proxy1 = */ createProxy(target1, handler1)
|
const proxy2 = addSource(source2, null, true /* do an initial compute/walk of all sources */)
|
||||||
|
|
||||||
setByObject(context2, target2)
|
|
||||||
const handler2 = createHandler(context2)
|
|
||||||
const proxy2 = createProxy(target2, handler2)
|
|
||||||
|
|
||||||
expect(active.obj.key).toBe('object value 2')
|
expect(active.obj.key).toBe('object value 2')
|
||||||
|
|
||||||
@@ -124,42 +102,35 @@ describe('resolve', () => {
|
|||||||
proxy2.obj = { key: 'test again' }
|
proxy2.obj = { key: 'test again' }
|
||||||
|
|
||||||
expect(active.obj.key).toBe('test again')
|
expect(active.obj.key).toBe('test again')
|
||||||
expect(shadow.obj.key.length).toBe(2)
|
|
||||||
|
|
||||||
remove(context2)
|
delSource(source2)
|
||||||
|
|
||||||
expect(active.obj.key).toBe('object value 1')
|
expect(active.obj.key).toBe('object value 1')
|
||||||
|
|
||||||
remove(context1)
|
delSource(proxy1)
|
||||||
|
|
||||||
// TODO: should we clean up the obj ref too?
|
expect(active.obj).toBeUndefined()
|
||||||
expect(active.obj).toEqual({})
|
expect(active).toEqual({})
|
||||||
|
|
||||||
expect(active.obj.key).toBeUndefined()
|
|
||||||
expect(shadow.obj.key.length).toBe(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolve (array)', () => {
|
test('resolve (array)', () => {
|
||||||
const target1 = {
|
const source1 = {
|
||||||
arr: [
|
arr: [
|
||||||
'array value 1'
|
'array value 1'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const target2 = {
|
const source2 = {
|
||||||
arr: [
|
arr: [
|
||||||
'array value 2'
|
'array value 2'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value & init proxy
|
const { active, sources, addSource, delSource } = createMergedObject(resolve)
|
||||||
setByObject(context1, target1)
|
|
||||||
const handler1 = createHandler(context1)
|
|
||||||
const proxy1 = createProxy(target1, handler1)
|
|
||||||
|
|
||||||
setByObject(context2, target2)
|
// Set initial value & init proxy
|
||||||
const handler2 = createHandler(context2)
|
const proxy1 = addSource(source1)
|
||||||
const proxy2 = createProxy(target2, handler2)
|
const proxy2 = addSource(source2, null, true /* do an initial compute/walk of all sources */)
|
||||||
|
|
||||||
expect(active.arr).toEqual(['array value 1', 'array value 2'])
|
expect(active.arr).toEqual(['array value 1', 'array value 2'])
|
||||||
|
|
||||||
@@ -170,49 +141,49 @@ describe('resolve', () => {
|
|||||||
proxy1.arr = ['test 1']
|
proxy1.arr = ['test 1']
|
||||||
|
|
||||||
expect(active.arr).toEqual(['test 1', 'test 2'])
|
expect(active.arr).toEqual(['test 1', 'test 2'])
|
||||||
expect(shadow.arr.length).toBe(2)
|
expect(sources.length).toBe(2)
|
||||||
|
|
||||||
remove(context1)
|
delSource(source1)
|
||||||
|
|
||||||
expect(active.arr).toEqual(['test 2'])
|
expect(active.arr).toEqual(['test 2'])
|
||||||
|
|
||||||
delete proxy2.arr
|
delete proxy2.arr
|
||||||
|
|
||||||
// TODO: should we clean up the obj ref too?
|
|
||||||
expect(active.arr).toBeUndefined()
|
expect(active.arr).toBeUndefined()
|
||||||
expect(shadow.arr.length).toBe(0)
|
expect(sources.length).toBe(1)
|
||||||
|
|
||||||
|
proxy2.arr = ['test again 2.1']
|
||||||
|
expect(active.arr).toEqual(['test again 2.1'])
|
||||||
|
|
||||||
proxy1.arr = ['test again 1']
|
proxy1.arr = ['test again 1']
|
||||||
expect(active.arr).toEqual(['test again 1'])
|
addSource(proxy1, null, true)
|
||||||
|
expect(active.arr).toEqual(['test again 2.1', 'test again 1'])
|
||||||
|
|
||||||
proxy2.arr = []
|
proxy2.arr = []
|
||||||
proxy2.arr[0] = 'test again 2'
|
proxy2.arr[0] = 'test again 2.2'
|
||||||
expect(active.arr).toEqual(['test again 1', 'test again 2'])
|
expect(active.arr).toEqual(['test again 2.2', 'test again 1'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('resolve (collection)', () => {
|
test('resolve (collection)', () => {
|
||||||
const target1 = {
|
const source1 = {
|
||||||
arr: [
|
arr: [
|
||||||
{ key: 'collection value 1.1' },
|
{ key: 'collection value 1.1' },
|
||||||
{ vmid: 'a', key: 'collection value 1.2' }
|
{ vmid: 'a', key: 'collection value 1.2' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const target2 = {
|
const source2 = {
|
||||||
arr: [
|
arr: [
|
||||||
{ vmid: 'a', key: 'collection value 2.1' },
|
{ vmid: 'a', key: 'collection value 2.1' },
|
||||||
{ vmid: 'b', key: 'collection value 2.2' }
|
{ vmid: 'b', key: 'collection value 2.2' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set initial value & init proxy
|
const { active, sources, addSource, delSource } = createMergedObject(resolve)
|
||||||
setByObject(context1, target1)
|
|
||||||
const handler1 = createHandler(context1)
|
|
||||||
const proxy1 = createProxy(target1, handler1)
|
|
||||||
|
|
||||||
setByObject(context2, target2)
|
// Set initial value & init proxy
|
||||||
const handler2 = createHandler(context2)
|
const proxy1 = addSource(source1)
|
||||||
const proxy2 = createProxy(target2, handler2)
|
const proxy2 = addSource(source2, null, true /* do an initial compute/walk of all sources */)
|
||||||
|
|
||||||
expect(active.arr).toEqual([
|
expect(active.arr).toEqual([
|
||||||
{ key: 'collection value 1.1' },
|
{ key: 'collection value 1.1' },
|
||||||
@@ -225,7 +196,7 @@ describe('resolve', () => {
|
|||||||
|
|
||||||
expect(active.arr).toEqual([
|
expect(active.arr).toEqual([
|
||||||
{ key: 'test 1.1' },
|
{ 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: 'a', key: 'collection value 2.1' },
|
||||||
{ vmid: 'b', key: 'collection value 2.2' }
|
{ vmid: 'b', key: 'collection value 2.2' }
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -241,10 +212,11 @@ describe('resolve', () => {
|
|||||||
{ vmid: 'c', key: 'collection value 2.2' }
|
{ vmid: 'c', key: 'collection value 2.2' }
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(shadow.arr.length).toBe(2)
|
expect(sources.length).toBe(2)
|
||||||
|
|
||||||
remove(context1)
|
delSource(proxy1)
|
||||||
|
|
||||||
|
expect(sources.length).toBe(1)
|
||||||
expect(active.arr).toEqual([
|
expect(active.arr).toEqual([
|
||||||
{ vmid: 'b', key: 'collection value 2.1' },
|
{ vmid: 'b', key: 'collection value 2.1' },
|
||||||
{ vmid: 'c', key: 'collection value 2.2' }
|
{ vmid: 'c', key: 'collection value 2.2' }
|
||||||
@@ -252,18 +224,21 @@ describe('resolve', () => {
|
|||||||
|
|
||||||
delete proxy2.arr
|
delete proxy2.arr
|
||||||
|
|
||||||
// TODO: should we clean up the obj ref too?
|
|
||||||
expect(active.arr).toBeUndefined()
|
expect(active.arr).toBeUndefined()
|
||||||
expect(shadow.arr.length).toBe(0)
|
expect(active).toEqual({})
|
||||||
|
|
||||||
proxy1.arr = [{ vmid: 'a', key: 'test again 1' }]
|
const proxy3 = addSource({ arr: [{ vmid: 'a', key: 'test again 1' }] }, null, true)
|
||||||
|
|
||||||
|
expect(sources.length).toBe(2)
|
||||||
expect(active.arr).toEqual([{ vmid: 'a', key: 'test again 1' }])
|
expect(active.arr).toEqual([{ vmid: 'a', key: 'test again 1' }])
|
||||||
|
|
||||||
// TODO: fix
|
expect(sources[0]).toBe(proxy2)
|
||||||
proxy2.arr = []
|
expect(sources[1]).toBe(proxy3)
|
||||||
proxy2.arr[0] = { vmid: 'a', value: 'test again 2' }
|
|
||||||
expect(active.arr).toEqual([
|
proxy2.arr = [{ vmid: 'a', key: 'test again 2' }]
|
||||||
{ vmid: 'a', key: 'test again 2' }
|
|
||||||
])
|
// This is still test again 1 because proxy3 is added after proxy2,
|
||||||
|
// and the resolve method returns the last value
|
||||||
|
expect(active.arr).toEqual([{ vmid: 'a', key: 'test again 1' }])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+6
-2
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"include": ["src/global.d.ts", "src/**/*.ts", "__tests__/**/*.ts"],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
@@ -27,5 +26,10 @@
|
|||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"lib": ["esnext", "dom"],
|
"lib": ["esnext", "dom"],
|
||||||
"types": ["jest", "node"]
|
"types": ["jest", "node"]
|
||||||
}
|
},
|
||||||
|
"include": [
|
||||||
|
"src/global.d.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"__tests__/**/*.ts"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user