mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-24 08:50:34 +03:00
feat: continued progress
This commit is contained in:
+32
-10
@@ -94,12 +94,32 @@ const App = {
|
|||||||
},
|
},
|
||||||
script: [
|
script: [
|
||||||
{ src: 'head-script1.js' },
|
{ src: 'head-script1.js' },
|
||||||
{ json: { '@context': 'http://schema.org', unsafe: '<p>hello</p>' } },
|
// TODO 'head-script2.js',
|
||||||
{ content: 'window.a = "<br/>"; </script><script>alert(\'asdasd\');' },
|
// TODO { json: { '@context': 'http://schema.org', unsafe: '<p>hello</p>' } },
|
||||||
{ rawContent: 'window.b = "<br/>"; </script><script> alert(\'123321\');' },
|
// TODO { content: 'window.a = "<br/>"; </script><script>alert(\'asdasd\');' },
|
||||||
|
// TODO { rawContent: 'window.b = "<br/>"; </script><script> alert(\'123321\');' },
|
||||||
{ 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: {
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
tag: 'choose',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
tag: 'when',
|
||||||
|
test: '$(HTTP_COOKIE{group})=="Advanced"',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
tag: 'include',
|
||||||
|
src: 'http://www.example.com/advanced.html'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
esi: {
|
esi: {
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -161,22 +181,24 @@ const App = {
|
|||||||
{{ content }} - {{ og.description }} - {{ metainfo.description }} - Hello Again
|
{{ content }} - {{ og.description }} - {{ metainfo.description }} - Hello Again
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="lalala1.js"></script>
|
<!-- // TODO: Using script triggers [Vue warn]: Template compilation error: Tags with side effect (<script> and <style>) are ignored in client component templates. -->
|
||||||
<script src="lalala2.js"></script>
|
<component is="script">window.users = []</component>
|
||||||
|
<component is="script" src="user-1.js"></component>
|
||||||
|
<component is="script" src="user-2.js"></component>
|
||||||
|
|
||||||
<template v-slot:head="{ metainfo }">
|
<template v-slot:head="{ metainfo }">
|
||||||
<!--[if IE]>
|
<!--[if IE]>
|
||||||
// -> Reactivity is not supported by Vue, all comments are ignored
|
// -> Reactivity is not supported by Vue in comments, all comments are ignored
|
||||||
<script :src="metainfo.script[0].src" ></script>
|
<component is="script" :src="metainfo.script[0].src" ></component>
|
||||||
// -> but a static file should work
|
// -> but a static file should work
|
||||||
<script src="lalala3.js" ></script>
|
<script src="user-3.js" ></script>
|
||||||
// -> altho Vue probably strips comments in production builds (but can be configged afaik)
|
// -> altho Vue probably strips comments in production builds (but can be configged afaik)
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<script :src="metainfo.script[0].src" ></script>
|
<component is="script" :src="metainfo.script[0].src" ></component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<script src="lalala4.js"></script>
|
<component is="script" src="user-4.js"></component>
|
||||||
</template>
|
</template>
|
||||||
</metainfo>
|
</metainfo>
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,10 @@ export default {
|
|||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||||
__DEV__: process.env.NODE_ENV !== 'production'
|
__DEV__: process.env.NODE_ENV !== 'production'
|
||||||
|
}),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__VUE_OPTIONS_API__: JSON.stringify(true),
|
||||||
|
__VUE_PROD_DEVTOOLS__: JSON.stringify(true),
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
devServer: {
|
devServer: {
|
||||||
|
|||||||
+38
-38
@@ -35,7 +35,7 @@
|
|||||||
"dev": "babel-node examples/server.js",
|
"dev": "babel-node examples/server.js",
|
||||||
"docs": "vuepress dev --host 0.0.0.0 --port 3000 docs",
|
"docs": "vuepress dev --host 0.0.0.0 --port 3000 docs",
|
||||||
"docs:build": "vuepress build docs",
|
"docs:build": "vuepress build docs",
|
||||||
"lint": "eslint --ext .js,.ts examples src test",
|
"lint": "eslint --ext .js,.ts src test",
|
||||||
"prerelease": "git checkout master && git pull -r",
|
"prerelease": "git checkout master && git pull -r",
|
||||||
"release": "yarn lint && yarn test && standard-version",
|
"release": "yarn lint && yarn test && standard-version",
|
||||||
"test": "yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",
|
"test": "yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",
|
||||||
@@ -48,61 +48,61 @@
|
|||||||
"vue": "next"
|
"vue": "next"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.11.1",
|
"@babel/core": "^7.12.3",
|
||||||
"@babel/node": "^7.10.5",
|
"@babel/node": "^7.12.1",
|
||||||
"@babel/preset-env": "^7.11.0",
|
"@babel/preset-env": "^7.12.1",
|
||||||
"@babel/preset-typescript": "^7.10.4",
|
"@babel/preset-typescript": "^7.12.1",
|
||||||
"@nuxtjs/eslint-config-typescript": "^3.0.0",
|
"@nuxtjs/eslint-config-typescript": "^4.0.0",
|
||||||
"@types/webpack": "^4.41.21",
|
"@types/webpack": "^4.41.24",
|
||||||
"@types/webpack-env": "^1.15.2",
|
"@types/webpack-env": "^1.15.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^3.8.0",
|
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
||||||
"@typescript-eslint/parser": "^3.8.0",
|
"@typescript-eslint/parser": "^4.6.0",
|
||||||
"@vue/compiler-sfc": "^3.0.0-rc.5",
|
"@vue/compiler-sfc": "^3.0.2",
|
||||||
"@vue/server-renderer": "^3.0.0-rc.5",
|
"@vue/server-renderer": "^3.0.2",
|
||||||
"@vue/server-test-utils": "^1.0.3",
|
"@vue/server-test-utils": "^1.1.1",
|
||||||
"@vue/test-utils": "^1.0.3",
|
"@vue/test-utils": "^1.1.1",
|
||||||
"@wishy-gift/html-include-chunks-webpack-plugin": "^0.1.5",
|
"@wishy-gift/html-include-chunks-webpack-plugin": "^0.1.5",
|
||||||
"babel-jest": "^26.2.2",
|
"babel-jest": "^26.6.1",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.1.0",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"browserstack-local": "^1.4.5",
|
"browserstack-local": "^1.4.8",
|
||||||
"chromedriver": "^84.0.1",
|
"chromedriver": "^86.0.0",
|
||||||
"codecov": "^3.7.2",
|
"codecov": "^3.8.0",
|
||||||
"consola": "^2.15.0",
|
"consola": "^2.15.0",
|
||||||
"eslint": "^7.6.0",
|
"eslint": "^7.12.1",
|
||||||
"express-urlrewrite": "^1.3.0",
|
"express-urlrewrite": "^1.3.0",
|
||||||
"geckodriver": "^1.20.0",
|
"geckodriver": "^1.20.0",
|
||||||
"html-webpack-plugin": "^4.3.0",
|
"html-webpack-plugin": "^4.5.0",
|
||||||
"jest": "^26.2.2",
|
"jest": "^26.6.1",
|
||||||
"jest-environment-jsdom": "^26.2.0",
|
"jest-environment-jsdom": "^26.6.1",
|
||||||
"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.19",
|
"lodash": "^4.17.20",
|
||||||
"node-env-file": "^0.1.8",
|
"node-env-file": "^0.1.8",
|
||||||
"puppeteer-core": "^5.2.1",
|
"puppeteer-core": "^5.4.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.23.1",
|
"rollup": "^2.33.0",
|
||||||
"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",
|
||||||
"rollup-plugin-node-resolve": "^5.2.0",
|
"rollup-plugin-node-resolve": "^5.2.0",
|
||||||
"rollup-plugin-replace": "^2.2.0",
|
"rollup-plugin-replace": "^2.2.0",
|
||||||
"rollup-plugin-terser": "^7.0.0",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-typescript2": "^0.27.2",
|
"rollup-plugin-typescript2": "^0.29.0",
|
||||||
"selenium-webdriver": "^4.0.0-alpha.7",
|
"selenium-webdriver": "^4.0.0-alpha.7",
|
||||||
"standard-version": "^8.0.2",
|
"standard-version": "^9.0.0",
|
||||||
"tib": "^0.7.4",
|
"tib": "^0.7.4",
|
||||||
"ts-jest": "^26.1.4",
|
"ts-jest": "^26.4.3",
|
||||||
"ts-loader": "^8.0.2",
|
"ts-loader": "^8.0.7",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^4.0.5",
|
||||||
"vue": "next",
|
"vue": "^3.0.0",
|
||||||
"vue-jest": "^3.0.6",
|
"vue-jest": "^3.0.7",
|
||||||
"vue-loader": "^16.0.0-beta.2",
|
"vue-loader": "^16.0.0",
|
||||||
"vue-router": "next",
|
"vue-router": "next",
|
||||||
"webpack": "^4.44.1",
|
"webpack": "^5.3.2",
|
||||||
"webpack-bundle-analyzer": "^3.8.0",
|
"webpack-bundle-analyzer": "^3.9.0",
|
||||||
"webpack-cli": "^3.3.12",
|
"webpack-cli": "^4.1.0",
|
||||||
"webpack-dev-server": "^3.11.0",
|
"webpack-dev-server": "^3.11.0",
|
||||||
"webpackbar": "^4.0.0"
|
"webpackbar": "^4.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ export const MetainfoImpl = defineComponent({
|
|||||||
const teleports: any = {}
|
const teleports: any = {}
|
||||||
|
|
||||||
const manager = getCurrentManager()
|
const manager = getCurrentManager()
|
||||||
|
if (!manager) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for (const key in metainfo) {
|
for (const key in metainfo) {
|
||||||
const config = manager.config[key] || {}
|
const config = manager.config[key] || {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export * from './globals'
|
export * from './proxy'
|
||||||
export * from './remove'
|
export * from './remove'
|
||||||
export * from './set'
|
export * from './set'
|
||||||
export * from './update'
|
export * from './update'
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import { markRaw } from 'vue'
|
import { markRaw } from 'vue'
|
||||||
import { isObject } from '@vue/shared'
|
import { isObject } from '@vue/shared'
|
||||||
import { update } from './info/update'
|
import { MetaContext, MetainfoInput, MetainfoProxy, PathSegments } from '../types'
|
||||||
import { MetaContext, MetainfoInput, PathSegments } from './types'
|
import { update } from './update'
|
||||||
|
import { remove } from './remove'
|
||||||
|
|
||||||
interface Target extends MetainfoInput {
|
export function createProxy (target: MetainfoInput, handler: ProxyHandler<object>): MetainfoProxy {
|
||||||
__vm_proxy?: any // eslint-disable-line camelcase
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createProxy (target: Target, handler: ProxyHandler<object>): Target {
|
|
||||||
return markRaw(new Proxy(target, handler))
|
return markRaw(new Proxy(target, handler))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,7 +13,7 @@ export function createHandler (context: MetaContext, pathSegments: PathSegments
|
|||||||
get (target: object, key: string, receiver: object) {
|
get (target: object, key: string, receiver: object) {
|
||||||
const value = Reflect.get(target, key, receiver)
|
const value = Reflect.get(target, key, receiver)
|
||||||
|
|
||||||
if (!isObject(value)) {
|
if (!isObject(value) || key === '__vm_proxy') {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +27,20 @@ export function createHandler (context: MetaContext, pathSegments: PathSegments
|
|||||||
return value.__vm_proxy
|
return value.__vm_proxy
|
||||||
},
|
},
|
||||||
set (
|
set (
|
||||||
target: object, // eslint-disable-line @typescript-eslint/no-unused-vars
|
target: { [key: string]: any }, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
key: string,
|
key: string,
|
||||||
value: unknown
|
value: any
|
||||||
): boolean {
|
): boolean {
|
||||||
update(context, pathSegments, key, value)
|
update(context, pathSegments, key, value)
|
||||||
|
// target[key] = value
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
deleteProperty (
|
||||||
|
target: { [key: string]: any },
|
||||||
|
prop: string
|
||||||
|
) {
|
||||||
|
remove(context, pathSegments, prop)
|
||||||
|
delete target[prop]
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -11,18 +11,20 @@ export function resolveActive (
|
|||||||
) {
|
) {
|
||||||
let value
|
let value
|
||||||
|
|
||||||
if (shadowParent[key].length > 1) {
|
const 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
|
// 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(shadowParent[key]))
|
||||||
const getActive: GetActiveNode = () => Object.freeze(clone(activeParent[key]))
|
const getActive: GetActiveNode = () => Object.freeze(clone(activeParent[key]))
|
||||||
|
|
||||||
value = context.manager.resolver.resolve(
|
value = context.resolve(
|
||||||
key,
|
key,
|
||||||
pathSegments,
|
pathSegments,
|
||||||
getShadow,
|
getShadow,
|
||||||
getActive
|
getActive
|
||||||
)
|
)
|
||||||
} else if (shadowParent[key].length) {
|
} else if (shadowLength) {
|
||||||
value = shadowParent[key][0].value
|
value = shadowParent[key][0].value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
} else if (isArray(value)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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,5 +1,4 @@
|
|||||||
import { MetaContext, PathSegments, ShadowNode, ActiveNode } from '../types'
|
import { MetaContext, PathSegments, ShadowNode, ActiveNode } from '../types'
|
||||||
import { shadow, active } from './globals'
|
|
||||||
import { set } from './set'
|
import { set } from './set'
|
||||||
|
|
||||||
export function update (
|
export function update (
|
||||||
@@ -8,12 +7,14 @@ export function update (
|
|||||||
key: string,
|
key: string,
|
||||||
value: any
|
value: any
|
||||||
) {
|
) {
|
||||||
let shadowParent: ShadowNode = shadow
|
const { active, shadow } = context
|
||||||
|
|
||||||
let activeParent: ActiveNode = active
|
let activeParent: ActiveNode = active
|
||||||
|
let shadowParent: ShadowNode = shadow
|
||||||
|
|
||||||
for (const segment of pathSegments) {
|
for (const segment of pathSegments) {
|
||||||
shadowParent = shadowParent[segment]
|
|
||||||
activeParent = activeParent[segment]
|
activeParent = activeParent[segment]
|
||||||
|
shadowParent = shadowParent[segment]
|
||||||
}
|
}
|
||||||
|
|
||||||
set(context, key, value, shadowParent, activeParent)
|
set(context, key, value, shadowParent, activeParent)
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { markRaw, reactive } from 'vue'
|
|
||||||
|
|
||||||
export const shadow = markRaw({})
|
|
||||||
export const active = reactive({})
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { MetaContext } from '../types'
|
|
||||||
import { setByObject } from './set'
|
|
||||||
|
|
||||||
export function remove (context: MetaContext) {
|
|
||||||
setByObject(context, {})
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { isPlainObject, /**/ hasOwn } from '@vue/shared'
|
|
||||||
import { ActiveNode, MetaContext, PathSegments, ShadowNode } from '../types'
|
|
||||||
import { shadow, active } from './globals'
|
|
||||||
import { resolveActive } from './resolve'
|
|
||||||
|
|
||||||
export function set (
|
|
||||||
context: MetaContext,
|
|
||||||
key: string,
|
|
||||||
value: any,
|
|
||||||
shadowParent: ShadowNode = shadow,
|
|
||||||
activeParent: ActiveNode = active,
|
|
||||||
pathSegments: PathSegments = []
|
|
||||||
) {
|
|
||||||
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
|
|
||||||
if (!shadowParent[key]) {
|
|
||||||
shadowParent[key] = []
|
|
||||||
} else {
|
|
||||||
// check if we already have a value listed for this element for this context
|
|
||||||
idx = shadowParent[key].findIndex(
|
|
||||||
({ context: shadowContext }: { context: MetaContext }) => shadowContext === context
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this context/key combo exists but value is undefined, remove it
|
|
||||||
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 (value) {
|
|
||||||
shadowParent[key].push({ context, value })
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveActive(context, key, pathSegments, shadowParent, activeParent)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setByObject (
|
|
||||||
context: MetaContext,
|
|
||||||
value: any,
|
|
||||||
shadowParent: ShadowNode = shadow,
|
|
||||||
activeParent: ActiveNode = active,
|
|
||||||
pathSegments: PathSegments = []
|
|
||||||
) {
|
|
||||||
// cleanup properties that no longer exists
|
|
||||||
for (const key in shadowParent) {
|
|
||||||
if (hasOwn(value, key)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPlainObject(shadowParent[key])) {
|
|
||||||
console.log('HERERER')
|
|
||||||
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
-2
@@ -1,7 +1,6 @@
|
|||||||
import { App } from 'vue'
|
import { App } from 'vue'
|
||||||
import { Metainfo } from './Metainfo'
|
import { Metainfo } from './Metainfo'
|
||||||
import { metaInfoKey } from './symbols'
|
import { metaInfoKey } from './symbols'
|
||||||
import { active } from './info/globals'
|
|
||||||
import { Manager } from './manager'
|
import { Manager } from './manager'
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
@@ -10,7 +9,7 @@ declare module '@vue/runtime-core' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyMetaPlugin (app: App, manager: Manager) {
|
export function applyMetaPlugin (app: App, manager: Manager, active: Object) {
|
||||||
app.component('Metainfo', Metainfo)
|
app.component('Metainfo', Metainfo)
|
||||||
|
|
||||||
app.config.globalProperties.$metaManager = manager
|
app.config.globalProperties.$metaManager = manager
|
||||||
|
|||||||
+54
-25
@@ -1,42 +1,71 @@
|
|||||||
import { App } from 'vue'
|
import { App, markRaw, 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 { 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, ManagerResolverObject, GetActiveNode, ActiveResolverObject, MetaContext, PathSegments, GetShadowNodes } from './types'
|
import { Config, ActiveResolverObject, ActiveResolverMethod, MetaContext, MetainfoInput, MetaProxy } from './types'
|
||||||
|
|
||||||
|
let contextCounter: number = 0
|
||||||
|
|
||||||
export type Manager = {
|
export type Manager = {
|
||||||
readonly config: Config
|
readonly config: Config
|
||||||
|
|
||||||
resolver: ManagerResolverObject
|
|
||||||
install(app: App): void
|
install(app: App): void
|
||||||
|
createMetaProxy(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const shadow = markRaw({})
|
||||||
|
export const active = reactive({})
|
||||||
|
|
||||||
export function createManager (config: Config, resolver: ActiveResolverObject): Manager {
|
export function createManager (config: Config, resolver: ActiveResolverObject): Manager {
|
||||||
|
const resolve: ActiveResolverMethod = (key, pathSegments, getShadow, getActive) => {
|
||||||
|
if (!resolver) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFunction(resolver)) {
|
||||||
|
return resolver(key, pathSegments, getShadow, getActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver.resolve(key, pathSegments, getShadow, getActive)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: validate resolver
|
// TODO: validate resolver
|
||||||
const manager: Manager = {
|
const manager: Manager = {
|
||||||
resolver: {
|
|
||||||
setup (context: MetaContext) {
|
|
||||||
if (!resolver || !resolver.setup || isFunction(resolver)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resolver.setup(context)
|
|
||||||
},
|
|
||||||
resolve (key: string, pathSegments: PathSegments, getShadow: GetShadowNodes, getActive: GetActiveNode) {
|
|
||||||
if (!resolver) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFunction(resolver)) {
|
|
||||||
return resolver(key, pathSegments, getShadow, getActive)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolver.resolve(key, pathSegments, getShadow, getActive)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
config,
|
config,
|
||||||
install (app: App) {
|
|
||||||
applyMetaPlugin(app, this)
|
install (app) {
|
||||||
|
applyMetaPlugin(app, this, active)
|
||||||
|
},
|
||||||
|
|
||||||
|
createMetaProxy (obj, vm) {
|
||||||
|
const context: MetaContext = {
|
||||||
|
id: PolySymbol(`ctx${contextCounter++}`),
|
||||||
|
vm: vm || undefined,
|
||||||
|
resolve,
|
||||||
|
shadow,
|
||||||
|
active
|
||||||
|
}
|
||||||
|
|
||||||
|
const unmount = <T extends Function = () => any>() => remove(context)
|
||||||
|
if (vm) {
|
||||||
|
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 {
|
||||||
|
meta,
|
||||||
|
unmount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ export function renderGroup (
|
|||||||
if (isArray(data)) {
|
if (isArray(data)) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn('Specifying an array for group properties isnt supported as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo')
|
console.warn('Specifying an array for group properties isnt supported mostly as we didnt found a use-case for this yet. If you have one, please create an issue on the vue-meta repo')
|
||||||
}
|
}
|
||||||
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
// config.attributes = getConfigKey([key, config.tag], 'attributes', config)
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ export function setup (context: DeepestResolverMetaContext): void {
|
|||||||
let { vm } = context
|
let { vm } = context
|
||||||
|
|
||||||
do {
|
do {
|
||||||
depth++
|
if (vm.parent) {
|
||||||
vm = vm.parent
|
depth++
|
||||||
} while (vm && vm !== vm.root)
|
|
||||||
|
vm = vm.parent
|
||||||
|
}
|
||||||
|
} while (vm && vm.parent && vm !== vm.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.depth = depth
|
context.depth = depth
|
||||||
@@ -50,7 +53,6 @@ export function resolve (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (resolvedOption) {
|
if (resolvedOption) {
|
||||||
console.log(resolvedOption.value)
|
|
||||||
return resolvedOption.value
|
return resolvedOption.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-2
@@ -1,5 +1,4 @@
|
|||||||
import { ComponentInternalInstance } from 'vue'
|
import { ComponentInternalInstance } from 'vue'
|
||||||
import { Manager } from '../manager'
|
|
||||||
|
|
||||||
export type Immutable<T> = {
|
export type Immutable<T> = {
|
||||||
readonly [P in keyof T]: Immutable<T[P]>
|
readonly [P in keyof T]: Immutable<T[P]>
|
||||||
@@ -28,6 +27,11 @@ export interface MetainfoInput {
|
|||||||
[key: string]: TODO
|
[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
|
||||||
}
|
}
|
||||||
@@ -35,7 +39,14 @@ export interface MetainfoActive {
|
|||||||
export type MetaContext = {
|
export type MetaContext = {
|
||||||
id: string | symbol
|
id: string | symbol
|
||||||
vm?: ComponentInternalInstance
|
vm?: ComponentInternalInstance
|
||||||
manager: Manager
|
resolve: ActiveResolverMethod
|
||||||
|
active: Object
|
||||||
|
shadow: Object
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetaProxy = {
|
||||||
|
meta: MetainfoProxy
|
||||||
|
unmount: TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ActiveResolverSetup = (context: MetaContext) => void
|
export type ActiveResolverSetup = (context: MetaContext) => void
|
||||||
|
|||||||
+13
-34
@@ -1,16 +1,13 @@
|
|||||||
import { inject, getCurrentInstance, onUnmounted } from 'vue'
|
import { inject, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||||
import { setByObject, remove } from './info'
|
|
||||||
import { Manager } from './manager'
|
import { Manager } from './manager'
|
||||||
import { createProxy, createHandler } from './proxy'
|
import { metaInfoKey } from './symbols'
|
||||||
import { metaInfoKey, PolySymbol } from './symbols'
|
import { MetainfoActive, MetainfoInput, MetaProxy } from './types'
|
||||||
import { MetaContext, MetainfoActive, MetainfoInput } from './types'
|
|
||||||
|
|
||||||
let contextCounter: number = 0
|
export function useMeta (obj: MetainfoInput, manager?: Manager): MetaProxy {
|
||||||
|
|
||||||
export function useMeta (obj: MetainfoInput, manager?: Manager) {
|
|
||||||
const vm = getCurrentInstance()
|
const vm = getCurrentInstance()
|
||||||
if (vm) {
|
|
||||||
manager = vm.appContext.config.globalProperties.$metaManager
|
if (!manager && vm) {
|
||||||
|
manager = getCurrentManager(vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
@@ -18,35 +15,17 @@ export function useMeta (obj: MetainfoInput, manager?: Manager) {
|
|||||||
throw new Error('No manager or current instance')
|
throw new Error('No manager or current instance')
|
||||||
}
|
}
|
||||||
|
|
||||||
const context: MetaContext = {
|
return manager.createMetaProxy(obj, vm || undefined)
|
||||||
id: PolySymbol(`context ${contextCounter++}`),
|
|
||||||
vm: vm || undefined,
|
|
||||||
manager
|
|
||||||
}
|
|
||||||
|
|
||||||
const unmount = <T extends Function = () => any>() => remove(context)
|
|
||||||
if (vm) {
|
|
||||||
onUnmounted(unmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.resolver.setup(context)
|
|
||||||
|
|
||||||
setByObject(context, obj)
|
|
||||||
|
|
||||||
const handler = /* #__PURE__ */ createHandler(context)
|
|
||||||
const meta = createProxy(obj, handler)
|
|
||||||
|
|
||||||
return {
|
|
||||||
meta,
|
|
||||||
unmount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMetainfo (): MetainfoActive {
|
export function useMetainfo (): MetainfoActive {
|
||||||
return inject(metaInfoKey)!
|
return inject(metaInfoKey)!
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCurrentManager (): Manager {
|
export function getCurrentManager (vm?: ComponentInternalInstance): Manager {
|
||||||
const vm = getCurrentInstance()!
|
if (!vm) {
|
||||||
|
vm = getCurrentInstance()!
|
||||||
|
}
|
||||||
|
|
||||||
return vm.appContext.config.globalProperties.$metaManager
|
return vm.appContext.config.globalProperties.$metaManager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export function clone (v: any): any {
|
|||||||
const res: any = {}
|
const res: any = {}
|
||||||
|
|
||||||
for (const key in v) {
|
for (const key in v) {
|
||||||
|
// never clone the context
|
||||||
if (key === 'context') {
|
if (key === 'context') {
|
||||||
res[key] = v[key]
|
res[key] = v[key]
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { createProxy, createHandler } from '../../src/continuous-object-merge'
|
||||||
|
|
||||||
|
describe('proxy', () => {
|
||||||
|
let context
|
||||||
|
beforeEach(() => {
|
||||||
|
context = {
|
||||||
|
active: {},
|
||||||
|
shadow: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('proxy (get)', () => {
|
||||||
|
const target = {
|
||||||
|
str: 'test',
|
||||||
|
obj: {
|
||||||
|
str: 'test'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = createHandler(context)
|
||||||
|
const proxy = createProxy(target, handler)
|
||||||
|
|
||||||
|
expect(proxy.str).toBe('test')
|
||||||
|
expect(proxy.obj.str).toBe('test')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('string (set, update, delete)', () => {
|
||||||
|
const target = {}
|
||||||
|
|
||||||
|
const handler = createHandler(context)
|
||||||
|
const proxy = createProxy(target, handler)
|
||||||
|
|
||||||
|
proxy.str = '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'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
expect(context.active.str).toBeUndefined()
|
||||||
|
expect(context.shadow.str.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('array (set, update, delete)', () => {
|
||||||
|
const target = {}
|
||||||
|
|
||||||
|
const handler = createHandler(context)
|
||||||
|
const proxy = createProxy(target, handler)
|
||||||
|
|
||||||
|
proxy.arr = [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
|
||||||
|
|
||||||
|
expect(context.active.arr).toEqual(expect.arrayContaining([0, 2]))
|
||||||
|
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]
|
||||||
|
|
||||||
|
expect(context.active.arr).toEqual(expect.not.arrayContaining([2]))
|
||||||
|
expect(context.shadow.arr[0]).toMatchObject({
|
||||||
|
context,
|
||||||
|
value: expect.not.arrayContaining([2])
|
||||||
|
})
|
||||||
|
|
||||||
|
delete proxy.arr
|
||||||
|
|
||||||
|
expect(context.active.arr).toBeUndefined()
|
||||||
|
expect(context.shadow.arr).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('proxy (set object)', () => {
|
||||||
|
const target = {}
|
||||||
|
|
||||||
|
const handler = createHandler(context)
|
||||||
|
const proxy = createProxy(target, handler)
|
||||||
|
|
||||||
|
proxy.obj = { str: 'test' }
|
||||||
|
|
||||||
|
expect(context.active.obj).toBeDefined()
|
||||||
|
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)', () => {
|
||||||
|
const target = {}
|
||||||
|
|
||||||
|
const handler = createHandler(context)
|
||||||
|
const proxy = createProxy(target, handler)
|
||||||
|
|
||||||
|
proxy.obj = { str: 'test' }
|
||||||
|
|
||||||
|
expect(context.active.obj).toBeDefined()
|
||||||
|
|
||||||
|
delete proxy.obj
|
||||||
|
|
||||||
|
expect(context.active.obj).not.toBeDefined()
|
||||||
|
expect(context.shadow.obj).not.toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('proxy (remove child)', () => {
|
||||||
|
const target = {}
|
||||||
|
|
||||||
|
const handler = createHandler(context)
|
||||||
|
const proxy = createProxy(target, handler)
|
||||||
|
|
||||||
|
proxy.obj = { str: 'test' }
|
||||||
|
|
||||||
|
expect(context.active.obj).toBeDefined()
|
||||||
|
|
||||||
|
delete proxy.obj.str
|
||||||
|
|
||||||
|
expect(context.active.obj).toEqual({})
|
||||||
|
expect(context.shadow.obj).toEqual({ str: [] })
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -79,6 +79,43 @@ describe('render', () => {
|
|||||||
expect(res.vnode.props).toMatchObject({ name: 'DescriptionTest2', content: 'my description 2' })
|
expect(res.vnode.props).toMatchObject({ name: 'DescriptionTest2', content: 'my description 2' })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('render key-object element with json', () => {
|
||||||
|
const context = {}
|
||||||
|
const key = 'JsonTest'
|
||||||
|
const data = { json: ['content'] }
|
||||||
|
const config = {
|
||||||
|
tag: 'script'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = render.renderMeta(context, key, data, config)
|
||||||
|
// console.log('RES', res)
|
||||||
|
|
||||||
|
expect(res.to).toBeUndefined()
|
||||||
|
expect(res.vnode).toMatchObject({ __v_isVNode: true })
|
||||||
|
|
||||||
|
expect(res.vnode.type).toEqual('script')
|
||||||
|
expect(res.vnode.props).toMatchObject({})
|
||||||
|
expect(res.vnode.children).toEqual('["content"]')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('render key-object element with raw content', () => {
|
||||||
|
const context = {}
|
||||||
|
const key = 'RawTest'
|
||||||
|
const data = { rawContent: '<p>One JS please!</p>' }
|
||||||
|
const config = {
|
||||||
|
tag: 'noscript'
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = render.renderMeta(context, key, data, config)
|
||||||
|
// console.log('RES', res)
|
||||||
|
|
||||||
|
expect(res.to).toBeUndefined()
|
||||||
|
expect(res.vnode).toMatchObject({ __v_isVNode: true })
|
||||||
|
|
||||||
|
expect(res.vnode.type).toEqual('noscript')
|
||||||
|
expect(res.vnode.props).toMatchObject({ innerHTML: '<p>One JS please!</p>' })
|
||||||
|
})
|
||||||
|
|
||||||
test('render array<key-string> elements', () => {
|
test('render array<key-string> elements', () => {
|
||||||
const context = {}
|
const context = {}
|
||||||
const key = 'kal-el'
|
const key = 'kal-el'
|
||||||
@@ -429,4 +466,48 @@ describe('render', () => {
|
|||||||
metainfo: context.metainfo
|
metainfo: context.metainfo
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('render attributes (add + remove)', () => {
|
||||||
|
const key = 'bodyAttrs'
|
||||||
|
const data = { class: 'theme-dark' }
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
attributesFor: 'body'
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAttribute = jest.fn()
|
||||||
|
const removeAttribute = jest.fn()
|
||||||
|
|
||||||
|
const doc = jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||||
|
setAttribute,
|
||||||
|
removeAttribute
|
||||||
|
})
|
||||||
|
|
||||||
|
const context = {}
|
||||||
|
|
||||||
|
const res = render.renderMeta(context, key, data, config)
|
||||||
|
// console.log('RES', res)
|
||||||
|
|
||||||
|
expect(res).toBeUndefined()
|
||||||
|
|
||||||
|
expect(doc).toHaveBeenCalledTimes(1)
|
||||||
|
expect(doc).toHaveBeenCalledWith('body')
|
||||||
|
|
||||||
|
expect(setAttribute).toHaveBeenCalledTimes(1)
|
||||||
|
expect(setAttribute).toHaveBeenCalledWith('class', 'theme-dark')
|
||||||
|
|
||||||
|
const dataUpdate = { 'data-content': ['a', 'b'] }
|
||||||
|
render.renderMeta(context, key, dataUpdate, config)
|
||||||
|
|
||||||
|
expect(doc).toHaveBeenCalledTimes(1)
|
||||||
|
expect(doc).toHaveBeenCalledWith('body')
|
||||||
|
|
||||||
|
expect(setAttribute).toHaveBeenCalledTimes(2)
|
||||||
|
expect(setAttribute).toHaveBeenCalledWith('data-content', 'a,b')
|
||||||
|
|
||||||
|
expect(removeAttribute).toHaveBeenCalledTimes(1)
|
||||||
|
expect(removeAttribute).toHaveBeenCalledWith('class')
|
||||||
|
|
||||||
|
doc.mockRestore()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import { createProxy, createHandler, setByObject, remove } from '../../src/continuous-object-merge'
|
||||||
|
|
||||||
|
describe('resolve', () => {
|
||||||
|
let active, shadow
|
||||||
|
let context1, context2
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
active = {}
|
||||||
|
shadow = {}
|
||||||
|
|
||||||
|
const resolve = (key, pathSegments, getOptions, getCurrentValue) => {
|
||||||
|
const options = getOptions()
|
||||||
|
console.log('RESOLVE', options)
|
||||||
|
return options[options.length - 1].value
|
||||||
|
}
|
||||||
|
|
||||||
|
context1 = { active, shadow, resolve }
|
||||||
|
context2 = { active, shadow, resolve }
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolve (string)', () => {
|
||||||
|
const target1 = {
|
||||||
|
str: 'string value 1'
|
||||||
|
}
|
||||||
|
|
||||||
|
const target2 = {
|
||||||
|
str: 'string value 2'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial value
|
||||||
|
setByObject(context1, target1)
|
||||||
|
|
||||||
|
// Init proxy
|
||||||
|
const handler1 = createHandler(context1)
|
||||||
|
const proxy1 = createProxy(target1, handler1)
|
||||||
|
|
||||||
|
setByObject(context2, target2)
|
||||||
|
const handler2 = createHandler(context2)
|
||||||
|
const proxy2 = createProxy(target2, handler2)
|
||||||
|
|
||||||
|
expect(active.str).toBe('string value 2')
|
||||||
|
|
||||||
|
proxy2.str = 'test'
|
||||||
|
|
||||||
|
expect(active.str).toBe('test')
|
||||||
|
|
||||||
|
remove(context2)
|
||||||
|
|
||||||
|
expect(active.str).toBe('string value 1')
|
||||||
|
|
||||||
|
remove(context1)
|
||||||
|
|
||||||
|
expect(active.str).toBeUndefined()
|
||||||
|
expect(shadow.str.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('resolve (object)', () => {
|
||||||
|
const target1 = {
|
||||||
|
obj: {
|
||||||
|
key: 'object value 1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const target2 = {
|
||||||
|
obj: {
|
||||||
|
key: 'object value 2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial value
|
||||||
|
setByObject(context1, target1)
|
||||||
|
|
||||||
|
// Init proxy
|
||||||
|
const handler1 = createHandler(context1)
|
||||||
|
const proxy1 = createProxy(target1, handler1)
|
||||||
|
|
||||||
|
setByObject(context2, target2)
|
||||||
|
const handler2 = createHandler(context2)
|
||||||
|
const proxy2 = createProxy(target2, handler2)
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('object value 2')
|
||||||
|
|
||||||
|
proxy2.obj.key = 'test'
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('test')
|
||||||
|
|
||||||
|
proxy2.obj = { key: 'test again' }
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('test again')
|
||||||
|
expect(shadow.obj.key.length).toBe(2)
|
||||||
|
|
||||||
|
remove(context2)
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('object value 1')
|
||||||
|
|
||||||
|
remove(context1)
|
||||||
|
|
||||||
|
// TODO: should we clean up the obj ref too?
|
||||||
|
expect(active.obj).toEqual({})
|
||||||
|
|
||||||
|
expect(active.obj.key).toBeUndefined()
|
||||||
|
expect(shadow.obj.key.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.skip('resolve (array)', () => {
|
||||||
|
const target1 = {
|
||||||
|
arr: [
|
||||||
|
'array value 1'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const target2 = {
|
||||||
|
arr: [
|
||||||
|
'array value 2'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test this, is specifying a resolver enough or do we need to add an array-merge option
|
||||||
|
|
||||||
|
// Set initial value
|
||||||
|
setByObject(context1, target1)
|
||||||
|
|
||||||
|
// Init proxy
|
||||||
|
const handler1 = createHandler(context1)
|
||||||
|
const proxy1 = createProxy(target1, handler1)
|
||||||
|
|
||||||
|
setByObject(context2, target2)
|
||||||
|
const handler2 = createHandler(context2)
|
||||||
|
const proxy2 = createProxy(target2, handler2)
|
||||||
|
|
||||||
|
expect(active.arr).toBe('object value 2')
|
||||||
|
|
||||||
|
proxy2.obj.key = 'test'
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('test')
|
||||||
|
|
||||||
|
proxy2.obj = { key: 'test again' }
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('test again')
|
||||||
|
expect(shadow.obj.key.length).toBe(2)
|
||||||
|
|
||||||
|
remove(context2)
|
||||||
|
|
||||||
|
expect(active.obj.key).toBe('object value 1')
|
||||||
|
|
||||||
|
remove(context1)
|
||||||
|
|
||||||
|
// TODO: should we clean up the obj ref too?
|
||||||
|
expect(active.obj).toEqual({})
|
||||||
|
|
||||||
|
expect(active.obj.key).toBeUndefined()
|
||||||
|
expect(shadow.obj.key.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.skip('resolve (collection)', () => {
|
||||||
|
const target1 = {
|
||||||
|
arr: [
|
||||||
|
{ key: 'collection value 1' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const target2 = {
|
||||||
|
arr: [
|
||||||
|
{ key: 'collection value 2' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial value
|
||||||
|
setByObject(context1, target1)
|
||||||
|
|
||||||
|
// Init proxy
|
||||||
|
const handler1 = createHandler(context1)
|
||||||
|
const proxy1 = createProxy(target1, handler1)
|
||||||
|
|
||||||
|
setByObject(context2, target2)
|
||||||
|
const handler2 = createHandler(context2)
|
||||||
|
const proxy2 = createProxy(target2, handler2)
|
||||||
|
|
||||||
|
expect(active.str).toBe('string value 2')
|
||||||
|
|
||||||
|
proxy2.str = 'test'
|
||||||
|
|
||||||
|
expect(active.str).toBe('test')
|
||||||
|
|
||||||
|
expect(shadow.str).toBeInstanceOf(Array)
|
||||||
|
expect(shadow.str.length).toBe(2)
|
||||||
|
|
||||||
|
remove(context2)
|
||||||
|
|
||||||
|
expect(active.str).toBe('string value 1')
|
||||||
|
|
||||||
|
remove(context1)
|
||||||
|
|
||||||
|
expect(active.str).toBeUndefined()
|
||||||
|
expect(shadow.str.length).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import * as utils from '../../src/utils'
|
||||||
|
|
||||||
|
describe('utils.clone', () => {
|
||||||
|
test('string', () => {
|
||||||
|
const str = 'test'
|
||||||
|
expect(utils.clone(str)).toBe(str)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('array', () => {
|
||||||
|
const arr = ['test']
|
||||||
|
const crr = utils.clone(arr)
|
||||||
|
|
||||||
|
expect(crr).not.toBe(arr)
|
||||||
|
expect(crr[0]).toBe(arr[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('object', () => {
|
||||||
|
const obj = { context: {}, child: {}, a: 1 }
|
||||||
|
const cbj = utils.clone(obj)
|
||||||
|
|
||||||
|
expect(cbj).not.toBe(obj)
|
||||||
|
expect(cbj.context).toBe(obj.context)
|
||||||
|
expect(cbj.child).not.toBe(obj.child)
|
||||||
|
expect(cbj.a).toBe(obj.a)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user