| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- import {
- createApp,
- defineComponent,
- getCurrentInstance,
- h,
- nodeOps,
- render,
- shallowReadonly,
- } from '@vue/runtime-test'
- import type {
- ComponentInternalInstance,
- ComponentOptions,
- } from '../src/component'
- describe('component: proxy', () => {
- test('data', () => {
- let instance: ComponentInternalInstance
- let instanceProxy: any
- const Comp = {
- data() {
- return {
- foo: 1,
- }
- },
- mounted() {
- instance = getCurrentInstance()!
- instanceProxy = this
- },
- render() {
- return null
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- expect(instanceProxy.foo).toBe(1)
- instanceProxy.foo = 2
- expect(instance!.data.foo).toBe(2)
- })
- test('setupState', () => {
- let instance: ComponentInternalInstance
- let instanceProxy: any
- const Comp = {
- setup() {
- return {
- foo: 1,
- }
- },
- mounted() {
- instance = getCurrentInstance()!
- instanceProxy = this
- },
- render() {
- return null
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- expect(instanceProxy.foo).toBe(1)
- instanceProxy.foo = 2
- expect(instance!.setupState.foo).toBe(2)
- })
- test('should not expose non-declared props', () => {
- let instanceProxy: any
- const Comp = {
- setup() {
- return () => null
- },
- mounted() {
- instanceProxy = this
- },
- }
- render(h(Comp, { count: 1 }), nodeOps.createElement('div'))
- expect('count' in instanceProxy).toBe(false)
- })
- test('public properties', async () => {
- let instance: ComponentInternalInstance
- let instanceProxy: any
- const Comp = {
- setup() {
- return () => null
- },
- mounted() {
- instance = getCurrentInstance()!
- instanceProxy = this
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- expect(instanceProxy.$data).toBe(instance!.data)
- expect(instanceProxy.$props).toBe(shallowReadonly(instance!.props))
- expect(instanceProxy.$attrs).toBe(shallowReadonly(instance!.attrs))
- expect(instanceProxy.$slots).toBe(shallowReadonly(instance!.slots))
- expect(instanceProxy.$refs).toBe(shallowReadonly(instance!.refs))
- expect(instanceProxy.$parent).toBe(
- instance!.parent && instance!.parent.proxy,
- )
- expect(instanceProxy.$root).toBe(instance!.root.proxy)
- expect(instanceProxy.$emit).toBe(instance!.emit)
- expect(instanceProxy.$el).toBe(instance!.vnode.el)
- expect(instanceProxy.$options).toBe(instance!.type as ComponentOptions)
- expect(() => (instanceProxy.$data = {})).toThrow(TypeError)
- expect(`Attempting to mutate public property "$data"`).toHaveBeenWarned()
- const nextTickThis = await instanceProxy.$nextTick(function (this: any) {
- return this
- })
- expect(nextTickThis).toBe(instanceProxy)
- })
- test('user attached properties', async () => {
- let instance: ComponentInternalInstance
- let instanceProxy: any
- const Comp = {
- setup() {
- return () => null
- },
- mounted() {
- instance = getCurrentInstance()!
- instanceProxy = this
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- instanceProxy.foo = 1
- expect(instanceProxy.foo).toBe(1)
- expect(instance!.ctx.foo).toBe(1)
- // should also allow properties that start with $
- const obj = (instanceProxy.$store = {})
- expect(instanceProxy.$store).toBe(obj)
- expect(instance!.ctx.$store).toBe(obj)
- })
- test('globalProperties', () => {
- let instance: ComponentInternalInstance
- let instanceProxy: any
- const Comp = {
- setup() {
- return () => null
- },
- mounted() {
- instance = getCurrentInstance()!
- instanceProxy = this
- },
- }
- const app = createApp(Comp)
- app.config.globalProperties.foo = 1
- app.mount(nodeOps.createElement('div'))
- expect(instanceProxy.foo).toBe(1)
- // set should overwrite globalProperties with local
- instanceProxy.foo = 2
- // expect(instanceProxy.foo).toBe(2)
- expect(instance!.ctx.foo).toBe(2)
- // should not affect global
- expect(app.config.globalProperties.foo).toBe(1)
- })
- test('has check', () => {
- let instanceProxy: any
- const Comp = {
- render() {},
- props: {
- msg: String,
- },
- data() {
- return {
- foo: 0,
- $foo: 0,
- }
- },
- computed: {
- cmp: () => {
- throw new Error('value of cmp should not be accessed')
- },
- $cmp: () => {
- throw new Error('value of $cmp should not be read')
- },
- },
- setup() {
- return {
- bar: 1,
- }
- },
- __cssModules: {
- $style: {},
- cssStyles: {},
- },
- mounted() {
- instanceProxy = this
- },
- }
- const app = createApp(Comp, { msg: 'hello' })
- app.config.globalProperties.global = 1
- app.config.globalProperties.$global = 1
- app.mount(nodeOps.createElement('div'))
- // props
- expect('msg' in instanceProxy).toBe(true)
- // data
- expect('foo' in instanceProxy).toBe(true)
- expect('$foo' in instanceProxy).toBe(false)
- // setupState
- expect('bar' in instanceProxy).toBe(true)
- // ctx
- expect('cmp' in instanceProxy).toBe(true)
- expect('$cmp' in instanceProxy).toBe(true)
- // public properties
- expect('$el' in instanceProxy).toBe(true)
- // CSS modules
- expect('$style' in instanceProxy).toBe(true)
- expect('cssStyles' in instanceProxy).toBe(true)
- // global properties
- expect('global' in instanceProxy).toBe(true)
- expect('$global' in instanceProxy).toBe(true)
- // non-existent
- expect('$foobar' in instanceProxy).toBe(false)
- expect('baz' in instanceProxy).toBe(false)
- // #4962 triggering getter should not cause non-existent property to
- // pass the has check
- instanceProxy.baz
- instanceProxy.$baz
- expect('baz' in instanceProxy).toBe(false)
- expect('$baz' in instanceProxy).toBe(false)
- // set non-existent (goes into proxyTarget sink)
- instanceProxy.baz = 1
- expect('baz' in instanceProxy).toBe(true)
- instanceProxy.$baz = 1
- expect('$baz' in instanceProxy).toBe(true)
- // dev mode ownKeys check for console inspection
- // should only expose own keys
- expect(Object.keys(instanceProxy)).toMatchObject([
- 'msg',
- 'bar',
- 'foo',
- 'cmp',
- '$cmp',
- 'baz',
- '$baz',
- ])
- })
- test('allow updating proxy with Object.defineProperty', () => {
- let instanceProxy: any
- const Comp = {
- render() {},
- setup() {
- return {
- isDisplayed: true,
- }
- },
- mounted() {
- instanceProxy = this
- },
- }
- const app = createApp(Comp)
- app.mount(nodeOps.createElement('div'))
- Object.defineProperty(instanceProxy, 'isDisplayed', { value: false })
- expect(instanceProxy.isDisplayed).toBe(false)
- Object.defineProperty(instanceProxy, 'isDisplayed', { value: true })
- expect(instanceProxy.isDisplayed).toBe(true)
- Object.defineProperty(instanceProxy, 'isDisplayed', {
- get() {
- return false
- },
- })
- expect(instanceProxy.isDisplayed).toBe(false)
- Object.defineProperty(instanceProxy, 'isDisplayed', {
- get() {
- return true
- },
- })
- expect(instanceProxy.isDisplayed).toBe(true)
- })
- test('allow test runner spying on proxy methods with Object.defineProperty', () => {
- // #5417
- let instanceProxy: any
- const Comp = {
- render() {},
- setup() {
- return {
- toggle() {
- return 'a'
- },
- }
- },
- mounted() {
- instanceProxy = this
- },
- }
- const app = createApp(Comp)
- app.mount(nodeOps.createElement('div'))
- // access 'toggle' to ensure key is cached
- const v1 = instanceProxy.toggle()
- expect(v1).toEqual('a')
- // reconfigure "toggle" to be getter based.
- let getCalledTimes = 0
- Object.defineProperty(instanceProxy, 'toggle', {
- get() {
- getCalledTimes++
- return () => 'b'
- },
- })
- // getter should not be evaluated on initial definition
- expect(getCalledTimes).toEqual(0)
- // invoke "toggle" after "defineProperty"
- const v2 = instanceProxy.toggle()
- expect(v2).toEqual('b')
- expect(getCalledTimes).toEqual(1)
- // expect toggle getter not to be cached. it can't be
- instanceProxy.toggle()
- expect(getCalledTimes).toEqual(2)
- // attaching spy, triggers the getter once, and override the property.
- // also uses Object.defineProperty
- const spy = vi.spyOn(instanceProxy, 'toggle')
- expect(getCalledTimes).toEqual(3)
- const v3 = instanceProxy.toggle()
- expect(v3).toEqual('b')
- expect(spy).toHaveBeenCalled()
- expect(getCalledTimes).toEqual(3)
- })
- test('defineProperty on proxy property with value descriptor', () => {
- // #5417
- let instanceProxy: any
- const Comp = {
- render() {},
- setup() {
- return {
- toggle: 'a',
- }
- },
- mounted() {
- instanceProxy = this
- },
- }
- const app = createApp(Comp)
- app.mount(nodeOps.createElement('div'))
- const v1 = instanceProxy.toggle
- expect(v1).toEqual('a')
- Object.defineProperty(instanceProxy, 'toggle', {
- value: 'b',
- })
- const v2 = instanceProxy.toggle
- expect(v2).toEqual('b')
- // expect null to be a settable value
- Object.defineProperty(instanceProxy, 'toggle', {
- value: null,
- })
- const v3 = instanceProxy.toggle
- expect(v3).toBeNull()
- })
- test('defineProperty on public instance proxy should work with SETUP,DATA,CONTEXT,PROPS', () => {
- // #5417
- let instanceProxy: any
- const Comp = {
- props: ['fromProp'],
- data() {
- return { name: 'data.name' }
- },
- computed: {
- greet() {
- return 'Hi ' + (this as any).name
- },
- },
- render() {},
- setup() {
- return {
- fromSetup: true,
- }
- },
- mounted() {
- instanceProxy = this
- },
- }
- const app = createApp(Comp, {
- fromProp: true,
- })
- app.mount(nodeOps.createElement('div'))
- expect(instanceProxy.greet).toEqual('Hi data.name')
- // define property on data
- Object.defineProperty(instanceProxy, 'name', {
- get() {
- return 'getter.name'
- },
- })
- // computed is same still cached
- expect(instanceProxy.greet).toEqual('Hi data.name')
- // trigger computed
- instanceProxy.name = ''
- // expect "greet" to evaluated and use name from context getter
- expect(instanceProxy.greet).toEqual('Hi getter.name')
- // defineProperty on computed ( context )
- Object.defineProperty(instanceProxy, 'greet', {
- get() {
- return 'Hi greet.getter.computed'
- },
- })
- expect(instanceProxy.greet).toEqual('Hi greet.getter.computed')
- // defineProperty on setupState
- expect(instanceProxy.fromSetup).toBe(true)
- Object.defineProperty(instanceProxy, 'fromSetup', {
- get() {
- return false
- },
- })
- expect(instanceProxy.fromSetup).toBe(false)
- // defineProperty on Props
- expect(instanceProxy.fromProp).toBe(true)
- Object.defineProperty(instanceProxy, 'fromProp', {
- get() {
- return false
- },
- })
- expect(instanceProxy.fromProp).toBe(false)
- })
- // #864
- test('should not warn declared but absent props', () => {
- const Comp = {
- props: ['test'],
- render(this: any) {
- return this.test
- },
- }
- render(h(Comp), nodeOps.createElement('div'))
- expect(
- `was accessed during render but is not defined`,
- ).not.toHaveBeenWarned()
- })
- test('should allow symbol to access on render', () => {
- const Comp = {
- render() {
- if ((this as any)[Symbol.unscopables]) {
- return '1'
- }
- return '2'
- },
- }
- const app = createApp(Comp)
- app.mount(nodeOps.createElement('div'))
- expect(
- `Property ${JSON.stringify(
- Symbol.unscopables,
- )} was accessed during render ` + `but is not defined on instance.`,
- ).toHaveBeenWarned()
- })
- test('should prevent mutating script setup bindings', () => {
- const Comp = defineComponent({
- render() {},
- setup() {
- return {
- __isScriptSetup: true,
- foo: 1,
- }
- },
- mounted() {
- expect('foo' in this).toBe(false)
- try {
- this.foo = 123
- } catch (e) {}
- },
- })
- render(h(Comp), nodeOps.createElement('div'))
- expect(`Cannot mutate <script setup> binding "foo"`).toHaveBeenWarned()
- })
- })
|