componentPublicInstance.spec.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import {
  2. h,
  3. render,
  4. getCurrentInstance,
  5. nodeOps,
  6. createApp,
  7. shallowReadonly
  8. } from '@vue/runtime-test'
  9. import { ComponentInternalInstance, ComponentOptions } from '../src/component'
  10. describe('component: proxy', () => {
  11. test('data', () => {
  12. let instance: ComponentInternalInstance
  13. let instanceProxy: any
  14. const Comp = {
  15. data() {
  16. return {
  17. foo: 1
  18. }
  19. },
  20. mounted() {
  21. instance = getCurrentInstance()!
  22. instanceProxy = this
  23. },
  24. render() {
  25. return null
  26. }
  27. }
  28. render(h(Comp), nodeOps.createElement('div'))
  29. expect(instanceProxy.foo).toBe(1)
  30. instanceProxy.foo = 2
  31. expect(instance!.data.foo).toBe(2)
  32. })
  33. test('setupState', () => {
  34. let instance: ComponentInternalInstance
  35. let instanceProxy: any
  36. const Comp = {
  37. setup() {
  38. return {
  39. foo: 1
  40. }
  41. },
  42. mounted() {
  43. instance = getCurrentInstance()!
  44. instanceProxy = this
  45. },
  46. render() {
  47. return null
  48. }
  49. }
  50. render(h(Comp), nodeOps.createElement('div'))
  51. expect(instanceProxy.foo).toBe(1)
  52. instanceProxy.foo = 2
  53. expect(instance!.setupState.foo).toBe(2)
  54. })
  55. test('should not expose non-declared props', () => {
  56. let instanceProxy: any
  57. const Comp = {
  58. setup() {
  59. return () => null
  60. },
  61. mounted() {
  62. instanceProxy = this
  63. }
  64. }
  65. render(h(Comp, { count: 1 }), nodeOps.createElement('div'))
  66. expect('count' in instanceProxy).toBe(false)
  67. })
  68. test('public properties', async () => {
  69. let instance: ComponentInternalInstance
  70. let instanceProxy: any
  71. const Comp = {
  72. setup() {
  73. return () => null
  74. },
  75. mounted() {
  76. instance = getCurrentInstance()!
  77. instanceProxy = this
  78. }
  79. }
  80. render(h(Comp), nodeOps.createElement('div'))
  81. expect(instanceProxy.$data).toBe(instance!.data)
  82. expect(instanceProxy.$props).toBe(shallowReadonly(instance!.props))
  83. expect(instanceProxy.$attrs).toBe(shallowReadonly(instance!.attrs))
  84. expect(instanceProxy.$slots).toBe(shallowReadonly(instance!.slots))
  85. expect(instanceProxy.$refs).toBe(shallowReadonly(instance!.refs))
  86. expect(instanceProxy.$parent).toBe(
  87. instance!.parent && instance!.parent.proxy
  88. )
  89. expect(instanceProxy.$root).toBe(instance!.root.proxy)
  90. expect(instanceProxy.$emit).toBe(instance!.emit)
  91. expect(instanceProxy.$el).toBe(instance!.vnode.el)
  92. expect(instanceProxy.$options).toBe(
  93. (instance!.type as ComponentOptions).__merged
  94. )
  95. expect(() => (instanceProxy.$data = {})).toThrow(TypeError)
  96. expect(`Attempting to mutate public property "$data"`).toHaveBeenWarned()
  97. const nextTickThis = await instanceProxy.$nextTick(function(this: any) {
  98. return this
  99. })
  100. expect(nextTickThis).toBe(instanceProxy)
  101. })
  102. test('user attached properties', async () => {
  103. let instance: ComponentInternalInstance
  104. let instanceProxy: any
  105. const Comp = {
  106. setup() {
  107. return () => null
  108. },
  109. mounted() {
  110. instance = getCurrentInstance()!
  111. instanceProxy = this
  112. }
  113. }
  114. render(h(Comp), nodeOps.createElement('div'))
  115. instanceProxy.foo = 1
  116. expect(instanceProxy.foo).toBe(1)
  117. expect(instance!.ctx.foo).toBe(1)
  118. // should also allow properties that start with $
  119. const obj = (instanceProxy.$store = {})
  120. expect(instanceProxy.$store).toBe(obj)
  121. expect(instance!.ctx.$store).toBe(obj)
  122. })
  123. test('globalProperties', () => {
  124. let instance: ComponentInternalInstance
  125. let instanceProxy: any
  126. const Comp = {
  127. setup() {
  128. return () => null
  129. },
  130. mounted() {
  131. instance = getCurrentInstance()!
  132. instanceProxy = this
  133. }
  134. }
  135. const app = createApp(Comp)
  136. app.config.globalProperties.foo = 1
  137. app.mount(nodeOps.createElement('div'))
  138. expect(instanceProxy.foo).toBe(1)
  139. // set should overwrite globalProperties with local
  140. instanceProxy.foo = 2
  141. // expect(instanceProxy.foo).toBe(2)
  142. expect(instance!.ctx.foo).toBe(2)
  143. // should not affect global
  144. expect(app.config.globalProperties.foo).toBe(1)
  145. })
  146. test('has check', () => {
  147. let instanceProxy: any
  148. const Comp = {
  149. render() {},
  150. props: {
  151. msg: String
  152. },
  153. data() {
  154. return {
  155. foo: 0
  156. }
  157. },
  158. setup() {
  159. return {
  160. bar: 1
  161. }
  162. },
  163. mounted() {
  164. instanceProxy = this
  165. }
  166. }
  167. const app = createApp(Comp, { msg: 'hello' })
  168. app.config.globalProperties.global = 1
  169. app.mount(nodeOps.createElement('div'))
  170. // props
  171. expect('msg' in instanceProxy).toBe(true)
  172. // data
  173. expect('foo' in instanceProxy).toBe(true)
  174. // ctx
  175. expect('bar' in instanceProxy).toBe(true)
  176. // public properties
  177. expect('$el' in instanceProxy).toBe(true)
  178. // global properties
  179. expect('global' in instanceProxy).toBe(true)
  180. // non-existent
  181. expect('$foobar' in instanceProxy).toBe(false)
  182. expect('baz' in instanceProxy).toBe(false)
  183. // set non-existent (goes into proxyTarget sink)
  184. instanceProxy.baz = 1
  185. expect('baz' in instanceProxy).toBe(true)
  186. // dev mode ownKeys check for console inspection
  187. // should only expose own keys
  188. expect(Object.keys(instanceProxy)).toMatchObject([
  189. 'msg',
  190. 'bar',
  191. 'foo',
  192. 'baz'
  193. ])
  194. })
  195. // #864
  196. test('should not warn declared but absent props', () => {
  197. const Comp = {
  198. props: ['test'],
  199. render(this: any) {
  200. return this.test
  201. }
  202. }
  203. render(h(Comp), nodeOps.createElement('div'))
  204. expect(
  205. `was accessed during render but is not defined`
  206. ).not.toHaveBeenWarned()
  207. })
  208. test('should allow symbol to access on render', () => {
  209. const Comp = {
  210. render() {
  211. if ((this as any)[Symbol.unscopables]) {
  212. return '1'
  213. }
  214. return '2'
  215. }
  216. }
  217. const app = createApp(Comp)
  218. app.mount(nodeOps.createElement('div'))
  219. expect(
  220. `Property ${JSON.stringify(
  221. Symbol.unscopables
  222. )} was accessed during render ` + `but is not defined on instance.`
  223. ).toHaveBeenWarned()
  224. })
  225. })