compile_spec.js 19 KB

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