componentProps.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. /**
  2. * @vitest-environment jsdom
  3. */
  4. import {
  5. type ComponentInternalInstance,
  6. type FunctionalComponent,
  7. type SetupContext,
  8. createApp,
  9. defineComponent,
  10. getCurrentInstance,
  11. h,
  12. inject,
  13. nextTick,
  14. nodeOps,
  15. provide,
  16. ref,
  17. render,
  18. serializeInner,
  19. toRaw,
  20. toRefs,
  21. watch,
  22. } from '@vue/runtime-test'
  23. import { render as domRender } from 'vue'
  24. describe('component props', () => {
  25. test('stateful', () => {
  26. let props: any
  27. let attrs: any
  28. let proxy: any
  29. const Comp = defineComponent({
  30. props: ['fooBar', 'barBaz'],
  31. render() {
  32. props = this.$props
  33. attrs = this.$attrs
  34. proxy = this
  35. },
  36. })
  37. const root = nodeOps.createElement('div')
  38. render(h(Comp, { fooBar: 1, bar: 2 }), root)
  39. expect(proxy.fooBar).toBe(1)
  40. expect(props).toEqual({ fooBar: 1 })
  41. expect(attrs).toEqual({ bar: 2 })
  42. // test passing kebab-case and resolving to camelCase
  43. render(h(Comp, { 'foo-bar': 2, bar: 3, baz: 4 }), root)
  44. expect(proxy.fooBar).toBe(2)
  45. expect(props).toEqual({ fooBar: 2 })
  46. expect(attrs).toEqual({ bar: 3, baz: 4 })
  47. // test updating kebab-case should not delete it (#955)
  48. render(h(Comp, { 'foo-bar': 3, bar: 3, baz: 4, barBaz: 5 }), root)
  49. expect(proxy.fooBar).toBe(3)
  50. expect(proxy.barBaz).toBe(5)
  51. expect(props).toEqual({ fooBar: 3, barBaz: 5 })
  52. expect(attrs).toEqual({ bar: 3, baz: 4 })
  53. render(h(Comp, { qux: 5 }), root)
  54. expect(proxy.fooBar).toBeUndefined()
  55. // remove the props with camelCase key (#1412)
  56. expect(proxy.barBaz).toBeUndefined()
  57. expect(props).toEqual({})
  58. expect(attrs).toEqual({ qux: 5 })
  59. })
  60. test('stateful with setup', () => {
  61. let props: any
  62. let attrs: any
  63. const Comp = defineComponent({
  64. props: ['foo'],
  65. setup(_props, { attrs: _attrs }) {
  66. return () => {
  67. props = _props
  68. attrs = _attrs
  69. }
  70. },
  71. })
  72. const root = nodeOps.createElement('div')
  73. render(h(Comp, { foo: 1, bar: 2 }), root)
  74. expect(props).toEqual({ foo: 1 })
  75. expect(attrs).toEqual({ bar: 2 })
  76. render(h(Comp, { foo: 2, bar: 3, baz: 4 }), root)
  77. expect(props).toEqual({ foo: 2 })
  78. expect(attrs).toEqual({ bar: 3, baz: 4 })
  79. render(h(Comp, { qux: 5 }), root)
  80. expect(props).toEqual({})
  81. expect(attrs).toEqual({ qux: 5 })
  82. })
  83. test('functional with declaration', () => {
  84. let props: any
  85. let attrs: any
  86. const Comp: FunctionalComponent = (_props, { attrs: _attrs }) => {
  87. props = _props
  88. attrs = _attrs
  89. }
  90. Comp.props = ['foo']
  91. const root = nodeOps.createElement('div')
  92. render(h(Comp, { foo: 1, bar: 2 }), root)
  93. expect(props).toEqual({ foo: 1 })
  94. expect(attrs).toEqual({ bar: 2 })
  95. render(h(Comp, { foo: 2, bar: 3, baz: 4 }), root)
  96. expect(props).toEqual({ foo: 2 })
  97. expect(attrs).toEqual({ bar: 3, baz: 4 })
  98. render(h(Comp, { qux: 5 }), root)
  99. expect(props).toEqual({})
  100. expect(attrs).toEqual({ qux: 5 })
  101. })
  102. test('functional without declaration', () => {
  103. let props: any
  104. let attrs: any
  105. const Comp: FunctionalComponent = (_props, { attrs: _attrs }) => {
  106. props = _props
  107. attrs = _attrs
  108. }
  109. const root = nodeOps.createElement('div')
  110. render(h(Comp, { foo: 1 }), root)
  111. expect(props).toEqual({ foo: 1 })
  112. expect(attrs).toEqual({ foo: 1 })
  113. expect(toRaw(props)).toBe(attrs)
  114. render(h(Comp, { bar: 2 }), root)
  115. expect(props).toEqual({ bar: 2 })
  116. expect(attrs).toEqual({ bar: 2 })
  117. expect(toRaw(props)).toBe(attrs)
  118. })
  119. test('boolean casting', () => {
  120. let proxy: any
  121. const Comp = {
  122. props: {
  123. foo: Boolean,
  124. bar: Boolean,
  125. baz: Boolean,
  126. qux: Boolean,
  127. },
  128. render() {
  129. proxy = this
  130. },
  131. }
  132. render(
  133. h(Comp, {
  134. // absent should cast to false
  135. bar: '', // empty string should cast to true
  136. baz: 'baz', // same string should cast to true
  137. qux: 'ok', // other values should be left in-tact (but raise warning)
  138. }),
  139. nodeOps.createElement('div'),
  140. )
  141. expect(proxy.foo).toBe(false)
  142. expect(proxy.bar).toBe(true)
  143. expect(proxy.baz).toBe(true)
  144. expect(proxy.qux).toBe('ok')
  145. expect('type check failed for prop "qux"').toHaveBeenWarned()
  146. })
  147. test('default value', () => {
  148. let proxy: any
  149. const defaultFn = vi.fn(() => ({ a: 1 }))
  150. const defaultBaz = vi.fn(() => ({ b: 1 }))
  151. const Comp = {
  152. props: {
  153. foo: {
  154. default: 1,
  155. },
  156. bar: {
  157. default: defaultFn,
  158. },
  159. baz: {
  160. type: Function,
  161. default: defaultBaz,
  162. },
  163. },
  164. render() {
  165. proxy = this
  166. },
  167. }
  168. const root = nodeOps.createElement('div')
  169. render(h(Comp, { foo: 2 }), root)
  170. expect(proxy.foo).toBe(2)
  171. const prevBar = proxy.bar
  172. expect(proxy.bar).toEqual({ a: 1 })
  173. expect(proxy.baz).toEqual(defaultBaz)
  174. expect(defaultFn).toHaveBeenCalledTimes(1)
  175. expect(defaultBaz).toHaveBeenCalledTimes(0)
  176. // #999: updates should not cause default factory of unchanged prop to be
  177. // called again
  178. render(h(Comp, { foo: 3 }), root)
  179. expect(proxy.foo).toBe(3)
  180. expect(proxy.bar).toEqual({ a: 1 })
  181. expect(proxy.bar).toBe(prevBar)
  182. expect(defaultFn).toHaveBeenCalledTimes(1)
  183. render(h(Comp, { bar: { b: 2 } }), root)
  184. expect(proxy.foo).toBe(1)
  185. expect(proxy.bar).toEqual({ b: 2 })
  186. expect(defaultFn).toHaveBeenCalledTimes(1)
  187. render(h(Comp, { foo: 3, bar: { b: 3 } }), root)
  188. expect(proxy.foo).toBe(3)
  189. expect(proxy.bar).toEqual({ b: 3 })
  190. expect(defaultFn).toHaveBeenCalledTimes(1)
  191. render(h(Comp, { bar: { b: 4 } }), root)
  192. expect(proxy.foo).toBe(1)
  193. expect(proxy.bar).toEqual({ b: 4 })
  194. expect(defaultFn).toHaveBeenCalledTimes(1)
  195. })
  196. test('using inject in default value factory', () => {
  197. const Child = defineComponent({
  198. props: {
  199. test: {
  200. default: () => inject('test', 'default'),
  201. },
  202. },
  203. setup(props) {
  204. return () => {
  205. return h('div', props.test)
  206. }
  207. },
  208. })
  209. const Comp = {
  210. setup() {
  211. provide('test', 'injected')
  212. return () => h(Child)
  213. },
  214. }
  215. const root = nodeOps.createElement('div')
  216. render(h(Comp), root)
  217. expect(serializeInner(root)).toBe(`<div>injected</div>`)
  218. })
  219. test('optimized props updates', async () => {
  220. const Child = defineComponent({
  221. props: ['foo'],
  222. template: `<div>{{ foo }}</div>`,
  223. })
  224. const foo = ref(1)
  225. const id = ref('a')
  226. const Comp = defineComponent({
  227. setup() {
  228. return {
  229. foo,
  230. id,
  231. }
  232. },
  233. components: { Child },
  234. template: `<Child :foo="foo" :id="id"/>`,
  235. })
  236. // Note this one is using the main Vue render so it can compile template
  237. // on the fly
  238. const root = document.createElement('div')
  239. domRender(h(Comp), root)
  240. expect(root.innerHTML).toBe('<div id="a">1</div>')
  241. foo.value++
  242. await nextTick()
  243. expect(root.innerHTML).toBe('<div id="a">2</div>')
  244. id.value = 'b'
  245. await nextTick()
  246. expect(root.innerHTML).toBe('<div id="b">2</div>')
  247. })
  248. describe('validator', () => {
  249. test('validator should be called with two arguments', async () => {
  250. const mockFn = vi.fn((...args: any[]) => true)
  251. const Comp = defineComponent({
  252. props: {
  253. foo: {
  254. type: Number,
  255. validator: (value, props) => mockFn(value, props),
  256. },
  257. bar: {
  258. type: Number,
  259. },
  260. },
  261. template: `<div />`,
  262. })
  263. // Note this one is using the main Vue render so it can compile template
  264. // on the fly
  265. const root = document.createElement('div')
  266. domRender(h(Comp, { foo: 1, bar: 2 }), root)
  267. expect(mockFn).toHaveBeenCalledWith(1, { foo: 1, bar: 2 })
  268. })
  269. test('validator should not be able to mutate other props', async () => {
  270. const mockFn = vi.fn((...args: any[]) => true)
  271. const Comp = defineComponent({
  272. props: {
  273. foo: {
  274. type: Number,
  275. validator: (value, props) => !!(props.bar = 1),
  276. },
  277. bar: {
  278. type: Number,
  279. validator: value => mockFn(value),
  280. },
  281. },
  282. template: `<div />`,
  283. })
  284. // Note this one is using the main Vue render so it can compile template
  285. // on the fly
  286. const root = document.createElement('div')
  287. domRender(h(Comp, { foo: 1, bar: 2 }), root)
  288. expect(
  289. `Set operation on key "bar" failed: target is readonly.`,
  290. ).toHaveBeenWarnedLast()
  291. expect(mockFn).toHaveBeenCalledWith(2)
  292. })
  293. })
  294. test('warn props mutation', () => {
  295. let instance: ComponentInternalInstance
  296. let setupProps: any
  297. const Comp = {
  298. props: ['foo'],
  299. setup(props: any) {
  300. instance = getCurrentInstance()!
  301. setupProps = props
  302. return () => null
  303. },
  304. }
  305. render(h(Comp, { foo: 1 }), nodeOps.createElement('div'))
  306. expect(setupProps.foo).toBe(1)
  307. expect(instance!.props.foo).toBe(1)
  308. setupProps.foo = 2
  309. expect(`Set operation on key "foo" failed`).toHaveBeenWarned()
  310. expect(() => {
  311. ;(instance!.proxy as any).foo = 2
  312. }).toThrow(TypeError)
  313. expect(`Attempting to mutate prop "foo"`).toHaveBeenWarned()
  314. // should not throw when overriding properties other than props
  315. expect(() => {
  316. ;(instance!.proxy as any).hasOwnProperty = () => {}
  317. }).not.toThrow(TypeError)
  318. })
  319. test('warn absent required props', () => {
  320. const Comp = {
  321. props: {
  322. bool: { type: Boolean, required: true },
  323. str: { type: String, required: true },
  324. num: { type: Number, required: true },
  325. },
  326. setup() {
  327. return () => null
  328. },
  329. }
  330. render(h(Comp), nodeOps.createElement('div'))
  331. expect(`Missing required prop: "bool"`).toHaveBeenWarned()
  332. expect(`Missing required prop: "str"`).toHaveBeenWarned()
  333. expect(`Missing required prop: "num"`).toHaveBeenWarned()
  334. })
  335. test('warn on type mismatch', () => {
  336. class MyClass {}
  337. const Comp = {
  338. props: {
  339. bool: { type: Boolean },
  340. str: { type: String },
  341. num: { type: Number },
  342. arr: { type: Array },
  343. obj: { type: Object },
  344. cls: { type: MyClass },
  345. fn: { type: Function },
  346. skipCheck: { type: [Boolean, Function], skipCheck: true },
  347. empty: { type: [] },
  348. },
  349. setup() {
  350. return () => null
  351. },
  352. }
  353. render(
  354. h(Comp, {
  355. bool: 'true',
  356. str: 100,
  357. num: '100',
  358. arr: {},
  359. obj: 'false',
  360. cls: {},
  361. fn: true,
  362. skipCheck: 'foo',
  363. empty: [1, 2, 3],
  364. }),
  365. nodeOps.createElement('div'),
  366. )
  367. expect(
  368. `Invalid prop: type check failed for prop "bool". Expected Boolean, got String`,
  369. ).toHaveBeenWarned()
  370. expect(
  371. `Invalid prop: type check failed for prop "str". Expected String with value "100", got Number with value 100.`,
  372. ).toHaveBeenWarned()
  373. expect(
  374. `Invalid prop: type check failed for prop "num". Expected Number with value 100, got String with value "100".`,
  375. ).toHaveBeenWarned()
  376. expect(
  377. `Invalid prop: type check failed for prop "arr". Expected Array, got Object`,
  378. ).toHaveBeenWarned()
  379. expect(
  380. `Invalid prop: type check failed for prop "obj". Expected Object, got String with value "false"`,
  381. ).toHaveBeenWarned()
  382. expect(
  383. `Invalid prop: type check failed for prop "fn". Expected Function, got Boolean with value true.`,
  384. ).toHaveBeenWarned()
  385. expect(
  386. `Invalid prop: type check failed for prop "cls". Expected MyClass, got Object`,
  387. ).toHaveBeenWarned()
  388. expect(
  389. `Invalid prop: type check failed for prop "skipCheck". Expected Boolean | Function, got String with value "foo".`,
  390. ).not.toHaveBeenWarned()
  391. expect(
  392. `Prop type [] for prop "empty" won't match anything. Did you mean to use type Array instead?`,
  393. ).toHaveBeenWarned()
  394. })
  395. // #3495
  396. test('should not warn required props using kebab-case', async () => {
  397. const Comp = {
  398. props: {
  399. fooBar: { type: String, required: true },
  400. },
  401. setup() {
  402. return () => null
  403. },
  404. }
  405. render(
  406. h(Comp, {
  407. 'foo-bar': 'hello',
  408. }),
  409. nodeOps.createElement('div'),
  410. )
  411. expect(`Missing required prop: "fooBar"`).not.toHaveBeenWarned()
  412. })
  413. test('merging props from mixins and extends', () => {
  414. let setupProps: any
  415. let renderProxy: any
  416. const E = {
  417. props: ['base'],
  418. }
  419. const M1 = {
  420. props: ['m1'],
  421. }
  422. const M2 = {
  423. props: { m2: null },
  424. }
  425. const Comp = {
  426. props: ['self'],
  427. mixins: [M1, M2],
  428. extends: E,
  429. setup(props: any) {
  430. setupProps = props
  431. },
  432. render(this: any) {
  433. renderProxy = this
  434. return h('div', [this.self, this.base, this.m1, this.m2])
  435. },
  436. }
  437. const root = nodeOps.createElement('div')
  438. const props = {
  439. self: 'from self, ',
  440. base: 'from base, ',
  441. m1: 'from mixin 1, ',
  442. m2: 'from mixin 2',
  443. }
  444. render(h(Comp, props), root)
  445. expect(serializeInner(root)).toMatch(
  446. `from self, from base, from mixin 1, from mixin 2`,
  447. )
  448. expect(setupProps).toMatchObject(props)
  449. expect(renderProxy.$props).toMatchObject(props)
  450. })
  451. test('merging props from global mixins', () => {
  452. let setupProps: any
  453. let renderProxy: any
  454. const M1 = {
  455. props: ['m1'],
  456. }
  457. const M2 = {
  458. props: { m2: null },
  459. }
  460. const Comp = {
  461. props: ['self'],
  462. setup(props: any) {
  463. setupProps = props
  464. },
  465. render(this: any) {
  466. renderProxy = this
  467. return h('div', [this.self, this.m1, this.m2])
  468. },
  469. }
  470. const props = {
  471. self: 'from self, ',
  472. m1: 'from mixin 1, ',
  473. m2: 'from mixin 2',
  474. }
  475. const app = createApp(Comp, props)
  476. app.mixin(M1)
  477. app.mixin(M2)
  478. const root = nodeOps.createElement('div')
  479. app.mount(root)
  480. expect(serializeInner(root)).toMatch(
  481. `from self, from mixin 1, from mixin 2`,
  482. )
  483. expect(setupProps).toMatchObject(props)
  484. expect(renderProxy.$props).toMatchObject(props)
  485. })
  486. test('props type support BigInt', () => {
  487. const Comp = {
  488. props: {
  489. foo: BigInt,
  490. },
  491. render(this: any) {
  492. return h('div', [this.foo])
  493. },
  494. }
  495. const root = nodeOps.createElement('div')
  496. render(
  497. h(Comp, {
  498. foo: BigInt(BigInt(100000111)) + BigInt(2000000000) * BigInt(30000000),
  499. }),
  500. root,
  501. )
  502. expect(serializeInner(root)).toMatch('<div>60000000100000111</div>')
  503. })
  504. // #3474
  505. test('should cache the value returned from the default factory to avoid unnecessary watcher trigger', async () => {
  506. let count = 0
  507. const Comp = {
  508. props: {
  509. foo: {
  510. type: Object,
  511. default: () => ({ val: 1 }),
  512. },
  513. bar: Number,
  514. },
  515. setup(props: any) {
  516. watch(
  517. () => props.foo,
  518. () => {
  519. count++
  520. },
  521. )
  522. return () => h('h1', [props.foo.val, props.bar])
  523. },
  524. }
  525. const foo = ref()
  526. const bar = ref(0)
  527. const app = createApp({
  528. render: () => h(Comp, { foo: foo.value, bar: bar.value }),
  529. })
  530. const root = nodeOps.createElement('div')
  531. app.mount(root)
  532. expect(serializeInner(root)).toMatch(`<h1>10</h1>`)
  533. expect(count).toBe(0)
  534. bar.value++
  535. await nextTick()
  536. expect(serializeInner(root)).toMatch(`<h1>11</h1>`)
  537. expect(count).toBe(0)
  538. })
  539. // #3288
  540. test('declared prop key should be present even if not passed', async () => {
  541. let initialKeys: string[] = []
  542. const changeSpy = vi.fn()
  543. const passFoo = ref(false)
  544. const Comp = {
  545. render() {},
  546. props: {
  547. foo: String,
  548. },
  549. setup(props: any) {
  550. initialKeys = Object.keys(props)
  551. const { foo } = toRefs(props)
  552. watch(foo, changeSpy)
  553. },
  554. }
  555. const Parent = () => (passFoo.value ? h(Comp, { foo: 'ok' }) : h(Comp))
  556. const root = nodeOps.createElement('div')
  557. createApp(Parent).mount(root)
  558. expect(initialKeys).toMatchObject(['foo'])
  559. passFoo.value = true
  560. await nextTick()
  561. expect(changeSpy).toHaveBeenCalledTimes(1)
  562. })
  563. // #3371
  564. test(`avoid double-setting props when casting`, async () => {
  565. const Parent = {
  566. setup(props: any, { slots }: SetupContext) {
  567. const childProps = ref()
  568. const registerChildProps = (props: any) => {
  569. childProps.value = props
  570. }
  571. provide('register', registerChildProps)
  572. return () => {
  573. // access the child component's props
  574. childProps.value && childProps.value.foo
  575. return slots.default!()
  576. }
  577. },
  578. }
  579. const Child = {
  580. props: {
  581. foo: {
  582. type: Boolean,
  583. required: false,
  584. },
  585. },
  586. setup(props: { foo: boolean }) {
  587. const register = inject('register') as any
  588. // 1. change the reactivity data of the parent component
  589. // 2. register its own props to the parent component
  590. register(props)
  591. return () => 'foo'
  592. },
  593. }
  594. const App = {
  595. setup() {
  596. return () => h(Parent, () => h(Child as any, { foo: '' }, () => null))
  597. },
  598. }
  599. const root = nodeOps.createElement('div')
  600. render(h(App), root)
  601. await nextTick()
  602. expect(serializeInner(root)).toBe(`foo`)
  603. })
  604. test('support null in required + multiple-type declarations', () => {
  605. const Comp = {
  606. props: {
  607. foo: { type: [Function, null], required: true },
  608. },
  609. render() {},
  610. }
  611. const root = nodeOps.createElement('div')
  612. expect(() => {
  613. render(h(Comp, { foo: () => {} }), root)
  614. }).not.toThrow()
  615. expect(() => {
  616. render(h(Comp, { foo: null }), root)
  617. }).not.toThrow()
  618. })
  619. // #5016
  620. test('handling attr with undefined value', () => {
  621. const Comp = {
  622. render(this: any) {
  623. return JSON.stringify(this.$attrs) + Object.keys(this.$attrs)
  624. },
  625. }
  626. const root = nodeOps.createElement('div')
  627. let attrs: any = { foo: undefined }
  628. render(h(Comp, attrs), root)
  629. expect(serializeInner(root)).toBe(
  630. JSON.stringify(attrs) + Object.keys(attrs),
  631. )
  632. render(h(Comp, (attrs = { foo: 'bar' })), root)
  633. expect(serializeInner(root)).toBe(
  634. JSON.stringify(attrs) + Object.keys(attrs),
  635. )
  636. })
  637. // #691ef
  638. test('should not mutate original props long-form definition object', () => {
  639. const props = {
  640. msg: {
  641. type: String,
  642. },
  643. }
  644. const Comp = defineComponent({
  645. props,
  646. render() {},
  647. })
  648. const root = nodeOps.createElement('div')
  649. render(h(Comp, { msg: 'test' }), root)
  650. expect(Object.keys(props.msg).length).toBe(1)
  651. })
  652. test('should warn against reserved prop names', () => {
  653. const Comp = defineComponent({
  654. props: {
  655. key: String,
  656. ref: String,
  657. $foo: String,
  658. },
  659. render() {},
  660. })
  661. const root = nodeOps.createElement('div')
  662. render(h(Comp, { msg: 'test' }), root)
  663. expect(`Invalid prop name: "key"`).toHaveBeenWarned()
  664. expect(`Invalid prop name: "ref"`).toHaveBeenWarned()
  665. expect(`Invalid prop name: "$foo"`).toHaveBeenWarned()
  666. })
  667. })