compile_spec.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. var Vue = require('src')
  2. var _ = require('src/util')
  3. var compiler = require('src/compiler')
  4. var compile = compiler.compile
  5. var publicDirectives = require('src/directives/public')
  6. var internalDirectives = require('src/directives/internal')
  7. describe('Compile', function () {
  8. var vm, el, data, directiveBind, directiveTeardown
  9. beforeEach(function () {
  10. // We mock vms here so we can assert what the generated
  11. // linker functions do.
  12. el = document.createElement('div')
  13. data = {}
  14. directiveBind = jasmine.createSpy('bind')
  15. directiveTeardown = jasmine.createSpy('teardown')
  16. vm = {
  17. _data: {},
  18. _directives: [],
  19. _bindDir: function (descriptor, node) {
  20. this._directives.push({
  21. name: descriptor.name,
  22. descriptor: descriptor,
  23. _bind: function () {
  24. directiveBind(this.name)
  25. },
  26. _teardown: directiveTeardown
  27. })
  28. },
  29. $get: function (exp) {
  30. return (new Vue()).$get(exp)
  31. },
  32. $eval: function (value) {
  33. return data[value]
  34. },
  35. $interpolate: function (value) {
  36. return data[value]
  37. },
  38. _context: {
  39. _directives: [],
  40. $get: function (v) {
  41. return 'from parent: ' + v
  42. }
  43. }
  44. }
  45. spyOn(vm, '_bindDir').and.callThrough()
  46. spyOn(vm, '$eval').and.callThrough()
  47. spyOn(vm, '$interpolate').and.callThrough()
  48. })
  49. it('normal directives', function () {
  50. el.setAttribute('v-a', 'b')
  51. el.innerHTML = '<p v-a:hello.a.b="a" v-b="1">hello</p><div v-b.literal="hi"></div>'
  52. var defA = { priority: 1 }
  53. var defB = { priority: 2 }
  54. var options = _.mergeOptions(Vue.options, {
  55. directives: {
  56. a: defA,
  57. b: defB
  58. }
  59. })
  60. var linker = compile(el, options)
  61. expect(typeof linker).toBe('function')
  62. linker(vm, el)
  63. expect(directiveBind.calls.count()).toBe(4)
  64. expect(vm._bindDir.calls.count()).toBe(4)
  65. // check if we are in firefox, which has different
  66. // attribute interation order
  67. var isAttrReversed = el.firstChild.attributes[0].name === 'v-b'
  68. // 1
  69. var args = vm._bindDir.calls.argsFor(0)
  70. expect(args[0].name).toBe('a')
  71. expect(args[0].expression).toBe('b')
  72. expect(args[0].def).toBe(defA)
  73. expect(args[1]).toBe(el)
  74. // 2
  75. args = vm._bindDir.calls.argsFor(isAttrReversed ? 2 : 1)
  76. expect(args[0].name).toBe('a')
  77. expect(args[0].expression).toBe('a')
  78. expect(args[0].def).toBe(defA)
  79. // args + multiple modifiers
  80. expect(args[0].arg).toBe('hello')
  81. expect(args[0].modifiers.a).toBe(true)
  82. expect(args[0].modifiers.b).toBe(true)
  83. expect(args[1]).toBe(el.firstChild)
  84. // 3 (expression literal)
  85. args = vm._bindDir.calls.argsFor(isAttrReversed ? 1 : 2)
  86. expect(args[0].name).toBe('b')
  87. expect(args[0].expression).toBe('1')
  88. expect(args[0].def).toBe(defB)
  89. expect(args[1]).toBe(el.firstChild)
  90. // 4 (explicit literal)
  91. args = vm._bindDir.calls.argsFor(3)
  92. expect(args[0].name).toBe('b')
  93. expect(args[0].expression).toBe('hi')
  94. expect(args[0].def).toBe(defB)
  95. expect(args[0].modifiers.literal).toBe(true)
  96. expect(args[1]).toBe(el.lastChild)
  97. // check the priority sorting
  98. // the "b"s should be called first!
  99. expect(directiveBind.calls.argsFor(0)[0]).toBe('b')
  100. expect(directiveBind.calls.argsFor(1)[0]).toBe('b')
  101. expect(directiveBind.calls.argsFor(2)[0]).toBe('a')
  102. expect(directiveBind.calls.argsFor(3)[0]).toBe('a')
  103. })
  104. it('v-bind shorthand', function () {
  105. el.setAttribute(':class', 'a')
  106. el.setAttribute(':style', 'b')
  107. el.setAttribute(':title', 'c')
  108. // The order of setAttribute is not guaranteed to be the same with
  109. // the order of attribute enumberation, therefore we need to save
  110. // it here!
  111. var descriptors = {
  112. ':class': {
  113. name: 'class',
  114. attr: ':class',
  115. expression: 'a',
  116. def: internalDirectives.class
  117. },
  118. ':style': {
  119. name: 'style',
  120. attr: ':style',
  121. expression: 'b',
  122. def: internalDirectives.style
  123. },
  124. ':title': {
  125. name: 'bind',
  126. attr: ':title',
  127. expression: 'c',
  128. arg: 'title',
  129. def: publicDirectives.bind
  130. }
  131. }
  132. var expects = [].map.call(el.attributes, function (attr) {
  133. return descriptors[attr.name]
  134. })
  135. var linker = compile(el, Vue.options)
  136. linker(vm, el)
  137. expect(vm._bindDir.calls.count()).toBe(3)
  138. expects.forEach(function (e, i) {
  139. var args = vm._bindDir.calls.argsFor(i)
  140. for (var key in e) {
  141. expect(args[0][key]).toBe(e[key])
  142. }
  143. expect(args[1]).toBe(el)
  144. })
  145. })
  146. it('v-on shorthand', function () {
  147. el.innerHTML = '<div @click="a++"></div>'
  148. el = el.firstChild
  149. var linker = compile(el, Vue.options)
  150. linker(vm, el)
  151. expect(vm._bindDir.calls.count()).toBe(1)
  152. var args = vm._bindDir.calls.argsFor(0)
  153. expect(args[0].name).toBe('on')
  154. expect(args[0].expression).toBe('a++')
  155. expect(args[0].arg).toBe('click')
  156. expect(args[0].def).toBe(publicDirectives.on)
  157. expect(args[1]).toBe(el)
  158. })
  159. it('text interpolation', function () {
  160. data.b = 'yeah'
  161. el.innerHTML = '{{a}} and {{*b}}'
  162. var def = Vue.options.directives.text
  163. var linker = compile(el, Vue.options)
  164. linker(vm, el)
  165. // expect 1 call because one-time bindings do not generate a directive.
  166. expect(vm._bindDir.calls.count()).toBe(1)
  167. var args = vm._bindDir.calls.argsFor(0)
  168. expect(args[0].name).toBe('text')
  169. expect(args[0].expression).toBe('a')
  170. expect(args[0].def).toBe(def)
  171. // skip the node because it's generated in the linker fn via cloneNode
  172. // expect $eval to be called during onetime
  173. expect(vm.$eval).toHaveBeenCalledWith('b')
  174. // {{a}} is mocked so it's a space.
  175. // but we want to make sure {{*b}} worked.
  176. expect(el.innerHTML).toBe(' and yeah')
  177. })
  178. it('text interpolation, adjacent nodes', function () {
  179. data.b = 'yeah'
  180. el.appendChild(document.createTextNode('{{a'))
  181. el.appendChild(document.createTextNode('}} and {{'))
  182. el.appendChild(document.createTextNode('*b}}'))
  183. var def = Vue.options.directives.text
  184. var linker = compile(el, Vue.options)
  185. linker(vm, el)
  186. // expect 1 call because one-time bindings do not generate a directive.
  187. expect(vm._bindDir.calls.count()).toBe(1)
  188. var args = vm._bindDir.calls.argsFor(0)
  189. expect(args[0].name).toBe('text')
  190. expect(args[0].expression).toBe('a')
  191. expect(args[0].def).toBe(def)
  192. // skip the node because it's generated in the linker fn via cloneNode
  193. // expect $eval to be called during onetime
  194. expect(vm.$eval).toHaveBeenCalledWith('b')
  195. // {{a}} is mocked so it's a space.
  196. // but we want to make sure {{*b}} worked.
  197. expect(el.innerHTML).toBe(' and yeah')
  198. })
  199. it('adjacent text nodes with no interpolation', function () {
  200. el.appendChild(document.createTextNode('a'))
  201. el.appendChild(document.createTextNode('b'))
  202. el.appendChild(document.createTextNode('c'))
  203. var linker = compile(el, Vue.options)
  204. linker(vm, el)
  205. expect(el.innerHTML).toBe('abc')
  206. })
  207. it('inline html', function () {
  208. data.html = '<div>yoyoyo</div>'
  209. el.innerHTML = '{{{html}}} {{{*html}}}'
  210. var htmlDef = Vue.options.directives.html
  211. var linker = compile(el, Vue.options)
  212. linker(vm, el)
  213. expect(vm._bindDir.calls.count()).toBe(1)
  214. var htmlArgs = vm._bindDir.calls.argsFor(0)
  215. expect(htmlArgs[0].name).toBe('html')
  216. expect(htmlArgs[0].expression).toBe('html')
  217. expect(htmlArgs[0].def).toBe(htmlDef)
  218. // with placeholder comments & interpolated one-time html
  219. expect(el.innerHTML).toBe('<!--v-html--> <div>yoyoyo</div>')
  220. })
  221. it('terminal directives', function () {
  222. el.innerHTML =
  223. '<div v-for="item in items"><p v-a="b"></p></div>' + // v-for
  224. '<div v-pre><p v-a="b"></p></div>' // v-pre
  225. var def = Vue.options.directives.for
  226. var linker = compile(el, Vue.options)
  227. linker(vm, el)
  228. // expect 1 call because terminal should return early and let
  229. // the directive handle the rest.
  230. expect(vm._bindDir.calls.count()).toBe(1)
  231. var args = vm._bindDir.calls.argsFor(0)
  232. expect(args[0].name).toBe('for')
  233. expect(args[0].expression).toBe('item in items')
  234. expect(args[0].def).toBe(def)
  235. expect(args[1]).toBe(el.firstChild)
  236. })
  237. it('custom element components', function () {
  238. var options = _.mergeOptions(Vue.options, {
  239. components: {
  240. 'my-component': {}
  241. }
  242. })
  243. el.innerHTML = '<my-component><div v-a="b"></div></my-component>'
  244. var linker = compile(el, options)
  245. linker(vm, el)
  246. expect(vm._bindDir.calls.count()).toBe(1)
  247. var args = vm._bindDir.calls.argsFor(0)
  248. expect(args[0].name).toBe('component')
  249. expect(args[0].expression).toBe('my-component')
  250. expect(args[0].modifiers.literal).toBe(true)
  251. expect(args[0].def).toBe(internalDirectives.component)
  252. expect(getWarnCount()).toBe(0)
  253. })
  254. it('props', function () {
  255. var bindingModes = Vue.config._propBindingModes
  256. var props = {
  257. testNormal: null,
  258. testLiteral: null,
  259. testBoolean: { type: Boolean },
  260. testTwoWay: null,
  261. twoWayWarn: null,
  262. testOneTime: null,
  263. optimizeLiteral: null,
  264. optimizeLiteralStr: null,
  265. optimizeLiteralNegativeNumber: null,
  266. literalWithFilter: null
  267. }
  268. el.innerHTML = '<div ' +
  269. 'v-bind:test-normal="a" ' +
  270. 'test-literal="1" ' +
  271. 'test-boolean ' +
  272. ':optimize-literal="1" ' +
  273. ':optimize-literal-str="\'true\'"' +
  274. ':optimize-literal-negative-number="-1"' +
  275. ':test-two-way.sync="a" ' +
  276. ':two-way-warn.sync="a + 1" ' +
  277. ':test-one-time.once="a" ' +
  278. ':literal-with-filter="\'HI\' | lowercase"' +
  279. '></div>'
  280. compiler.compileAndLinkProps(vm, el.firstChild, props)
  281. // check bindDir calls:
  282. // skip literal and one time, but not literal with filter
  283. expect(vm._bindDir.calls.count()).toBe(4)
  284. // literal
  285. expect(vm.testLiteral).toBe('1')
  286. expect(vm.testBoolean).toBe(true)
  287. expect(vm.optimizeLiteral).toBe(1)
  288. expect(vm.optimizeLiteralStr).toBe('true')
  289. expect(vm.optimizeLiteralNegativeNumber).toBe(-1)
  290. // one time
  291. expect(vm.testOneTime).toBe('from parent: a')
  292. // normal
  293. var args = vm._bindDir.calls.argsFor(0)
  294. var prop = args[0].prop
  295. expect(args[0].name).toBe('prop')
  296. expect(prop.path).toBe('testNormal')
  297. expect(prop.parentPath).toBe('a')
  298. expect(prop.mode).toBe(bindingModes.ONE_WAY)
  299. // two way
  300. args = vm._bindDir.calls.argsFor(1)
  301. prop = args[0].prop
  302. expect(args[0].name).toBe('prop')
  303. expect(prop.path).toBe('testTwoWay')
  304. expect(prop.parentPath).toBe('a')
  305. expect(prop.mode).toBe(bindingModes.TWO_WAY)
  306. // two way warn
  307. expect('non-settable parent path').toHaveBeenWarned()
  308. // literal with filter
  309. args = vm._bindDir.calls.argsFor(3)
  310. prop = args[0].prop
  311. expect(args[0].name).toBe('prop')
  312. expect(prop.path).toBe('literalWithFilter')
  313. expect(prop.parentPath).toBe("'HI'")
  314. expect(prop.filters.length).toBe(1)
  315. expect(prop.mode).toBe(bindingModes.ONE_WAY)
  316. })
  317. it('props on root instance', function () {
  318. // temporarily remove vm.$parent
  319. var context = vm._context
  320. vm._context = null
  321. el.setAttribute('v-bind:a', '"hi"')
  322. el.setAttribute(':b', '[1,2,3]')
  323. compiler.compileAndLinkProps(vm, el, { a: null, b: null })
  324. expect(vm._bindDir.calls.count()).toBe(0)
  325. expect(vm.a).toBe('hi')
  326. expect(vm.b.join(',')).toBe('1,2,3')
  327. // restore parent mock
  328. vm._context = context
  329. })
  330. it('DocumentFragment', function () {
  331. var frag = document.createDocumentFragment()
  332. frag.appendChild(el)
  333. var el2 = document.createElement('div')
  334. frag.appendChild(el2)
  335. el.innerHTML = '{{*a}}'
  336. el2.innerHTML = '{{*b}}'
  337. data.a = 'A'
  338. data.b = 'B'
  339. var linker = compile(frag, Vue.options)
  340. linker(vm, frag)
  341. expect(el.innerHTML).toBe('A')
  342. expect(el2.innerHTML).toBe('B')
  343. })
  344. it('partial compilation', function () {
  345. el.innerHTML = '<div v-bind:test="abc">{{bcd}}<p v-show="ok"></p></div>'
  346. var linker = compile(el, Vue.options, true)
  347. var decompile = linker(vm, el)
  348. expect(vm._directives.length).toBe(3)
  349. decompile()
  350. expect(directiveTeardown.calls.count()).toBe(3)
  351. expect(vm._directives.length).toBe(0)
  352. })
  353. it('skip script tags', function () {
  354. el.innerHTML = '<script type="x/template">{{test}}</script>'
  355. var linker = compile(el, Vue.options)
  356. linker(vm, el)
  357. expect(vm._bindDir.calls.count()).toBe(0)
  358. })
  359. it('should handle container/replacer directives with same name', function () {
  360. var parentSpy = jasmine.createSpy()
  361. var childSpy = jasmine.createSpy()
  362. vm = new Vue({
  363. el: el,
  364. template:
  365. '<test class="a" v-on:click="test(1)"></test>',
  366. methods: {
  367. test: parentSpy
  368. },
  369. components: {
  370. test: {
  371. template: '<div class="b" v-on:click="test(2)"></div>',
  372. replace: true,
  373. methods: {
  374. test: childSpy
  375. }
  376. }
  377. }
  378. })
  379. expect(vm.$el.firstChild.className).toBe('b a')
  380. var e = document.createEvent('HTMLEvents')
  381. e.initEvent('click', true, true)
  382. vm.$el.firstChild.dispatchEvent(e)
  383. expect(parentSpy).toHaveBeenCalledWith(1)
  384. expect(childSpy).toHaveBeenCalledWith(2)
  385. })
  386. it('should teardown props and replacer directives when unlinking', function () {
  387. var vm = new Vue({
  388. el: el,
  389. template: '<test :msg="msg"></test>',
  390. data: {
  391. msg: 'hi'
  392. },
  393. components: {
  394. test: {
  395. props: ['msg'],
  396. template: '<div v-show="true"></div>',
  397. replace: true
  398. }
  399. }
  400. })
  401. var dirs = vm.$children[0]._directives
  402. expect(dirs.length).toBe(2)
  403. vm.$children[0].$destroy()
  404. var i = dirs.length
  405. while (i--) {
  406. expect(dirs[i]._bound).toBe(false)
  407. }
  408. })
  409. it('should remove parent container directives from parent when unlinking', function () {
  410. var vm = new Vue({
  411. el: el,
  412. template:
  413. '<test v-show="ok"></test>',
  414. data: {
  415. ok: true
  416. },
  417. components: {
  418. test: {
  419. template: 'hi'
  420. }
  421. }
  422. })
  423. expect(el.firstChild.style.display).toBe('')
  424. expect(vm._directives.length).toBe(2)
  425. expect(vm.$children.length).toBe(1)
  426. vm.$children[0].$destroy()
  427. expect(vm._directives.length).toBe(1)
  428. expect(vm.$children.length).toBe(0)
  429. })
  430. it('should remove transcluded directives from parent when unlinking (component)', function () {
  431. var vm = new Vue({
  432. el: el,
  433. template:
  434. '<test>{{test}}</test>',
  435. data: {
  436. test: 'parent'
  437. },
  438. components: {
  439. test: {
  440. template: '<slot></slot>'
  441. }
  442. }
  443. })
  444. expect(vm.$el.textContent).toBe('parent')
  445. expect(vm._directives.length).toBe(2)
  446. expect(vm.$children.length).toBe(1)
  447. vm.$children[0].$destroy()
  448. expect(vm._directives.length).toBe(1)
  449. expect(vm.$children.length).toBe(0)
  450. })
  451. it('should remove transcluded directives from parent when unlinking (v-if + component)', function (done) {
  452. var vm = new Vue({
  453. el: el,
  454. template:
  455. '<div v-if="ok">' +
  456. '<test>{{test}}</test>' +
  457. '</div>',
  458. data: {
  459. test: 'parent',
  460. ok: true
  461. },
  462. components: {
  463. test: {
  464. template: '<slot></slot>'
  465. }
  466. }
  467. })
  468. expect(vm.$el.textContent).toBe('parent')
  469. expect(vm._directives.length).toBe(3)
  470. expect(vm.$children.length).toBe(1)
  471. vm.ok = false
  472. _.nextTick(function () {
  473. expect(vm.$el.textContent).toBe('')
  474. expect(vm._directives.length).toBe(1)
  475. expect(vm.$children.length).toBe(0)
  476. done()
  477. })
  478. })
  479. it('element directive', function () {
  480. new Vue({
  481. el: el,
  482. template: '<test>{{a}}</test>',
  483. elementDirectives: {
  484. test: {
  485. bind: function () {
  486. this.el.setAttribute('test', '1')
  487. }
  488. }
  489. }
  490. })
  491. // should be terminal
  492. expect(el.innerHTML).toBe('<test test="1">{{a}}</test>')
  493. })
  494. it('attribute interpolation', function (done) {
  495. var vm = new Vue({
  496. el: el,
  497. template: '<div id="{{a}}" class="b bla-{{c}} d"></div>',
  498. data: {
  499. a: 'aaa',
  500. c: 'ccc'
  501. }
  502. })
  503. expect(el.firstChild.id).toBe('aaa')
  504. expect(el.firstChild.className).toBe('b bla-ccc d')
  505. vm.a = 'aa'
  506. vm.c = 'cc'
  507. _.nextTick(function () {
  508. expect(el.firstChild.id).toBe('aa')
  509. expect(el.firstChild.className).toBe('b bla-cc d')
  510. done()
  511. })
  512. })
  513. it('attribute interpolation: one-time', function (done) {
  514. var vm = new Vue({
  515. el: el,
  516. template: '<div id="{{a}} b {{*c}}"></div>',
  517. data: {
  518. a: 'aaa',
  519. c: 'ccc'
  520. }
  521. })
  522. expect(el.firstChild.id).toBe('aaa b ccc')
  523. vm.a = 'aa'
  524. vm.c = 'cc'
  525. _.nextTick(function () {
  526. expect(el.firstChild.id).toBe('aa b ccc')
  527. done()
  528. })
  529. })
  530. it('attribute interpolation: special cases', function () {
  531. new Vue({
  532. el: el,
  533. template: '<label for="{{a}}" data-test="{{b}}"></label><form accept-charset="{{c}}"></form>',
  534. data: {
  535. a: 'aaa',
  536. b: 'bbb',
  537. c: 'UTF-8'
  538. }
  539. })
  540. expect(el.innerHTML).toBe('<label for="aaa" data-test="bbb"></label><form accept-charset="UTF-8"></form>')
  541. })
  542. it('attribute interpolation: warn invalid', function () {
  543. new Vue({
  544. el: el,
  545. template: '<div v-text="{{a}}"></div>',
  546. data: {
  547. a: '123'
  548. }
  549. })
  550. expect(el.innerHTML).toBe('<div></div>')
  551. expect('attribute interpolation is not allowed in Vue.js directives').toHaveBeenWarned()
  552. })
  553. it('attribute interpolation: warn mixed usage with v-bind', function () {
  554. new Vue({
  555. el: el,
  556. template: '<div class="{{a}}" :class="bcd"></div>',
  557. data: {
  558. a: 'hi'
  559. }
  560. })
  561. expect('Do not mix mustache interpolation and v-bind').toHaveBeenWarned()
  562. })
  563. it('warn directives on fragment instances', function () {
  564. new Vue({
  565. el: el,
  566. template: '<test id="hi" class="ok" :prop="123"></test>',
  567. components: {
  568. test: {
  569. replace: true,
  570. props: ['prop'],
  571. template: '{{prop}}'
  572. }
  573. }
  574. })
  575. expect(getWarnCount()).toBe(1)
  576. expect([
  577. 'Attributes "id", "class" are ignored on component <test>',
  578. 'Attributes "class", "id" are ignored on component <test>'
  579. ]).toHaveBeenWarned()
  580. })
  581. it('should compile component container directives using correct context', function () {
  582. new Vue({
  583. el: el,
  584. directives: {
  585. test: {
  586. bind: function () {
  587. this.el.textContent = 'worked!'
  588. }
  589. }
  590. },
  591. template: '<comp v-test></comp>',
  592. components: { comp: { template: '<div></div>' }}
  593. })
  594. expect(el.textContent).toBe('worked!')
  595. expect(getWarnCount()).toBe(0)
  596. })
  597. it('allow custom terminal directive', function () {
  598. Vue.mixin({}) // #2366 conflict with custom terminal directive
  599. Vue.compiler.terminalDirectives.push('foo')
  600. Vue.directive('foo', {})
  601. new Vue({
  602. el: el,
  603. template: '<div v-foo></div>'
  604. })
  605. expect(getWarnCount()).toBe(0)
  606. })
  607. })