提交 7ce03da7 编写于 作者: DCloud-WZF's avatar DCloud-WZF 💬

Merge branch 'dev' into alpha

<script lang="uts"> <script lang="uts">
import { state, setLifeCycleNum, setAppLaunchPath, setAppShowPath } from './store/index.uts' import { state, setLifeCycleNum, setAppLaunchPath, setAppShowPath } from './store/index.uts'
let firstBackTime = 0 let firstBackTime = 0
export default { export default {
// #ifndef APP-ANDROID // #ifndef APP-ANDROID
mixins: [ mixins: [
{ {
data() { data() {
return { return {
appMixinDataMsg: 'App.uvue mixin data msg' appMixinDataMsg: 'App.uvue mixin data msg'
}
} }
} }],
}],
// #endif
onLaunch: function (options) {
console.log(options)
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1000)
setAppLaunchPath(options.path)
console.log('App Launch')
// #ifdef UNI-APP-X && APP-ANDROID
const performance = uni.getPerformance()
const observer : PerformanceObserver = performance.createObserver((entryList : PerformanceObserverEntryList) => {
console.log('observer:entryList.getEntries()')
console.log(entryList.getEntries())
})
observer.observe({
entryTypes: ['render', 'navigation'],
} as PerformanceObserverOptions)
// #endif // #endif
}, onLaunch: function (options) {
onShow: function (options) { console.log(options)
// 自动化测试 // 自动化测试
setLifeCycleNum(state.lifeCycleNum + 100) setLifeCycleNum(state.lifeCycleNum + 1000)
setAppShowPath(options.path) setAppLaunchPath(options.path)
console.log('App Show') console.log('App Launch')
}, // #ifdef UNI-APP-X && APP-ANDROID
onHide: function () { const performance = uni.getPerformance()
// 自动化测试 const observer : PerformanceObserver = performance.createObserver((entryList : PerformanceObserverEntryList) => {
setLifeCycleNum(state.lifeCycleNum - 100) console.log('observer:entryList.getEntries()')
console.log('App Hide') console.log(entryList.getEntries())
},
// #ifdef APP-ANDROID
onLastPageBackPress: function () {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1000)
console.log('App LastPageBackPress')
if (firstBackTime == 0) {
uni.showToast({
title: '再按一次退出应用',
position: 'bottom',
}) })
firstBackTime = Date.now() observer.observe({
setTimeout(() => { entryTypes: ['render', 'navigation'],
firstBackTime = 0 } as PerformanceObserverOptions)
}, 2000) // #endif
} else if (Date.now() - firstBackTime < 2000) { },
firstBackTime = Date.now() onShow: function (options) {
uni.exit() // 自动化测试
} setLifeCycleNum(state.lifeCycleNum + 100)
}, setAppShowPath(options.path)
onExit() { if(this.globalPropertiesStr === 'default string'){
console.log('App Exit') setLifeCycleNum(state.lifeCycleNum + 10)
},
// #endif
onError: function(error: any) {
console.log('App Error', error)
setLifeCycleNum(state.lifeCycleNum + 100)
},
methods: {
checkLaunchPath() : boolean {
const HOME_PATH = 'pages/index/index'
if (state.appLaunchPath != HOME_PATH) {
return false
}
if (state.appShowPath != HOME_PATH) {
return false
} }
return true console.log('App Show')
}, },
// #ifndef APP-ANDROID onHide: function () {
checkAppMixin() : boolean { // 自动化测试
if(this.globalMixinDataMsg1 != '通过 defineMixin 定义全局 mixin data') { setLifeCycleNum(state.lifeCycleNum - 100)
return false console.log('App Hide')
},
// #ifdef APP-ANDROID
onLastPageBackPress: function () {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1000)
console.log('App LastPageBackPress')
if (firstBackTime == 0) {
uni.showToast({
title: '再按一次退出应用',
position: 'bottom',
})
firstBackTime = Date.now()
setTimeout(() => {
firstBackTime = 0
}, 2000)
} else if (Date.now() - firstBackTime < 2000) {
firstBackTime = Date.now()
uni.exit()
} }
if(this.appMixinDataMsg != 'App.uvue mixin data msg') { },
return false onExit() {
console.log('App Exit')
},
// #endif
onError: function(error: any) {
console.log('App Error', error)
setLifeCycleNum(state.lifeCycleNum + 100)
},
methods: {
checkLaunchPath() : boolean {
const HOME_PATH = 'pages/index/index'
if (state.appLaunchPath != HOME_PATH) {
return false
}
if (state.appShowPath != HOME_PATH) {
return false
}
return true
},
// #ifndef APP-ANDROID
checkAppMixin() : boolean {
if(this.globalMixinDataMsg1 != '通过 defineMixin 定义全局 mixin data') {
return false
}
if(this.appMixinDataMsg != 'App.uvue mixin data msg') {
return false
}
return true
} }
return true // #endif
} }
// #endif
} }
}
</script> </script>
<style> <style>
@import './styles/common.css'; @import './styles/common.css';
.list-item-text { .list-item-text {
line-height: 36px; line-height: 36px;
} }
.split-title { .split-title {
margin: 20px 0 5px; margin: 20px 0 5px;
padding: 5px 0; padding: 5px 0;
border-bottom: 1px solid #dfdfdf; border-bottom: 1px solid #dfdfdf;
} }
.btn-view { .btn-view {
margin: 10px 0; margin: 10px 0;
padding: 10px; padding: 10px;
border: 1px solid #dfdfdf; border: 1px solid #dfdfdf;
border-radius: 3px; border-radius: 3px;
} }
</style> </style>
<style> <style>
.text-red{ .text-red{
color: red; color: red;
} }
</style> </style>
\ No newline at end of file
const path = require('path')
module.exports = { module.exports = {
testTimeout: 10000, testTimeout: 10000,
reporters: ['default'], reporters: ['default'],
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'], watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
moduleFileExtensions: ['js', 'json'], moduleFileExtensions: ['js', 'json'],
rootDir: __dirname, rootDir: __dirname,
testMatch: ['<rootDir>/pages/**/**/*.test.js'], testMatch: ["<rootDir>/pages/App.test.js"],
testPathIgnorePatterns: ['/node_modules/'], testPathIgnorePatterns: ['/node_modules/'],
setupFilesAfterEnv: ['<rootDir>/jest-setup.js'], setupFilesAfterEnv: ['<rootDir>/jest-setup.js'],
testSequencer: path.join(__dirname, "testSequencer.js")
} }
...@@ -8,5 +8,8 @@ describe("app launch & show options", () => { ...@@ -8,5 +8,8 @@ describe("app launch & show options", () => {
if (!process.env.uniTestPlatformInfo.startsWith('android')) { if (!process.env.uniTestPlatformInfo.startsWith('android')) {
expect(await page.callMethod("checkAppMixin")).toBe(true) expect(await page.callMethod("checkAppMixin")).toBe(true)
} }
const lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(1110)
}) })
}) })
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
num: 10, num: 10,
arr: [4, 5, 6] arr: [4, 5, 6]
} as Obj, } as Obj,
refElement: null, refElement: null as UniElement | null,
refElementIsSame: false refElementIsSame: false
} }
}, },
......
const PAGE_PATH = "/pages/component-instance/methods/call-method-easycom-uni-modules-composition"
let page
beforeAll(async () => {
page = await program.reLaunch(PAGE_PATH)
await page.waitFor('view')
})
it('callMethodTest', async () => {
// a[[]] only issue 8582
if (process.env.uniTestPlatformInfo.toLowerCase().startsWith('web')) {
expect(1).toBe(1)
return
}
const delay = () =>
new Promise((resolve, _) => {
setTimeout(() => {
resolve('')
}, 1000)
})
await page.callMethod('onButtonClick')
await delay()
const resStr1 = await page.$("#isNumListValid")
const resStr2 = await page.$("#isObjListValid")
expect(await resStr1.text()).toBe(`true`)
expect(await resStr2.text()).toBe(`true`)
})
<template> <template>
<view> <view>
<call-easy-method-uni-modules ref="callEasyMethod1"></call-easy-method-uni-modules> <call-easy-method-uni-modules ref="callEasyMethod1"></call-easy-method-uni-modules>
<!-- #ifdef APP -->
<view>---</view>
<test-props id="btn1" :numList="numList" :objList='objList' @buttonclick='onButtonClick'
@numListChange='numListChange' @objListChange='objListChange'
style="width: 80px;height: 30px;background-color: lightblue"></test-props>
<view style="flex-direction: row ;">
<text>isNumListValid: </text>
<text id='isNumListValid'>{{isNumListValid}}</text>
</view>
<view style="flex-direction: row ;">
<text>isObjListValid: </text>
<text id='isObjListValid'>{{isObjListValid}}</text>
</view>
<!-- #endif -->
</view> </view>
</template> </template>
<script setup lang="uts"> <script setup lang="uts">
import { testInOtherFile } from './call-method-easycom-uni-modules' import { testInOtherFile } from './call-method-easycom-uni-modules'
import { ref, isProxy, isRef } from 'vue'
const delay = (): Promise<string> => // #ifdef APP
new Promise((resolve, _) => { import { PropsChangeEvent } from '@/uni_modules/test-props'
setTimeout(() => { // #endif
resolve('')
}, 1000) const delay = () : Promise<string> =>
new Promise((resolve, _) => {
setTimeout(() => {
resolve('')
}, 1000)
})
const callEasyMethod1 = ref<CallEasyMethodUniModulesComponentPublicInstance | null>(null)
const numList = ref<number[]>([1]) // 传递 props
const objList = ref<any[]>([])
const isNumListValid = ref(false)
const isObjListValid = ref(false)
const callMethod1 = () => {
// 调用组件的 foo1 方法
callEasyMethod1.value?.foo1?.()
}
const callMethod2 = () => {
// 调用组件的 foo2 方法并传递 1个参数
callEasyMethod1.value?.foo2?.(Date.now())
}
const callMethod3 = () => {
// 调用组件的 foo3 方法并传递 2个参数
callEasyMethod1.value?.foo3?.(Date.now(), Date.now())
}
const callMethod4 = () => {
// 调用组件的 foo4 方法并传递 callback
callEasyMethod1.value?.foo4?.(() => {
console.log('callback')
})
}
const callMethod5 = () => {
// 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
const result = callEasyMethod1.value?.foo5?.('string5') as string
console.log(result) // string1
}
const callMethodTest = (text : string) : string | null => {
const result = callEasyMethod1.value?.foo5?.(text) as string
return result
}
const callMethodInOtherFile = (text : string) : string => {
return testInOtherFile(callEasyMethod1.value!, text)
}
// #ifdef APP-ANDROID
const numListChange = (res : Map<string, Map<string, any>>) => {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isNumListValid.value = isArray && isLengthGt0
}
// #endif
// #ifdef APP-IOS
const numListChange = (res : any) => {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isNumListValid.value = isArray && isLengthGt0
}
// #endif
// #ifdef APP-ANDROID
const objListChange = (res : Map<string, Map<string, any>>) => {
const value = res['detail']!['value'] as any[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isObjListValid.value = isArray && isLengthGt0
}
// #endif
// #ifdef APP-IOS
const objListChange = (res : any) => {
const value = res['detail']!['value'] as any[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isObjListValid.value = isArray && isLengthGt0
}
// #endif
const onButtonClick = () => {
// 改变 props: 观察 props 返回值为非响应式值
numList.value = [3, 2, 1]
objList.value = [{ id: '3' }, { id: '4' }]
}
const call = async () : Promise<void> => {
callMethod1()
await delay()
callMethod2()
await delay()
callMethod3()
await delay()
callMethod4()
await delay()
callMethod5()
}
onReady(() => {
call()
}) })
const callEasyMethod1 = ref<CallEasyMethodUniModulesComponentPublicInstance | null>(null) defineExpose({
callMethodTest,
const callMethod1 = () => { callMethodInOtherFile,
// 调用组件的 foo1 方法 onButtonClick
callEasyMethod1.value?.foo1?.()
}
const callMethod2 = () => {
// 调用组件的 foo2 方法并传递 1个参数
callEasyMethod1.value?.foo2?.(Date.now())
}
const callMethod3 = () => {
// 调用组件的 foo3 方法并传递 2个参数
callEasyMethod1.value?.foo3?.(Date.now(), Date.now())
}
const callMethod4 = () => {
// 调用组件的 foo4 方法并传递 callback
callEasyMethod1.value?.foo4?.(() => {
console.log('callback')
}) })
} </script>
const callMethod5 = () => {
// 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
const result = callEasyMethod1.value?.foo5?.('string5') as string
console.log(result) // string1
}
const callMethodTest = (text: string): string | null => {
const result = callEasyMethod1.value?.foo5?.(text) as string
return result
}
const callMethodInOtherFile = (text: string): string => {
return testInOtherFile(callEasyMethod1.value!, text)
}
const call = async (): Promise<void> => {
callMethod1()
await delay()
callMethod2()
await delay()
callMethod3()
await delay()
callMethod4()
await delay()
callMethod5()
}
onReady(() => {
call()
})
defineExpose({
callMethodTest,
callMethodInOtherFile
})
</script>
\ No newline at end of file
const PAGE_PATH = "/pages/component-instance/methods/call-method-easycom-uni-modules-options"
let page
beforeAll(async () => {
page = await program.reLaunch(PAGE_PATH)
await page.waitFor('view')
})
it('callMethodTest', async () => {
// app only issue 8582
if (process.env.uniTestPlatformInfo.toLowerCase().startsWith('web')) {
expect(1).toBe(1)
return
}
const delay = () =>
new Promise((resolve, _) => {
setTimeout(() => {
resolve('')
}, 1000)
})
await page.callMethod('onButtonClick')
await delay()
const resStr1 = await page.$("#isNumListValid")
const resStr2 = await page.$("#isObjListValid")
expect(await resStr1.text()).toBe(`true`)
expect(await resStr2.text()).toBe(`true`)
})
<template> <template>
<view> <view>
<call-easy-method-uni-modules ref="callEasyMethod1"></call-easy-method-uni-modules> <call-easy-method-uni-modules ref="callEasyMethod1"></call-easy-method-uni-modules>
<!-- #ifdef APP -->
<view>---</view>
<test-props id="btn1" :numList="numList" :objList='objList' @buttonclick='onButtonClick'
@numListChange='numListChange' @objListChange='objListChange'
style="width: 80px;height: 30px;background-color: lightblue"></test-props>
<view style="flex-direction: row ;">
<text>isNumListValid: </text>
<text id='isNumListValid'>{{isNumListValid}}</text>
</view>
<view style="flex-direction: row ;">
<text>isObjListValid: </text>
<text id='isObjListValid'>{{isObjListValid}}</text>
</view>
<!-- #endif -->
</view> </view>
</template> </template>
<script lang="uts"> <script lang="uts">
import { testInOtherFile } from './call-method-easycom-uni-modules' import { testInOtherFile } from './call-method-easycom-uni-modules'
const delay = (): Promise<string> => const delay = () : Promise<string> =>
new Promise((resolve, _) => { new Promise((resolve, _) => {
setTimeout(() => { setTimeout(() => {
resolve('') resolve('')
}, 1000) }, 1000)
}) })
export default { export default {
data() { data() {
return { return {
callEasyMethod1: null as CallEasyMethodUniModulesComponentPublicInstance | null callEasyMethod1: null as CallEasyMethodUniModulesComponentPublicInstance | null,
} isWatched: false,
changeTimes: 0,
numList: [1] as number[], // 传递 props
objList: [] as any[],
isNumListValid: false,
isObjListValid: false
}
},
onReady() {
// 通过组件 ref 属性获取组件实例, 组件标签名首字母大写,驼峰+ComponentPublicInstance
this.callEasyMethod1 = this.$refs['callEasyMethod1'] as CallEasyMethodUniModulesComponentPublicInstance
this.call()
},
methods: {
async call() : Promise<void> {
this.callMethod1()
await delay()
this.callMethod2()
await delay()
this.callMethod3()
await delay()
this.callMethod4()
await delay()
this.callMethod5()
},
callMethod1() {
// 调用组件的 foo1 方法
this.callEasyMethod1?.foo1?.()
},
callMethod2() {
// 调用组件的 foo2 方法并传递 1个参数
this.callEasyMethod1?.foo2?.(Date.now())
},
callMethod3() {
// 调用组件的 foo3 方法并传递 2个参数
this.callEasyMethod1?.foo3?.(Date.now(), Date.now())
},
callMethod4() {
// 调用组件的 foo4 方法并传递 callback
this.callEasyMethod1?.foo4?.(() => {
console.log('callback')
})
},
callMethod5() {
// 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
const result = this.callEasyMethod1?.foo5?.('string5') as string
console.log(result) // string1
},
callMethodTest(text : string) : string | null {
const result = this.callEasyMethod1?.foo5?.(text) as string
return result
},
callMethodInOtherFile(text : string) : string {
return testInOtherFile(this.callEasyMethod1!, text)
},
// #ifdef APP-ANDROID
numListChange(res : Map<string, Map<string, any>>) {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
this.isNumListValid = isArray && isLengthGt0
}, },
onReady() { // #endif
// 通过组件 ref 属性获取组件实例, 组件标签名首字母大写,驼峰+ComponentPublicInstance // #ifdef APP-IOS
this.callEasyMethod1 = this.$refs['callEasyMethod1'] as CallEasyMethodUniModulesComponentPublicInstance numListChange(res : any) {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
this.isNumListValid = isArray && isLengthGt0
},
// #endif
this.call() // #ifdef APP-ANDROID
objListChange(res : Map<string, Map<string, any>>) {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
this.isObjListValid = isArray && isLengthGt0
}, },
methods: { // #endif
async call(): Promise<void> { // #ifdef APP-IOS
this.callMethod1() objListChange(res : any) {
await delay() const value = res['detail']!['value'] as number[]
this.callMethod2() const isArray = Array.isArray(value)
await delay() const isLengthGt0 = value.length > 0
this.callMethod3() this.isObjListValid = isArray && isLengthGt0
await delay() },
this.callMethod4() // #endif
await delay() onButtonClick() {
this.callMethod5() // 改变 props: 观察 props 返回值为非响应式值
}, console.log('button click');
callMethod1() { this.numList = [3, 2, 1]
// 调用组件的 foo1 方法 this.objList = [{ id: '3' }, { id: '4' }]
this.callEasyMethod1?.foo1?.() }
},
callMethod2() {
// 调用组件的 foo2 方法并传递 1个参数
this.callEasyMethod1?.foo2?.(Date.now())
},
callMethod3() {
// 调用组件的 foo3 方法并传递 2个参数
this.callEasyMethod1?.foo3?.(Date.now(), Date.now())
},
callMethod4() {
// 调用组件的 foo4 方法并传递 callback
this.callEasyMethod1?.foo4?.(() => {
console.log('callback')
})
},
callMethod5() {
// 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
const result = this.callEasyMethod1?.foo5?.('string5') as string
console.log(result) // string1
},
callMethodTest(text: string): string | null {
const result = this.callEasyMethod1?.foo5?.(text) as string
return result
},
callMethodInOtherFile(text: string): string {
return testInOtherFile(this.callEasyMethod1!, text)
} }
} }
} </script>
</script>
\ No newline at end of file
...@@ -24,33 +24,30 @@ defineOptions({ ...@@ -24,33 +24,30 @@ defineOptions({
mixins: [mixins], mixins: [mixins],
name: "$options", name: "$options",
_customKey: "custom key" _customKey: "custom key"
}) })
type DataInfo = { type DataInfo = {
name: string name: string
customKey: string customKey: string
mixinDataStr: string mixinDataStr: string
} }
const dataInfo = reactive({ const dataInfo = reactive({
name: "", name: "",
customKey: "", customKey: "",
mixinDataStr: "" mixinDataStr: ""
} as DataInfo) } as DataInfo)
onMounted(() => { onMounted(() => {
const instance = getCurrentInstance()!.proxy! const instance = getCurrentInstance()!.proxy!
// #ifdef APP-ANDROID
dataInfo.name = instance.$options.name
// #endif
// #ifndef APP-ANDROID
dataInfo.name = instance.$options.name! dataInfo.name = instance.$options.name!
// #ifndef APP-ANDROID
dataInfo.customKey = instance.$options._customKey dataInfo.customKey = instance.$options._customKey
dataInfo.mixinDataStr = instance.$options.data!({})!['str'] dataInfo.mixinDataStr = instance.$options.data!({})!['str']
// #endif // #endif
}) })
defineExpose({ defineExpose({
dataInfo dataInfo
}) })
</script> </script>
...@@ -19,12 +19,12 @@ ...@@ -19,12 +19,12 @@
<script lang="uts"> <script lang="uts">
import mixins from "./mixins.uts" import mixins from "./mixins.uts"
type DataInfo = { type DataInfo = {
name: string name: string
customKey: string customKey: string
mixinDataStr: string mixinDataStr: string
} }
export default { export default {
mixins: [mixins], mixins: [mixins],
...@@ -32,20 +32,17 @@ export default { ...@@ -32,20 +32,17 @@ export default {
_customKey: "custom key", _customKey: "custom key",
data() { data() {
return { return {
dataInfo: { dataInfo: {
name: "", name: "",
customKey: "", customKey: "",
mixinDataStr: "", mixinDataStr: "",
} as DataInfo } as DataInfo
} }
}, },
mounted() { mounted() {
// #ifdef APP-ANDROID
this.dataInfo.name = this.$options.name
// #endif
// #ifndef APP-ANDROID
this.dataInfo.name = this.$options.name! this.dataInfo.name = this.$options.name!
this.dataInfo.customKey = this.$options._customKey // #ifndef APP-ANDROID
this.dataInfo.customKey = this.$options._customKey
// @ts-ignore // @ts-ignore
this.dataInfo.mixinDataStr = this.$options.data({})['str'] this.dataInfo.mixinDataStr = this.$options.data({})['str']
// #endif // #endif
......
...@@ -73,6 +73,5 @@ ...@@ -73,6 +73,5 @@
background-color: v-bind(dataInfo.vBindClassBackgroundColor); background-color: v-bind(dataInfo.vBindClassBackgroundColor);
height: v-bind(dataInfo.vBindClassRpxHeight); height: v-bind(dataInfo.vBindClassRpxHeight);
} }
/* #endif */ /* #endif */
</style> </style>
\ No newline at end of file
...@@ -72,6 +72,5 @@ ...@@ -72,6 +72,5 @@
background-color: v-bind(dataInfo.vBindClassBackgroundColor); background-color: v-bind(dataInfo.vBindClassBackgroundColor);
height: v-bind(dataInfo.vBindClassRpxHeight); height: v-bind(dataInfo.vBindClassRpxHeight);
} }
/* #endif */ /* #endif */
</style> </style>
\ No newline at end of file
...@@ -8,11 +8,18 @@ ...@@ -8,11 +8,18 @@
<text>v-model:msg in Foo:</text> <text>v-model:msg in Foo:</text>
<text id="model-msg-text">{{ msg }}</text> <text id="model-msg-text">{{ msg }}</text>
</view> </view>
<view class="mb-10 flex justify-between flex-row"> <view class="mb-10 flex justify-between flex-row">
<text>defineModel num:</text> <text>defineModel num:</text>
<text id="model-num-text">{{ num }}</text> <text id="model-num-text">{{ num }}</text>
</view> </view>
<view class="mb-10 flex justify-between flex-row">
<text>defineModel strArr:</text>
<text id="model-str-arr-text">{{ JSON.stringify(strArr) }}</text>
</view>
<view class="mb-10 flex justify-between flex-row">
<text>defineModel numArr:</text>
<text id="model-num-arr-text">{{ JSON.stringify(numArr) }}</text>
</view>
<button class="mb-10" id="update-value-btn" @click="updateValue"> <button class="mb-10" id="update-value-btn" @click="updateValue">
update value update value
</button> </button>
...@@ -28,9 +35,14 @@ const msg = defineModel('msg', { type: String, default: 'default msg' }) ...@@ -28,9 +35,14 @@ const msg = defineModel('msg', { type: String, default: 'default msg' })
const num = defineModel('num', { type: Number, default: 1 }) const num = defineModel('num', { type: Number, default: 1 })
const strArr = defineModel<string[]>('strArr', { default: () => [] as string[] })
const numArr = defineModel('numArr', {type: Array as PropType<number[]>, required: true })
const updateValue = () => { const updateValue = () => {
modelValue.value += '1' modelValue.value += '1'
msg.value += '2' msg.value += '2'
num.value++ num.value++
strArr.value.push(`${strArr.value.length}`)
numArr.value.push(numArr.value.length)
} }
</script> </script>
...@@ -19,18 +19,28 @@ describe('defineModel', () => { ...@@ -19,18 +19,28 @@ describe('defineModel', () => {
const modelMsgInput = await page.$('#model-msg-input') const modelMsgInput = await page.$('#model-msg-input')
expect(await modelMsgInput.value()).toBe('msg') expect(await modelMsgInput.value()).toBe('msg')
const modelNumText = await page.$('#model-num-text') const modelNumText = await page.$('#model-num-text')
expect(await modelNumText.text()).toBe('1') expect(await modelNumText.text()).toBe('1')
const modelStrArrText = await page.$('#model-str-arr-text')
expect(await modelStrArrText.text()).toBe('["0"]')
const modelNumArrText = await page.$('#model-num-arr-text')
expect(await modelNumArrText.text()).toBe('[0]')
const updateValueBtn = await page.$('#update-value-btn') const updateValueBtn = await page.$('#update-value-btn')
await updateValueBtn.tap() await updateValueBtn.tap()
expect(await modelNumText.text()).toBe('2')
expect(await modelValueText.text()).toBe('str1') expect(await modelValueText.text()).toBe('str1')
expect(await modelValueInput.value()).toBe('str1') expect(await modelValueInput.value()).toBe('str1')
expect(await modelMsgText.text()).toBe('msg2') expect(await modelMsgText.text()).toBe('msg2')
expect(await modelMsgInput.value()).toBe('msg2') expect(await modelMsgInput.value()).toBe('msg2')
expect(await modelStrArrText.text()).toBe('["0","1"]')
expect(await modelNumArrText.text()).toBe('[0,1]')
const handleModelValueUpdateRes = await page.$('#handle-model-value-update-res') const handleModelValueUpdateRes = await page.$('#handle-model-value-update-res')
expect(await handleModelValueUpdateRes.text()).toBe('str1') expect(await handleModelValueUpdateRes.text()).toBe('str1')
......
...@@ -3,8 +3,11 @@ ...@@ -3,8 +3,11 @@
<Foo <Foo
v-model="str" v-model="str"
v-model:msg="msg" v-model:msg="msg"
v-model:strArr="strArr"
v-model:numArr="numArr"
@update:modelValue="handleModelValueUpdate" @update:modelValue="handleModelValueUpdate"
@update:msg="handleModelMsgUpdate" /> @update:msg="handleModelMsgUpdate"
/>
<input class="mb-10 input" id="model-value-input" v-model="str" /> <input class="mb-10 input" id="model-value-input" v-model="str" />
<input class="mb-10 input" id="model-msg-input" v-model="msg" /> <input class="mb-10 input" id="model-msg-input" v-model="msg" />
<view class="mb-10 flex justify-between flex-row"> <view class="mb-10 flex justify-between flex-row">
...@@ -23,6 +26,8 @@ import Foo from './Foo-composition.uvue' ...@@ -23,6 +26,8 @@ import Foo from './Foo-composition.uvue'
const str = ref('str') const str = ref('str')
const msg = ref('msg') const msg = ref('msg')
const strArr = ref<string[]>(['0'])
const numArr = ref<number[]>([0])
const handleModelValueUpdateRes = ref('') const handleModelValueUpdateRes = ref('')
const handleModelValueUpdate = (val : string) => { const handleModelValueUpdate = (val : string) => {
......
...@@ -39,7 +39,7 @@ describe('throw error', () => { ...@@ -39,7 +39,7 @@ describe('throw error', () => {
}) })
afterAll(async () => { afterAll(async () => {
const resetLifecycleNum = 1100 const resetLifecycleNum = 1110
await page.callMethod('setLifeCycleNum', resetLifecycleNum) await page.callMethod('setLifeCycleNum', resetLifecycleNum)
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(resetLifecycleNum) expect(lifeCycleNum).toBe(resetLifecycleNum)
......
...@@ -647,8 +647,8 @@ export default { ...@@ -647,8 +647,8 @@ export default {
{ {
id: 'directive', id: 'directive',
name: '指令', name: '指令',
pages: [ pages: [
// #ifndef APP-IOS // #ifndef APP-IOS
{ {
id: 'v-html', id: 'v-html',
name: 'v-html', name: 'v-html',
...@@ -664,7 +664,7 @@ export default { ...@@ -664,7 +664,7 @@ export default {
url: 'v-html-composition' url: 'v-html-composition'
}, },
] ]
}, },
// #endif // #endif
{ {
id: 'v-show', id: 'v-show',
...@@ -1227,13 +1227,13 @@ export default { ...@@ -1227,13 +1227,13 @@ export default {
// 自动化测试 // 自动化测试
checkLaunchPath() : boolean { checkLaunchPath() : boolean {
const app = getApp() const app = getApp()
return app.checkLaunchPath() return app.vm!.checkLaunchPath()
}, },
// #ifndef APP-ANDROID // #ifndef APP-ANDROID
// 自动化测试 // 自动化测试
checkAppMixin() : boolean { checkAppMixin() : boolean {
const app = getApp() const app = getApp()
return app.checkAppMixin() return app.vm.checkAppMixin()
} }
// #endif // #endif
} }
......
...@@ -57,7 +57,7 @@ onUnload(() => { ...@@ -57,7 +57,7 @@ onUnload(() => {
onBeforeMount(() => { onBeforeMount(() => {
// 自动化测试 // 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1) setLifeCycleNum(state.lifeCycleNum + 1)
console.log('component for lifecycle test mounted') console.log('component for lifecycle test onBeforeMount')
}) })
onMounted(() => { onMounted(() => {
...@@ -90,10 +90,16 @@ onUnmounted(() => { ...@@ -90,10 +90,16 @@ onUnmounted(() => {
console.log('component for lifecycle test unmounted') console.log('component for lifecycle test unmounted')
}) })
// TODO: app-android 不触发 onActivated(() => {
onActivated(() => { }) // 自动化测试
// TODO: app-android 不触发 setLifeCycleNum(state.lifeCycleNum + 1)
onDeactivated(() => { }) console.log('component for lifecycle test onActivated')
})
onDeactivated(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1)
console.log('component for lifecycle test onDeactivated')
})
const updateTitle = () => { const updateTitle = () => {
title.value = 'component for lifecycle test updated' title.value = 'component for lifecycle test updated'
......
...@@ -54,6 +54,16 @@ ...@@ -54,6 +54,16 @@
setLifeCycleNum(state.lifeCycleNum - 1); setLifeCycleNum(state.lifeCycleNum - 1);
console.log('component for lifecycle test unmounted'); console.log('component for lifecycle test unmounted');
}, },
activated() {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1);
console.log('component for lifecycle test activated');
},
deactivated() {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1);
console.log('component for lifecycle test deactivated');
},
methods: { methods: {
updateTitle() { updateTitle() {
this.title = 'component for lifecycle test updated'; this.title = 'component for lifecycle test updated';
......
...@@ -4,6 +4,9 @@ const HOME_PATH = '/pages/index/index' ...@@ -4,6 +4,9 @@ const HOME_PATH = '/pages/index/index'
describe('component-lifecycle', () => { describe('component-lifecycle', () => {
let page let page
let lifeCycleNum let lifeCycleNum
const platformInfo = process.env.uniTestPlatformInfo.toLocaleLowerCase()
const isAndroid = platformInfo.includes('android')
const isIos = platformInfo.includes('ios')
beforeAll(async () => { beforeAll(async () => {
page = await program.reLaunch(HOME_PATH) page = await program.reLaunch(HOME_PATH)
await page.waitFor(700) await page.waitFor(700)
...@@ -16,15 +19,29 @@ describe('component-lifecycle', () => { ...@@ -16,15 +19,29 @@ describe('component-lifecycle', () => {
await page.waitFor(700) await page.waitFor(700)
}) })
afterAll(async () => { afterAll(async () => {
const resetLifecycleNum = 1100 const resetLifecycleNum = 1110
await page.callMethod('setLifeCycleNum', resetLifecycleNum) await page.callMethod('setLifeCycleNum', resetLifecycleNum)
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(resetLifecycleNum) expect(lifeCycleNum).toBe(resetLifecycleNum)
}) })
it('onLoad onPageShow onReady onBeforeMount onMounted', async () => { it('onLoad onPageShow onReady onBeforeMount onMounted onActivated', async () => {
lifeCycleNum = await page.callMethod('pageGetLifeCycleNum')
// TODO: android 组合式 API 不触发 onActivated
expect(lifeCycleNum).toBe(isAndroid ? 112 : 113)
})
it('onDeactivated', async () => {
// TODO: android 组合式 API 不触发 onActivated onDeactivated
const toggleAliveComponentBtn = await page.$('#toggle-alive-component-btn')
await toggleAliveComponentBtn.tap()
lifeCycleNum = await page.callMethod('pageGetLifeCycleNum') lifeCycleNum = await page.callMethod('pageGetLifeCycleNum')
expect(lifeCycleNum).toBe(112) expect(lifeCycleNum).toBe(112)
await toggleAliveComponentBtn.tap()
lifeCycleNum = await page.callMethod('pageGetLifeCycleNum')
// TODO: android 端 组合式 API 不触发 activated
expect(lifeCycleNum).toBe(isAndroid ? 112 : 113)
await page.callMethod('pageSetLifeCycleNum', 0) await page.callMethod('pageSetLifeCycleNum', 0)
}) })
it('onBeforeUpdate onUpdated', async () => { it('onBeforeUpdate onUpdated', async () => {
...@@ -56,17 +73,18 @@ describe('component-lifecycle', () => { ...@@ -56,17 +73,18 @@ describe('component-lifecycle', () => {
page = await program.navigateTo(HOME_PATH) page = await program.navigateTo(HOME_PATH)
await page.waitFor('view') await page.waitFor('view')
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(-10) // App 端页面离开返回不触发 keepAlive 组件 activated deactivated, 详见 https://issues.dcloud.net.cn/pages/issues/detail?id=7419
expect(lifeCycleNum).toBe(isIos || isAndroid ? -10 : -11)
page = await program.navigateBack() page = await program.navigateBack()
await page.waitFor('view') await page.waitFor('view')
lifeCycleNum = await page.callMethod('pageGetLifeCycleNum') lifeCycleNum = await page.callMethod('pageGetLifeCycleNum')
expect(lifeCycleNum).toBe(0) expect(lifeCycleNum).toBe(0)
await page.callMethod('pageSetLifeCycleNum', 0)
}) })
it('beforeUnmount unmounted onUnload onBackPress', async () => { it('onDeactivated beforeUnmount unmounted onUnload onBackPress', async () => {
page = await program.navigateBack() page = await program.navigateBack()
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(-112) // TODO: android 组合式 API 不触发 onDeactivated
expect(lifeCycleNum).toBe(isAndroid ? -112 : -113)
await page.callMethod('setLifeCycleNum', 0) await page.callMethod('setLifeCycleNum', 0)
}) })
}) })
\ No newline at end of file
...@@ -4,8 +4,12 @@ ...@@ -4,8 +4,12 @@
<!-- #endif --> <!-- #endif -->
<view class="page container"> <view class="page container">
<text class="mb-10">component lifecycle 组合式 API</text> <text class="mb-10">component lifecycle 组合式 API</text>
<child-component @updateIsScroll="updateIsScroll" /> <keep-alive>
<button class="mt-10" @click="scrollToBottom">scrollToBottom</button> <component :is="aliveComponent" @updateIsScroll="updateIsScroll" />
</keep-alive>
<button class="mt-10" @click="scrollToBottom">scrollToBottom</button>
<button id="toggle-alive-component-btn" class="mt-10" @click="toggleAliveComponent">toggle alive component</button>
<button class="mt-10" @click="navigateToHome">navigateTo home</button>
</view> </view>
<!-- #ifdef APP --> <!-- #ifdef APP -->
</scroll-view> </scroll-view>
...@@ -16,6 +20,8 @@ ...@@ -16,6 +20,8 @@
import ChildComponent from './ChildComponentComposition.uvue' import ChildComponent from './ChildComponentComposition.uvue'
import { state, setLifeCycleNum } from '@/store/index.uts' import { state, setLifeCycleNum } from '@/store/index.uts'
const aliveComponent = shallowRef<any | null>(ChildComponent)
const isScrolled = ref(false) const isScrolled = ref(false)
// 自动化测试 // 自动化测试
...@@ -24,7 +30,7 @@ ...@@ -24,7 +30,7 @@
} }
// 自动化测试 // 自动化测试
const pageSetLifeCycleNum = (num : number) => { const pageSetLifeCycleNum = (num : number) => {
setLifeCycleNum(num) setLifeCycleNum(num)
} }
...@@ -44,6 +50,10 @@ ...@@ -44,6 +50,10 @@
scrollTop: 3000, scrollTop: 3000,
}) })
} }
const toggleAliveComponent = () => {
aliveComponent.value = aliveComponent.value == null ? ChildComponent : null
}
const updateIsScroll = (val : boolean) => { const updateIsScroll = (val : boolean) => {
isScrolled.value = val isScrolled.value = val
...@@ -52,6 +62,12 @@ ...@@ -52,6 +62,12 @@
// 自动化测试 // 自动化测试
const getIsScrolled = () : boolean => { const getIsScrolled = () : boolean => {
return isScrolled.value return isScrolled.value
}
const navigateToHome = () => {
uni.navigateTo({
url: '/pages/index/index'
})
} }
defineExpose({ defineExpose({
......
...@@ -2,39 +2,48 @@ const PAGE_PATH = '/pages/lifecycle/component/component-options' ...@@ -2,39 +2,48 @@ const PAGE_PATH = '/pages/lifecycle/component/component-options'
const HOME_PATH = '/pages/index/index' const HOME_PATH = '/pages/index/index'
describe('component-lifecycle', () => { describe('component-lifecycle', () => {
let page let page
let lifeCycleNum let lifeCycleNum
beforeAll(async () => { beforeAll(async () => {
page = await program.reLaunch(HOME_PATH) page = await program.reLaunch(HOME_PATH)
await page.waitFor(700) await page.waitFor(700)
const initLifecycleNum = 0 const initLifecycleNum = 0
await page.callMethod('setLifeCycleNum', initLifecycleNum) await page.callMethod('setLifeCycleNum', initLifecycleNum)
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(initLifecycleNum) expect(lifeCycleNum).toBe(initLifecycleNum)
page = await program.navigateTo(PAGE_PATH) page = await program.navigateTo(PAGE_PATH)
await page.waitFor(700) await page.waitFor(700)
}) })
afterAll(async () => { afterAll(async () => {
const resetLifecycleNum = 1100 const resetLifecycleNum = 1110
await page.callMethod('setLifeCycleNum', resetLifecycleNum) await page.callMethod('setLifeCycleNum', resetLifecycleNum)
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(resetLifecycleNum) expect(lifeCycleNum).toBe(resetLifecycleNum)
}) })
it('beforeCreate created beforeMount mounted', async () => { it('beforeCreate created beforeMount mounted activated', async () => {
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(4) expect(lifeCycleNum).toBe(5)
}) })
it('beforeUpdate updated', async () => { it('deactivated', async () => {
const updateTitleBtn = await page.$('.component-lifecycle-btn') const toggleAliveComponentBtn = await page.$('#toggle-alive-component-btn')
await updateTitleBtn.tap() await toggleAliveComponentBtn.tap()
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(6) expect(lifeCycleNum).toBe(4)
}) await toggleAliveComponentBtn.tap()
it('beforeUnmount unmounted', async () => { lifeCycleNum = await page.callMethod('getLifeCycleNum')
page = await program.navigateBack() expect(lifeCycleNum).toBe(5)
lifeCycleNum = await page.callMethod('getLifeCycleNum') })
expect(lifeCycleNum).toBe(4) it('beforeUpdate updated', async () => {
}) const updateTitleBtn = await page.$('.component-lifecycle-btn')
await updateTitleBtn.tap()
lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(7)
})
it('deactivated beforeUnmount unmounted', async () => {
page = await program.navigateBack()
lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(4)
})
}) })
\ No newline at end of file
<template> <template>
<view class="page"> <view class="page">
<text class="mb-10">component lifecycle 选项式 API</text> <text class="mb-10">component lifecycle 选项式 API</text>
<child-component /> <keep-alive>
<component :is="aliveComponent" />
</keep-alive>
<button id="toggle-alive-component-btn" class="mt-10" @click="toggleAliveComponent">toggle alive component</button>
<button class="mt-10" @click="navigateToHome">navigateTo home</button>
</view> </view>
</template> </template>
...@@ -11,11 +15,24 @@ import { state } from '@/store/index.uts' ...@@ -11,11 +15,24 @@ import { state } from '@/store/index.uts'
export default { export default {
components: { ChildComponent }, components: { ChildComponent },
data(){
return {
aliveComponent: ChildComponent as any | null,
}
},
methods: { methods: {
// 自动化测试 // 自动化测试
getLifeCycleNum(): number { getLifeCycleNum(): number {
return state.lifeCycleNum return state.lifeCycleNum
}, },
toggleAliveComponent(){
this.aliveComponent = this.aliveComponent == null ? ChildComponent : null
},
navigateToHome() {
uni.navigateTo({
url: '/pages/index/index'
})
}
}, },
} }
</script> </script>
...@@ -15,7 +15,7 @@ const initLifecycle = async () => { ...@@ -15,7 +15,7 @@ const initLifecycle = async () => {
} }
const testPageLifecycle = async (pagePath) => { const testPageLifecycle = async (pagePath) => {
// onLoad onShow onReady onResize // onLoad onShow onReady onResize
page = await program.reLaunch(pagePath) page = await program.reLaunch(pagePath)
await page.waitFor(1000) await page.waitFor(1000)
lifeCycleNum = await page.callMethod('pageGetLifeCycleNum') lifeCycleNum = await page.callMethod('pageGetLifeCycleNum')
expect(lifeCycleNum).toBe(120) expect(lifeCycleNum).toBe(120)
...@@ -60,7 +60,7 @@ const testPageLifecycle = async (pagePath) => { ...@@ -60,7 +60,7 @@ const testPageLifecycle = async (pagePath) => {
await page.waitFor(700) await page.waitFor(700)
lifeCycleNum = await page.callMethod('pageGetLifeCycleNum') lifeCycleNum = await page.callMethod('pageGetLifeCycleNum')
expect(lifeCycleNum).toBe(120) expect(lifeCycleNum).toBe(120)
page = await program.navigateBack() page = await program.navigateBack()
await page.waitFor('view') await page.waitFor('view')
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(10) expect(lifeCycleNum).toBe(10)
...@@ -72,7 +72,7 @@ describe('app-lifecycle', () => { ...@@ -72,7 +72,7 @@ describe('app-lifecycle', () => {
page = await program.reLaunch(HOME_PATH) page = await program.reLaunch(HOME_PATH)
await page.waitFor(700) await page.waitFor(700)
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(1100) expect(lifeCycleNum).toBe(1110)
}) })
it('onLastPageBackPress', async () => { it('onLastPageBackPress', async () => {
if (process.env.uniTestPlatformInfo.startsWith('android')) { if (process.env.uniTestPlatformInfo.startsWith('android')) {
...@@ -85,8 +85,8 @@ describe('app-lifecycle', () => { ...@@ -85,8 +85,8 @@ describe('app-lifecycle', () => {
}) })
describe('page-lifecycle', () => { describe('page-lifecycle', () => {
it('page-lifecycle options API', async () => { it('page-lifecycle options API', async () => {
await initLifecycle() await initLifecycle()
await testPageLifecycle(OPTIONS_PAGE_PATH) await testPageLifecycle(OPTIONS_PAGE_PATH)
}) })
...@@ -96,7 +96,7 @@ describe('page-lifecycle', () => { ...@@ -96,7 +96,7 @@ describe('page-lifecycle', () => {
}) })
afterAll(async () => { afterAll(async () => {
const resetLifecycleNum = 1100 const resetLifecycleNum = 1110
await page.callMethod('setLifeCycleNum', resetLifecycleNum) await page.callMethod('setLifeCycleNum', resetLifecycleNum)
lifeCycleNum = await page.callMethod('getLifeCycleNum') lifeCycleNum = await page.callMethod('getLifeCycleNum')
expect(lifeCycleNum).toBe(resetLifecycleNum) expect(lifeCycleNum).toBe(resetLifecycleNum)
......
const PAGE_PATH = '/pages/reactivity/core/reactive/reactive' const PAGE_PATH = '/pages/reactivity/core/reactive/reactive'
describe('reactive', () => { describe('reactive', () => {
let page = null let page = null
beforeAll(async () => { beforeAll(async () => {
page = await program.reLaunch(PAGE_PATH) page = await program.reLaunch(PAGE_PATH)
await page.waitFor('view') await page.waitFor('view')
}) })
it('basic', async () => { it('basic', async () => {
const count = await page.$('#count') const count = await page.$('#count')
expect(await count.text()).toBe('0') expect(await count.text()).toBe('0')
const objStr = await page.$('#obj-str') const objStr = await page.$('#obj-str')
expect(await objStr.text()).toBe('default str') expect(await objStr.text()).toBe('default str')
const objNum = await page.$('#obj-num') const objNum = await page.$('#obj-num')
expect(await objNum.text()).toBe('0') expect(await objNum.text()).toBe('0')
const objArr = await page.$('#obj-arr') const objArr = await page.$('#obj-arr')
expect(await objArr.text()).toBe('["a","b","c"]') expect(await objArr.text()).toBe('["a","b","c"]')
const updateCountBtn = await page.$('#update-count-btn') const arr1 = await page.$('#arr1')
await updateCountBtn.tap() expect(await arr1.text()).toBe('[]')
expect(await count.text()).toBe('1')
const updateCountBtn = await page.$('#update-count-btn')
const updateObjStrBtn = await page.$('#update-obj-str-btn') await updateCountBtn.tap()
await updateObjStrBtn.tap() expect(await count.text()).toBe('1')
expect(await objStr.text()).toBe('new str')
const updateObjStrBtn = await page.$('#update-obj-str-btn')
const updateObjNumBtn = await page.$('#update-obj-num-btn') await updateObjStrBtn.tap()
await updateObjNumBtn.tap() expect(await objStr.text()).toBe('new str')
expect(await count.text()).toBe('2')
expect(await objNum.text()).toBe('2') const updateObjNumBtn = await page.$('#update-obj-num-btn')
await updateObjNumBtn.tap()
const updateObjArrBtn = await page.$('#update-obj-arr-btn') expect(await count.text()).toBe('2')
await updateObjArrBtn.tap() expect(await objNum.text()).toBe('2')
expect(await objArr.text()).toBe('["a","b","c","d"]')
}) const updateObjArrBtn = await page.$('#update-obj-arr-btn')
await updateObjArrBtn.tap()
expect(await objArr.text()).toBe('["a","b","c","d"]')
const count1 = await page.$('#count1')
expect(await count1.text()).toBe('1')
const updateObj_A_B_C_Btn = await page.$('#update-obj1-a-b-c-btn')
await updateObj_A_B_C_Btn.tap()
expect(await count1.text()).toBe('2')
const updateArr1Btn = await page.$('#update-arr1-btn')
await updateArr1Btn.tap()
expect(await arr1.text()).toBe(JSON.stringify([1, 2, 3]))
const updateArr1ReactiveBtn = await page.$('#update-arr1-reactive-btn')
await updateArr1ReactiveBtn.tap()
expect(await arr1.text()).toBe(JSON.stringify([4, 5, 6]))
})
}) })
\ No newline at end of file
<template> <template>
<view class="page"> <view class="page">
<view class="flex justify-between flex-row mb-10"> <view class="flex justify-between flex-row mb-10">
<text>count:</text> <text>count:</text>
<text id="count">{{ count }}</text> <text id="count">{{ count }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.str:</text>
<text id="obj-str">{{ obj['str'] }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.num:</text>
<text id="obj-num">{{ obj['num'] }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.arr:</text>
<text id="obj-arr">{{ JSON.stringify(obj['arr']) }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>count1:</text>
<text id="count1">{{ count1 }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj1.a.b.c:</text>
<text id="obj1-a-b-c">{{ obj1.getString('a.b.c') }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>arr1(spread):</text>
<text id="arr1">{{ JSON.stringify(arr1) }}</text>
</view>
<button class='mb-10' id="update-count-btn" @click="updateCount">update count</button>
<button class='mb-10' id="update-obj-str-btn" @click="updateObjStr">update obj.str</button>
<button class='mb-10' id="update-obj-num-btn" @click="updateObjNum">update obj.num</button>
<button class='mb-10' id="update-obj-arr-btn" @click="updateObjArr">update obj.arr</button>
<button class='mb-10' id="update-obj1-a-b-c-btn" @click="updateObj1_A_B_C">update obj1.a.b.c</button>
<button class='mb-10' id="update-arr1-btn" @click="updateArr1(false)">update arr1 without reactive</button>
<button class='mb-10' id="update-arr1-reactive-btn" @click="updateArr1(true)">update arr1 with reactive</button>
</view> </view>
<view class="flex justify-between flex-row mb-10">
<text>obj.str:</text>
<text id="obj-str">{{ obj['str'] }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.num:</text>
<text id="obj-num">{{ obj['num'] }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.arr:</text>
<text id="obj-arr">{{ JSON.stringify(obj['arr']) }}</text>
</view>
<button class='mb-10' id="update-count-btn" @click="updateCount">update count</button>
<button class='mb-10' id="update-obj-str-btn" @click="updateObjStr">update obj.str</button>
<button class='mb-10' id="update-obj-num-btn" @click="updateObjNum">update obj.num</button>
<button class='mb-10' id="update-obj-arr-btn" @click="updateObjArr">update obj.arr</button>
</view>
</template> </template>
<script setup lang="uts"> <script setup lang="uts">
const count = ref(0) const count = ref(0)
// TODO: 待支持后补充泛型示例 // TODO: 待支持后补充泛型示例
const obj = reactive({ const obj = reactive({
str: 'default str', str: 'default str',
num: count, num: count,
arr: ['a', 'b', 'c'] arr: ['a', 'b', 'c']
}) })
const updateObjStr = () => { const updateObjStr = () => {
obj['str'] = 'new str'; obj['str'] = 'new str';
} }
const updateObjNum = () => { const updateObjNum = () => {
obj['num'] = (obj['num'] as number) + 1 obj['num'] = (obj['num'] as number) + 1
} }
const updateCount = () => { const updateCount = () => {
count.value++ count.value++
} }
const updateObjArr = () => { const updateObjArr = () => {
(obj['arr'] as string[]).push('d') (obj['arr'] as string[]).push('d')
} }
const obj1 = reactive({
a: { b: { c: 'c' } }
})
const count1 = ref(0)
watchEffect(() => {
count1.value++
// 测试getString等keyPath触发依赖收集
obj1.getString("a.b.c")
})
function updateObj1_A_B_C() {
((obj1["a"] as UTSJSONObject)["b"] as UTSJSONObject)["c"] = "c1-" + Date.now()
}
const arr1 = ref<number[]>([])
function test(...args : number[]) {
arr1.value = args
}
function updateArr1(isReactive : boolean) {
if (isReactive) {
test(...reactive([4, 5, 6]))
} else {
test(...[1, 2, 3])
}
}
</script> </script>
\ No newline at end of file
...@@ -156,7 +156,7 @@ ...@@ -156,7 +156,7 @@
watch: { watch: {
obj: { obj: {
handler(obj : Obj, prevObj ?: Obj) { handler(obj : Obj, prevObj ?: Obj) {
if (prevObj === null) { if (prevObj == null) {
this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: ${JSON.stringify(prevObj)}` this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: ${JSON.stringify(prevObj)}`
} else { } else {
// #ifdef WEB // #ifdef WEB
......
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
import CompForHFunction from '@/components/CompForHFunction.uvue' import CompForHFunction from '@/components/CompForHFunction.uvue'
const msg = ref('default msg') const msg = ref('default msg')
// 故意外部声明为UTSJSONObject
const msgProps = { class: 'uni-common-mt msg', style: { color: 'blue' } }
const render = ():VNode => h('view', { class: 'page' }, [ const render = ():VNode => h('view', { class: 'page' }, [
h(CompForHFunction, {}, (): VNode[] => [h('text', { class: 'comp-slot' }, 'component slot')]), h(CompForHFunction, {}, (): VNode[] => [h('text', { class: 'comp-slot' }, 'component slot')]),
h('text', { class: 'uni-common-mt msg', style: { color: 'blue' } }, msg.value), h('text', msgProps, msg.value),
h( h(
'button', 'button',
{ {
......
<script lang="uts"> <script lang="uts">
import CompForHFunction from '@/components/CompForHFunction.uvue' import CompForHFunction from '@/components/CompForHFunction.uvue'
// 故意外部声明为UTSJSONObject
const msgProps = { class: 'uni-common-mt msg', style: { color: 'blue' } }
export default { export default {
data() { data() {
return { return {
...@@ -9,7 +11,7 @@ export default { ...@@ -9,7 +11,7 @@ export default {
render(): VNode { render(): VNode {
return h('view', { class: 'page' }, [ return h('view', { class: 'page' }, [
h(CompForHFunction, {}, (): VNode[] => [h('text', { class: 'comp-slot' }, 'component slot')]), h(CompForHFunction, {}, (): VNode[] => [h('text', { class: 'comp-slot' }, 'component slot')]),
h('text', { class: 'uni-common-mt msg', style: { color: 'blue' } }, this.msg), h('text', msgProps, this.msg),
h( h(
'button', 'button',
{ {
......
const Sequencer = require("@jest/test-sequencer").default
const sortTestFilePaths = [
"pages/App.test.js",
]
class CustomSequencer extends Sequencer {
sort(tests) {
// 测试例排序
const sortedTests = sortTestFilePaths
.map((filePath) => {
return tests.find((test) => test.path.endsWith(filePath))
})
.filter(Boolean)
return [...new Set([...sortedTests, ...tests])]
}
}
module.exports = CustomSequencer
...@@ -4,10 +4,17 @@ ...@@ -4,10 +4,17 @@
<script> <script>
export default { export default {
props: {
},
data() { data() {
return { return {
result: '' result: ''
} }
},
emits:['propsChanged'],
watch: {
}, },
methods: { methods: {
foo1() { foo1() {
...@@ -28,4 +35,4 @@ ...@@ -28,4 +35,4 @@
} }
} }
} }
</script> </script>
\ No newline at end of file
{
"id": "test-props",
"displayName": "test-props",
"version": "1.0.0",
"description": "test-props",
"keywords": [
"test-props"
],
"repository": "",
"engines": {
"HBuilderX": "^3.7.0"
},
"dcloudext": {
"type": "component-uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "",
"data": "",
"permissions": ""
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "u",
"aliyun": "u",
"alipay": "u"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-android": "u",
"app-ios": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# test-props
### 开发文档
[UTS 语法](https://uniapp.dcloud.net.cn/tutorial/syntax-uts.html)
[UTS API插件](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html)
[UTS 组件插件](https://uniapp.dcloud.net.cn/plugin/uts-component.html)
[Hello UTS](https://gitcode.net/dcloud/hello-uts)
\ No newline at end of file
import { PropsChangeEvent, PropsChangeEventDetail } from './index.uts'
export class PropChangeEventImpl extends UniCustomEvent<PropsChangeEventDetail> implements PropsChangeEvent {
constructor(detail : PropsChangeEventDetail) {
super("propChange", detail);
}
}
import { IPropsChangeEvent,IPropsChangeEventDetail } from '../interface.uts'
export type PropsChangeEvent = IPropsChangeEvent
export type PropsChangeEventDetail = IPropsChangeEventDetail
<template>
<view>
</view>
</template>
<script lang="uts">
/**
* 引用 Android 系统库
* [可选实现,按需引入]
*/
import TextUtils from 'android.text.TextUtils';
import Button from 'android.widget.Button';
import View from 'android.view.View';
import { IObjItem ,IPropsChangeEvent} from '../interface.uts'
import {PropChangeEventImpl} from './event.uts'
/**
* 引入三方库
* [可选实现,按需引入]
*
* 在 Android 平台引入三方库有以下两种方式:
* 1、[推荐] 通过 仓储 方式引入,将 三方库的依赖信息 配置到 config.json 文件下的 dependencies 字段下。详细配置方式[详见](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html#dependencies)
* 2、直接引入,将 三方库的aar或jar文件 放到libs目录下。更多信息[详见](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html#android%E5%B9%B3%E5%8F%B0%E5%8E%9F%E7%94%9F%E9%85%8D%E7%BD%AE)
*
* 在通过上述任意方式依赖三方库后,使用时需要在文件中 import
* import { LottieAnimationView } from 'com.airbnb.lottie.LottieAnimationView'
*/
/**
* UTSAndroid 为平台内置对象,不需要 import 可直接调用其API,[详见](https://uniapp.dcloud.net.cn/uts/utsandroid.html#utsandroid)
*/
//原生提供以下属性或方法的实现
export default {
/**
* 组件名称,也就是开发者使用的标签
*/
name: "test-props",
/**
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
*/
emits: ['buttonclick', 'numListChange', 'objListChange'],
/**
* 属性声明,组件的使用者会传递这些属性值到组件
*/
props: {
"buttontext": {
type: String,
default: "点击触发"
},
numList: {
type: Array as PropType<number[]>,
default: () => [] as number[]
},
objList: {
type: Array as PropType<IObjItem[]>,
default: () => [] as IObjItem[]
}
},
/**
* 组件内部变量声明
*/
data() {
return {}
},
/**
* 属性变化监听器实现
*/
watch: {
"buttontext": {
/**
* 这里监听属性变化,并进行组件内部更新
*/
handler(newValue : string, oldValue : string) {
if (!TextUtils.isEmpty(newValue) && newValue != oldValue) {
this.$el?.setText(newValue);
}
},
immediate: false // 创建时是否通过此方法更新属性,默认值为false
},
numList: {
handler(newVal : number[], oldVal : number[]) {
let detail = new Map<string, number[]>()
detail.set("value", newVal)
let data = new Map<string, any>()
data.set("detail", detail)
// const event = new PropChangeEventImpl(newVal)
this.$emit('numListChange', data)
},
immediate: true
},
objList: {
handler(newVal : any[], oldVal : any[]) {
let detail = new Map<string, any>()
detail.set("value", newVal)
let data = new Map<string, any>()
data.set("detail", detail)
this.$emit('objListChange', data)
},
immediate: true
}
},
/**
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
*/
expose: ['doSomething'],
methods: {
/**
* 对外公开的组件方法
*
* uni-app中调用示例:
* this.$refs["组件ref"].doSomething("uts-button");
*
* uni-app x中调用示例:
* 1、引入对应Element
* import { UtsButtonElement(组件名称以upper camel case方式命名 + Element) } from 'uts.sdk.modules.utsComponent(组件目录名称以lower camel case方式命名)';
* 2、(this.$refs["组件ref"] as UtsButtonElement).doSomething("uts-button");
* 或 (uni.getElementById("组件id") as UtsButtonElement).doSomething("uts-button");
*/
doSomething(param : string) {
console.log(param);
},
/**
* 内部使用的组件方法
*/
privateMethod() {
}
},
/**
* [可选实现] 组件被创建,组件第一个生命周期,
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
*/
created() {
},
/**
* [可选实现] 对应平台的view载体即将被创建,对应前端beforeMount
*/
NVBeforeLoad() {
},
/**
* [必须实现] 创建原生View,必须定义返回值类型
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
* (Android需要明确知道View类型,需特殊校验)
*/
NVLoad() : Button {
let button = new Button($androidContext!);
button.setText("点击触发");
button.setOnClickListener(new ButtonClickListener(this));
return button;
},
/**
* [可选实现] 原生View已创建
*/
NVLoaded() {
},
/**
* [可选实现] 原生View布局完成
*/
NVLayouted() {
},
/**
* [可选实现] 原生View将释放
*/
NVBeforeUnload() {
},
/**
* [可选实现] 原生View已释放,这里可以做释放View之后的操作
*/
NVUnloaded() {
},
/**
* [可选实现] 组件销毁
*/
unmounted() {
},
/**
* [可选实现] 自定组件布局尺寸,用于告诉排版系统,组件自身需要的宽高
* 一般情况下,组件的宽高应该是由终端系统的排版引擎决定,组件开发者不需要实现此函数
* 但是部分场景下,组件开发者需要自己维护宽高,则需要开发者重写此函数
*/
NVMeasure(size : UTSSize) : UTSSize {
// size.width = 300.0.toFloat();
// size.height = 200.0.toFloat();
return size;
}
}
/**
* 定义按钮点击后触发回调的类
* [可选实现]
*/
class ButtonClickListener extends View.OnClickListener {
/**
* 如果需要在回调类或者代理类中对组件进行操作,比如调用组件方法,发送事件等,需要在该类中持有组件对应的原生类的对象
* 组件原生类的基类为 UTSComponent,该类是一个泛型类,需要接收一个类型变量,该类型变量就是原生组件的类型
*/
private comp : UTSComponent<Button>;
constructor(comp : UTSComponent<Button>) {
super();
this.comp = comp;
}
/**
* 按钮点击回调方法
*/
override onClick(v ?: View) {
console.log("按钮被点击");
// 发送事件
this.comp.$emit("buttonclick");
}
}
</script>
<style>
</style>
<template>
<view class="defaultStyles">
</view>
</template>
<script lang="uts">
/**
* 引用 iOS 系统库
* [可选实现,按需引入]
*/
import {
UIButton,
UIControl
} from "UIKit"
/**
* 引入三方库
* [可选实现,按需引入]
*
* 在 iOS 平台引入三方库有以下两种方式:
* 1、通过引入三方库framework 或者.a 等方式,需要将 .framework 放到 ./Frameworks 目录下,将.a 放到 ./Libs 目录下。更多信息[详见](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html#ios-平台原生配置)
* 2、通过 cocoaPods 方式引入,将要引入的 pod 信息配置到 config.json 文件下的 dependencies-pods 字段下。详细配置方式[详见](https://uniapp.dcloud.net.cn/plugin/uts-ios-cocoapods.html)
*
* 在通过上述任意方式依赖三方库后,使用时需要在文件中 import:
* 示例:import { LottieAnimationView, LottieAnimation, LottieLoopMode } from 'Lottie'
*/
/**
* UTSiOS、UTSComponent 为平台内置对象,不需要 import 可直接调用其API,[详见](https://uniapp.dcloud.net.cn/uts/utsios.html)
*/
import { UTSComponent } from "DCloudUTSFoundation"
import { IObjItem } from '../interface.uts'
//原生提供以下属性或方法的实现
export default {
data() {
return {
};
},
/**
* 组件名称,也就是开发者使用的标签
*/
name: "test-props",
/**
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
*/
emits: ['buttonclick', 'numListChange', 'objListChange'],
/**
* 属性声明,组件的使用者会传递这些属性值到组件
*/
props: {
/**
* 字符串类型 属性:buttontext 需要设置默认值
*/
"buttontext": {
type: String,
default: "点击触发"
},
numList: {
type: Array as PropType<number[]>,
default: () => [] as number[]
},
objList: {
type: Array as PropType<IObjItem[]>,
default: () => [] as IObjItem[]
}
},
/**
* 组件内部变量声明
*/
/**
* 属性变化监听器实现
*/
watch: {
"buttontext": {
/**
* 这里监听属性变化,并进行组件内部更新
*/
handler(newValue : String, oldValue : String) {
this.$el.setTitle(newValue, for = UIControl.State.normal)
},
/**
* 创建时是否通过此方法更新属性,默认值为false
*/
immediate: false
},
numList: {
handler(newVal : number[], oldVal : number[]) {
let detail = new Map<string, any>()
detail.set("value", newVal)
let data = new Map<string, any>()
data.set("detail", detail)
this.$emit('numListChange', data)
},
immediate: true
},
objList: {
handler(newVal : any[], oldVal : any[]) {
let detail = new Map<string, any>()
detail.set("value", newVal)
let data = new Map<string, any>()
data.set("detail", detail)
this.$emit('objListChange', data)
},
immediate: true
}
},
/**
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
*/
expose: ['doSomething'],
methods: {
/**
* 对外公开的组件方法
* 在uni-app中调用组件方法,可以通过指定ref的方式,例如指定uts-button 标签的ref 为 ’button‘, 调用时使用:this.$refs["button"].doSomething('message');
*/
doSomething(paramA : string) {
// 这是组件的自定义方法
console.log(paramA, 'this is in uts-button component')
},
/**
* 内部使用的组件方法
*/
},
/**
* 组件被创建,组件第一个生命周期,
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
* [可选实现]
*/
created() {
},
/**
* 对应平台的view载体即将被创建,对应前端beforeMount
* [可选实现]
*/
NVBeforeLoad() {
},
/**
* 创建原生View,必须定义返回值类型
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
* [必须实现]
*/
NVLoad() : UIButton {
//必须实现
buttonClickListsner = new ButtonClickListsner(this)
let button = new UIButton()
button.setTitle(this.buttontext, for = UIControl.State.normal)
// 在 swift target-action 对应的方法需要以OC的方式来调用,那么OC语言中用Selector来表示一个方法的名称(又称方法选择器),创建一个Selector可以使用 Selector("functionName") 的方式。
const method = Selector("buttonClickAction")
if (buttonClickListsner != null) {
button.addTarget(buttonClickListsner!, action = method, for = UIControl.Event.touchUpInside)
}
return button
},
/**
* 原生View已创建
* [可选实现]
*/
NVLoaded() {
/**
* 通过 this.$el 来获取原生控件。
*/
this.$el.setTitle(this.buttontext, for = UIControl.State.normal)
},
/**
* 原生View布局完成
* [可选实现]
*/
NVLayouted() {
},
/**
* 原生View将释放
* [可选实现]
*/
NVBeforeUnload() { },
/**
* 原生View已释放,这里可以做释放View之后的操作
* [可选实现]
*/
NVUnloaded() {
},
/**
* 组件销毁
* [可选实现]
*/
unmounted() { }
/**
* 更多组件开发的信息详见:https://uniapp.dcloud.net.cn/plugin/uts-component.html
*/
}
/**
* 定义按钮点击后触发回调的类
* [可选实现]
*/
class ButtonClickListsner {
/**
* 如果需要在回调类或者代理类中对组件进行操作,比如调用组件方法,发送事件等,需要在该类中持有组件对应的原生类的对象。
* 组件原生类的基类为 UTSComponent,该类是一个泛型类,需要接收一个类型变量,该类型变量就是原生组件的类型。
*/
private component : UTSComponent<UIButton>
constructor(component : UTSComponent<UIButton>) {
this.component = component
super.init()
}
/**
* 按钮点击回调方法
* 在 swift 中,所有target-action (例如按钮的点击事件,NotificationCenter 的通知事件等)对应的 action 函数前面都要使用 @objc 进行标记。
* [可选实现]
*/
@objc buttonClickAction() {
console.log("按钮被点击")
// 发送事件
this.component.__$$emit("buttonclick");
}
}
/**
* 定义回调类或者代理类的实例
* [可选实现]
*/
let buttonClickListsner : ButtonClickListsner | null = null
</script>
<style>
</style>
export type IObjItem = {
id : string
}
export type IPropsChangeEventDetail = {
value : number
}
export interface IPropsChangeEvent extends UniVideoEvent {
detail : IPropsChangeEventDetail[]
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册