types: bump TS version + type test for createComponent

This commit is contained in:
Evan You 2019-05-30 22:09:48 +08:00
parent caad39c353
commit e665a133e9
11 changed files with 46 additions and 770 deletions

View File

@ -41,7 +41,7 @@
"rollup-plugin-terser": "^2.0.2",
"rollup-plugin-typescript2": "^0.17.0",
"ts-jest": "^23.10.0",
"typescript": "^3.1.3",
"typescript": "^3.5.0",
"yorkie": "^2.0.0"
}
}

View File

@ -120,8 +120,8 @@ export function trigger(
// never been tracked
return
}
const effects = new Set()
const computedRunners = new Set()
const effects: Set<ReactiveEffect> = new Set()
const computedRunners: Set<ReactiveEffect> = new Set()
if (type === OperationTypes.CLEAR) {
// collection being cleared, trigger all effects for target
depsMap.forEach(dep => {

View File

@ -0,0 +1,36 @@
import { createComponent } from '../src/component'
import { value } from '@vue/observer'
test('createComponent type inference', () => {
const MyComponent = createComponent({
props: {
a: Number,
b: {
type: String
}
},
setup(props) {
props.a * 2
props.b.slice()
return {
c: value(1),
d: {
e: value('hi')
}
}
},
render({ state, props }) {
state.c * 2
state.d.e.slice()
props.a * 2
props.b.slice()
this.a * 2
this.b.slice()
this.c * 2
this.d.e.slice()
}
})
MyComponent // avoid unused
// rename this file to .tsx to test TSX props inference
// ;(<MyComponent a={1} b="foo"/>)
})

View File

@ -1,291 +0,0 @@
import {
Component,
observable,
h,
nextTick,
KeepAlive,
ComponentPropsOptions,
ComponentWatchOptions
} from '@vue/runtime-core'
import { createInstance, renderInstance } from '@vue/runtime-test'
describe('class inheritance', () => {
it('should merge data', () => {
class Base extends Component {
foo = 1
data() {
return {
bar: 2
}
}
}
class Child extends Base {
foo: number
bar: number
baz: number
qux: number = 4
data(): any {
return {
baz: 3
}
}
}
const child = createInstance(Child)
expect(child.foo).toBe(1)
expect(child.bar).toBe(2)
expect(child.baz).toBe(3)
expect(child.qux).toBe(4)
})
it('should merge props', () => {
class Base extends Component {
static props: ComponentPropsOptions = {
foo: Number
}
}
class Child extends Base {
foo: number
bar: number
$props: { foo: number; bar: number }
static props: ComponentPropsOptions = {
bar: Number
}
}
const child = createInstance(Child, {
foo: 1,
bar: 2
})
expect(child.foo).toBe(1)
expect(child.bar).toBe(2)
expect(child.$props.foo).toBe(1)
expect(child.$props.bar).toBe(2)
})
it('should merge lifecycle hooks', async () => {
const calls: string[] = []
const state = observable({ ok: true })
class Base extends Component {
beforeCreate() {
calls.push('base beforeCreate')
}
created() {
calls.push('base created')
}
beforeMount() {
calls.push('base beforeMount')
}
mounted() {
calls.push('base mounted')
}
beforeUpdate() {
calls.push('base beforeUpdate')
}
updated() {
calls.push('base updated')
}
beforeUnmount() {
calls.push('base beforeUnmount')
}
unmounted() {
calls.push('base unmounted')
}
}
class Child extends Base {
beforeCreate() {
calls.push('child beforeCreate')
}
created() {
calls.push('child created')
}
beforeMount() {
calls.push('child beforeMount')
}
mounted() {
calls.push('child mounted')
}
beforeUpdate() {
calls.push('child beforeUpdate')
}
updated() {
calls.push('child updated')
}
beforeUnmount() {
calls.push('child beforeUnmount')
}
unmounted() {
calls.push('child unmounted')
}
render() {
return state.ok ? 'foo' : 'bar'
}
}
class Container extends Component {
show = true
render() {
return this.show ? h(Child) : null
}
}
const container = await renderInstance(Container)
expect(calls).toEqual([
'base beforeCreate',
'child beforeCreate',
'base created',
'child created',
'base beforeMount',
'child beforeMount',
'base mounted',
'child mounted'
])
calls.length = 0
state.ok = false
await nextTick()
expect(calls).toEqual([
'base beforeUpdate',
'child beforeUpdate',
'base updated',
'child updated'
])
calls.length = 0
container.show = false
await nextTick()
expect(calls).toEqual([
'base beforeUnmount',
'child beforeUnmount',
'base unmounted',
'child unmounted'
])
})
it('should merge lifecycle hooks (activated/deactivated)', async () => {
const calls: string[] = []
class Base extends Component {
activated() {
calls.push('base activated')
}
deactivated() {
calls.push('base deactivated')
}
}
class Child extends Base {
activated() {
calls.push('child activated')
}
deactivated() {
calls.push('child deactivated')
}
render() {
return 'foo'
}
}
class Container extends Component {
ok = true
render() {
return h(KeepAlive, this.ok ? h(Child) : null)
}
}
const container = await renderInstance(Container)
expect(container.$el.text).toBe('foo')
container.ok = false
await nextTick()
expect(container.$el.text).toBe('')
expect(calls).toEqual(['base deactivated', 'child deactivated'])
calls.length = 0
container.ok = true
await nextTick()
expect(container.$el.text).toBe('foo')
expect(calls).toEqual(['base activated', 'child activated'])
})
it('should merge watchers', async () => {
const fooCallback = jest.fn()
const barCallback = jest.fn()
class Base extends Component {
static watch: ComponentWatchOptions = {
foo: fooCallback
}
}
class Child extends Base {
foo = 1
bar = 2
static watch: ComponentWatchOptions = {
bar: barCallback
}
}
const child = createInstance(Child)
child.foo = 2
await nextTick()
expect(fooCallback).toHaveBeenCalledWith(2, 1)
child.bar = 3
await nextTick()
expect(barCallback).toHaveBeenCalledWith(3, 2)
})
it('should inherit methods', () => {
const fooCallback = jest.fn()
const barCallback = jest.fn()
class Base extends Component {
foo() {
fooCallback()
}
}
class Child extends Base {
bar() {
barCallback()
}
}
const child = createInstance(Child)
child.foo()
child.bar()
expect(fooCallback).toHaveBeenCalled()
expect(barCallback).toHaveBeenCalled()
})
it('should inherit computed properties', () => {
class Base extends Component {
a = 1
get foo() {
return this.a + 1
}
}
class Child extends Base {
b = 1
get bar() {
return this.b + this.foo + 1
}
}
const child = createInstance(Child)
expect(child.foo).toBe(2)
expect(child.bar).toBe(4)
child.a = 2
expect(child.foo).toBe(3)
expect(child.bar).toBe(5)
})
})

View File

@ -1,54 +0,0 @@
import { h, Component, memoize, nextTick } from '../src'
import { renderInstance, serialize } from '@vue/runtime-test'
describe('memoize', () => {
it('should work', async () => {
class App extends Component {
count = 1
render() {
return h('div', [
this.count,
this.count % 2
? memoize(() => h('div', `A` + this.count), this, 0)
: null,
memoize(() => h('div', `B` + this.count), this, 1)
])
}
}
const app = await renderInstance(App)
expect(serialize(app.$el)).toBe(`<div>1<div>A1</div><div>B1</div></div>`)
app.count++
await nextTick()
expect(serialize(app.$el)).toBe(`<div>2<div>B1</div></div>`)
app.count++
await nextTick()
// test remounting a memoized tree
expect(serialize(app.$el)).toBe(`<div>3<div>A1</div><div>B1</div></div>`)
})
it('should invalidate based on keys', async () => {
class App extends Component {
foo = 1
bar = 1
render() {
return memoize(() => h('div', this.foo + this.bar), this, 0, [this.bar])
}
}
const app = await renderInstance(App)
expect(serialize(app.$el)).toBe(`<div>2</div>`)
app.foo++
await nextTick()
// should not update
expect(serialize(app.$el)).toBe(`<div>2</div>`)
app.bar++
await nextTick()
// should update now
expect(serialize(app.$el)).toBe(`<div>4</div>`)
})
})

View File

@ -1,294 +0,0 @@
import { Component, ComponentClass, mixins } from '@vue/runtime-core'
import { createInstance } from '@vue/runtime-test'
import { prop } from '@vue/decorators'
const calls: string[] = []
beforeEach(() => {
calls.length = 0
})
class ClassMixinA extends Component<{ p1: string }, { d11: number }> {
// props
@prop
p1: string
// data
d1 = 1
data() {
return {
d11: 2
}
}
// computed
get c1() {
return this.d1 + this.$data.d11
}
// lifecycle
created() {
calls.push('created from mixin A')
}
// methods
foo() {
return this.d1
}
}
class ClassMixinB extends Component<{ p2: string }, { d21: number }> {
// props
static props = {
p2: String
}
// data
d2 = 1
data() {
return {
d21: 2
}
}
get c2() {
return this.d2 + this.$data.d21
}
// lifecycle
created() {
calls.push('created from mixin B')
}
// methods
bar() {
return this.d2
}
}
const ObjectMixinA = {
props: {
p1: String
},
data() {
return {
d1: 1,
d11: 2
}
},
computed: {
c1() {
return this.d1 + this.d11
}
},
created() {
calls.push('created from mixin A')
},
methods: {
foo() {
return this.d1
}
}
}
const ObjectMixinB = {
props: {
p2: String
},
data() {
return {
d2: 1,
d21: 2
}
},
computed: {
c2() {
return this.d2 + this.d21
}
},
created() {
calls.push('created from mixin B')
},
methods: {
bar() {
return this.d2
}
}
}
function assertMixins(Test: any) {
const instance = createInstance(Test, {
p1: '1',
p2: '2',
p3: '3'
}) as any
// data
expect(instance.d1).toBe(1)
expect(instance.d11).toBe(2)
expect(instance.d2).toBe(1)
expect(instance.d21).toBe(2)
expect(instance.d3).toBe(1)
expect(instance.d31).toBe(2)
// props
expect(instance.p1).toBe('1')
expect(instance.p2).toBe('2')
expect(instance.p3).toBe('3')
expect(instance.$props.p1).toBe('1')
expect(instance.$props.p2).toBe('2')
expect(instance.$props.p3).toBe('3')
// computed
expect(instance.c1).toBe(3)
expect(instance.c2).toBe(3)
expect(instance.c3).toBe(3)
// lifecycle
expect(calls).toEqual([
'created from mixin A',
'created from mixin B',
'created from Test'
])
// methods
expect(instance.foo()).toBe(1)
expect(instance.bar()).toBe(1)
expect(instance.baz()).toBe(1)
}
describe('mixins', () => {
it('should work with classes', () => {
class Test extends mixins(ClassMixinA, ClassMixinB)<
{ p3: string },
{ d31: number }
> {
static props = {
p3: String
}
d3 = 1
data(): any {
return {
d31: 2
}
}
get c3() {
return this.d3 + this.d31
}
created() {
calls.push('created from Test')
}
baz() {
return this.d3
}
}
const instance = createInstance(Test, {
p1: '1',
p2: '2',
p3: '3'
})
// we duplicate the assertions because they serve as type tests as well
// data
expect(instance.d1).toBe(1)
expect(instance.$data.d11).toBe(2)
expect(instance.d2).toBe(1)
expect(instance.$data.d21).toBe(2)
expect(instance.d3).toBe(1)
expect(instance.d31).toBe(2)
// props
expect(instance.p1).toBe('1')
expect(instance.$props.p2).toBe('2')
expect(instance.p3).toBe('3')
expect(instance.$props.p1).toBe('1')
expect(instance.$props.p2).toBe('2')
expect(instance.$props.p3).toBe('3')
// computed
expect(instance.c1).toBe(3)
expect(instance.c2).toBe(3)
expect(instance.c3).toBe(3)
// lifecycle
expect(calls).toEqual([
'created from mixin A',
'created from mixin B',
'created from Test'
])
// methods
expect(instance.foo()).toBe(1)
expect(instance.bar()).toBe(1)
expect(instance.baz()).toBe(1)
})
it('should work with objects', () => {
class Test extends ((mixins as any)(
ObjectMixinA,
ObjectMixinB
) as ComponentClass)<{ p3: string }, { d31: number }> {
static props = {
p3: String
}
d3 = 1
data(): any {
return {
d31: 2
}
}
get c3() {
return this.d3 + this.$data.d31
}
created() {
calls.push('created from Test')
}
baz() {
return this.d3
}
}
assertMixins(Test)
})
it('should work with a mix of objects and classes', () => {
class Test extends ((mixins as any)(
ClassMixinA,
ObjectMixinB
) as ComponentClass)<{ p3: string }, { d31: number }> {
static props = {
p3: String
}
d3 = 1
data(): any {
return {
d31: 2
}
}
get c3() {
return this.d3 + this.$data.d31
}
created() {
calls.push('created from Test')
}
baz() {
return this.d3
}
}
assertMixins(Test)
})
})

View File

@ -1,120 +0,0 @@
import {
h,
Component,
observable,
nextTick,
renderInstance
} from '@vue/runtime-test'
describe('Parent chain management', () => {
it('should have correct $parent / $root / $children', async () => {
let child: any
let grandChildren: any[] = []
const state = observable({ ok: true })
class Parent extends Component {
render() {
return h(Child)
}
}
class Child extends Component {
created() {
child = this
}
render() {
return [state.ok ? h(GrandChild) : null, h(GrandChild)]
}
}
class GrandChild extends Component {
created() {
grandChildren.push(this)
}
unmounted() {
grandChildren.splice(grandChildren.indexOf(this), 1)
}
render() {
return h('div')
}
}
const parent = await renderInstance(Parent)
expect(child.$parent).toBe(parent)
expect(child.$root).toBe(parent)
grandChildren.forEach(grandChild => {
expect(grandChild.$parent).toBe(child)
expect(grandChild.$root).toBe(parent)
})
expect(parent.$children).toEqual([child])
expect(grandChildren.length).toBe(2)
expect(child.$children).toEqual(grandChildren)
state.ok = false
await nextTick()
expect(grandChildren.length).toBe(1)
expect(child.$children).toEqual(grandChildren)
})
it('should have correct $parent / $root w/ functional component in between', async () => {
let child: any
let grandChildren: any[] = []
const state = observable({ ok: true })
class Parent extends Component {
render() {
return h(FunctionalChild)
}
}
const FunctionalChild = () => h(Child)
class Child extends Component {
created() {
child = this
}
render() {
return [
state.ok ? h(FunctionalGrandChild) : null,
h(FunctionalGrandChild)
]
}
}
const FunctionalGrandChild = () => h(GrandChild)
class GrandChild extends Component {
created() {
grandChildren.push(this)
}
unmounted() {
grandChildren.splice(grandChildren.indexOf(this), 1)
}
render() {}
}
const parent = await renderInstance(Parent)
expect(child.$parent).toBe(parent)
expect(child.$root).toBe(parent)
grandChildren.forEach(grandChild => {
expect(grandChild.$parent).toBe(child)
expect(grandChild.$root).toBe(parent)
})
expect(parent.$children).toEqual([child])
expect(grandChildren.length).toBe(2)
expect(child.$children).toEqual(grandChildren)
state.ok = false
await nextTick()
expect(grandChildren.length).toBe(1)
expect(child.$children).toEqual(grandChildren)
})
})

View File

@ -1,7 +1,7 @@
declare namespace JSX {
interface Element {}
interface ElementClass {
render(props: any, slots: any, attrs: any): any
$props: {}
}
interface ElementAttributesProperty {
$props: {}

View File

@ -20,7 +20,6 @@
},
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/runtime-core#readme",
"dependencies": {
"@vue/observer": "3.0.0-alpha.1",
"@vue/scheduler": "3.0.0-alpha.1"
"@vue/observer": "3.0.0-alpha.1"
}
}

View File

@ -751,7 +751,7 @@ type NativeElements = {
declare namespace JSX {
interface Element {}
interface ElementClass {
render(props: any, slots: any, attrs: any): any
$props: {}
}
interface ElementAttributesProperty {
$props: {}

View File

@ -6072,10 +6072,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.3.tgz#01b70247a6d3c2467f70c45795ef5ea18ce191d5"
integrity sha512-+81MUSyX+BaSo+u2RbozuQk/UWx6hfG0a5gHu4ANEM4sU96XbuIyAB+rWBW1u70c6a5QuZfuYICn3s2UjuHUpA==
typescript@^3.5.0:
version "3.5.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==
uglify-js@^3.1.4:
version "3.4.9"