How to use useQuerySelector method in tracetest

Best JavaScript code snippet using tracetest

test-bind-impl.js

Source:test-bind-impl.js Github

copy

Full Screen

1/**2 * Copyright 2016 The AMP HTML Authors. All Rights Reserved.3 *4 * Licensed under the Apache License, Version 2.0 (the "License");5 * you may not use this file except in compliance with the License.6 * You may obtain a copy of the License at7 *8 * http://www.apache.org/licenses/LICENSE-2.09 *10 * Unless required by applicable law or agreed to in writing, software11 * distributed under the License is distributed on an "AS-IS" BASIS,12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13 * See the License for the specific language governing permissions and14 * limitations under the License.15 */16/**17 * @fileoverview "Unit" test for bind-impl.js. Runs as an integration test18 * because it requires building web-worker binary.19 */20import * as fakeTimers from '@sinonjs/fake-timers';21import {AmpEvents} from '#core/constants/amp-events';22import {Bind} from '../../bind-impl';23import {BindEvents} from '../../bind-events';24import {Deferred} from '#core/data-structures/promise';25import {RAW_OBJECT_ARGS_KEY} from '#core/constants/action-constants';26import {Services} from '#service';27import {chunkInstanceForTesting} from '../../../../../src/chunk';28import {dev, user} from '../../../../../src/log';29import {toArray} from '#core/types/array';30/**31 * @param {!Object} env32 * @param {?Element} container33 * @param {string} binding34 * @param {string=} opts.tag Tag name of element (default is <p>).35 * @param {boolean=} opts.amp Is this an AMP element?36 * @param {boolean=} opts.insertInHead Add element to document <head>?37 * @param {boolean=} opts.insertQuerySelectorAttr Add i-amphtml-binding attribute to element?38 * @return {!Element}39 */40function createElement(env, container, binding, opts = {}) {41 const {42 amp = false,43 insertInHead = false,44 insertQuerySelectorAttr = false,45 tag = 'p',46 } = opts;47 const div = env.win.document.createElement('div');48 div.innerHTML = `<${tag} ${binding}></${tag}>`;49 const element = div.firstElementChild;50 if (amp) {51 element.className = 'i-amphtml-foo -amp-foo amp-foo';52 element.mutatedAttributesCallback = () => {};53 }54 if (insertQuerySelectorAttr) {55 element.setAttribute('i-amphtml-binding', '');56 }57 if (insertInHead) {58 env.win.document.head.appendChild(element);59 } else if (container) {60 container.appendChild(element);61 }62 return element;63}64/**65 * @param {!Object} env66 * @param {?Element} container67 * @param {string} id68 * @param {!Promise} valuePromise69 */70function addAmpState(env, container, id, fetchingPromise) {71 const ampState = env.win.document.createElement('amp-state');72 ampState.setAttribute('id', id);73 ampState.createdCallback = () => {};74 ampState.getImpl = () =>75 Promise.resolve({76 getFetchingPromise() {77 return fetchingPromise;78 },79 parseAndUpdate: () => {},80 element: ampState,81 });82 container.appendChild(ampState);83}84/**85 * @param {!Object} env86 * @param {!Bind} bind87 * @return {!Promise}88 */89function onBindReady(env, bind) {90 return bind.initializePromiseForTesting().then(() => {91 env.flushVsync();92 });93}94/**95 * @param {!Object} env96 * @param {!Bind} bind97 * @param {!Object} state98 * @param {boolean=} skipAmpState99 * @return {!Promise}100 */101function onBindReadyAndSetState(env, bind, state, skipAmpState) {102 return bind103 .initializePromiseForTesting()104 .then(() => {105 return bind.setState(state, {skipAmpState});106 })107 .then(() => {108 env.flushVsync();109 return bind.setStatePromiseForTesting();110 });111}112/**113 * @param {!Object} env114 * @param {!Bind} bind115 * @param {string} expression116 * @param {!Object} scope117 * @return {!Promise}118 */119function onBindReadyAndSetStateWithExpression(env, bind, expression, scope) {120 return bind121 .initializePromiseForTesting()122 .then(() => {123 return bind.setStateWithExpression(expression, scope);124 })125 .then(() => {126 env.flushVsync();127 return bind.setStatePromiseForTesting();128 });129}130/**131 * @param {!Object} env132 * @param {!Bind} bind133 * @param {!JsonObject} state134 * @return {!Promise}135 */136function onBindReadyAndSetStateWithObject(env, bind, state) {137 return bind138 .initializePromiseForTesting()139 .then(() => {140 return bind.setStateWithObject(state);141 })142 .then(() => {143 env.flushVsync();144 return bind.setStatePromiseForTesting();145 });146}147/**148 * @param {!Object} env149 * @param {!Bind} bind150 * @param {string} name151 * @return {!Promise}152 */153function onBindReadyAndGetState(env, bind, name) {154 return bind.initializePromiseForTesting().then(() => {155 env.flushVsync();156 return bind.getState(name);157 });158}159/**160 * @param {!Object} env161 * @param {!Bind} bind162 * @param {!Array<!Element>} added163 * @param {!Array<!Element>} removed164 * @param {!BindRescanOptions=} options165 * @return {!Promise}166 */167function onBindReadyAndRescan(env, bind, added, removed, options) {168 return bind169 .initializePromiseForTesting()170 .then(() => {171 return bind.rescan(added, removed, options);172 })173 .then(() => {174 env.flushVsync();175 });176}177/**178 * @param {!Object} env179 * @param {string} name180 * @return {!Promise}181 */182function waitForEvent(env, name) {183 return new Promise((resolve) => {184 const callback = () => {185 resolve();186 env.win.removeEventListener(name, callback);187 };188 env.win.addEventListener(name, callback);189 });190}191const FORM_VALUE_CHANGE_EVENT_ARGUMENTS = {192 type: AmpEvents.FORM_VALUE_CHANGE,193 bubbles: true,194};195const chromed = describes.sandboxed.configure().ifChrome();196chromed.run('Bind', {}, function () {197 describes.repeated(198 'Walker',199 {200 'using TreeWalker': {useQuerySelector: false},201 'using querySelectorAll': {useQuerySelector: true},202 },203 (name, variant) => {204 describe(name, function () {205 testSuite.call(this, variant.useQuerySelector);206 });207 }208 );209 function testSuite(useQuerySelector) {210 // Give more than default 2000ms timeout for local testing.211 const TIMEOUT = Math.max(window.ampTestRuntimeConfig.mochaTimeout, 4000);212 this.timeout(TIMEOUT);213 describes.realWin(214 'in FIE',215 {216 amp: {217 ampdoc: 'fie',218 runtimeOn: false,219 },220 mockFetch: false,221 },222 (env) => {223 let fieBind;224 let fieWindow, fieBody;225 let hostWindow, hostBody;226 beforeEach(() => {227 // Make sure we have a chunk instance for testing.228 chunkInstanceForTesting(env.ampdoc);229 if (useQuerySelector) {230 env.win.document.documentElement.setAttribute(231 'i-amphtml-binding',232 ''233 );234 }235 fieBind = new Bind(env.ampdoc);236 fieWindow = env.ampdoc.win;237 fieBody = env.ampdoc.getBody();238 hostWindow = env.parentWin;239 hostBody = env.parentAmpdoc.getBody();240 });241 it('should scan for bindings when ampdoc is ready', () => {242 createElement(env, fieBody, '[text]="1+1"', {243 insertQuerySelectorAttr: useQuerySelector,244 });245 expect(fieBind.numberOfBindings()).to.equal(0);246 return onBindReady(env, fieBind).then(() => {247 expect(fieBind.numberOfBindings()).to.equal(1);248 });249 });250 it('should not update host document title for <title> elements', () => {251 createElement(env, fieBody, '[text]="\'bar\'"', {252 tag: 'title',253 amp: false,254 insertInHead: true,255 insertQuerySelectorAttr: useQuerySelector,256 });257 fieWindow.document.title = 'foo';258 hostWindow.document.title = 'foo';259 return onBindReadyAndSetState(env, fieBind, {}).then(() => {260 // Make sure it does not update the host window's document title.261 expect(fieWindow.document.title).to.equal('bar');262 expect(hostWindow.document.title).to.equal('foo');263 });264 });265 describe('with Bind in host window', () => {266 let hostBind;267 beforeEach(() => {268 hostBind = new Bind(env.parentAmpdoc);269 });270 it('should only scan elements in provided window', () => {271 createElement(env, fieBody, '[text]="1+1"', {272 insertQuerySelectorAttr: useQuerySelector,273 });274 createElement(env, hostBody, '[text]="2+2"', {275 insertQuerySelectorAttr: useQuerySelector,276 });277 return Promise.all([278 onBindReady(env, fieBind),279 onBindReady(env, hostBind),280 ]).then(() => {281 expect(fieBind.numberOfBindings()).to.equal(1);282 expect(hostBind.numberOfBindings()).to.equal(1);283 });284 });285 it('should not be able to access variables from other windows', () => {286 const element = createElement(env, fieBody, '[text]="foo + bar"', {287 insertQuerySelectorAttr: useQuerySelector,288 });289 const parentElement = createElement(290 env,291 hostBody,292 '[text]="foo + bar"',293 {insertQuerySelectorAttr: useQuerySelector}294 );295 const promises = [296 onBindReadyAndSetState(env, fieBind, {foo: '123', bar: '456'}),297 onBindReadyAndSetState(env, hostBind, {foo: 'ABC', bar: 'DEF'}),298 ];299 return Promise.all(promises).then(() => {300 // `element` only sees `foo` and `parentElement` only sees `bar`.301 expect(element.textContent).to.equal('123456');302 expect(parentElement.textContent).to.equal('ABCDEF');303 });304 });305 });306 }307 );308 describes.realWin(309 'in shadow ampdoc',310 {311 amp: {312 ampdoc: 'shadow',313 runtimeOn: false,314 },315 mockFetch: false,316 },317 (env) => {318 let bind;319 let container;320 beforeEach(() => {321 // Make sure we have a chunk instance for testing.322 chunkInstanceForTesting(env.ampdoc);323 if (useQuerySelector) {324 env.win.document.documentElement.setAttribute(325 'i-amphtml-binding',326 ''327 );328 }329 bind = new Bind(env.ampdoc);330 container = env.ampdoc.getBody();331 });332 it('should scan for bindings when ampdoc is ready', () => {333 createElement(env, container, '[text]="1+1"', {334 insertQuerySelectorAttr: useQuerySelector,335 });336 expect(bind.numberOfBindings()).to.equal(0);337 return onBindReady(env, bind).then(() => {338 expect(bind.numberOfBindings()).to.equal(1);339 });340 });341 it('should not update document title for <title> elements', () => {342 createElement(env, container, '[text]="\'bar\'"', {343 tag: 'title',344 amp: false,345 insertInHead: true,346 insertQuerySelectorAttr: useQuerySelector,347 });348 env.win.document.title = 'foo';349 return onBindReadyAndSetState(env, bind, {}).then(() => {350 // Make sure does not update the host window's document title.351 expect(env.win.document.title).to.equal('foo');352 });353 });354 }355 );356 describes.realWin(357 'in single ampdoc',358 {359 amp: {360 ampdoc: 'single',361 runtimeOn: false,362 },363 mockFetch: false,364 },365 (env) => {366 let bind;367 let clock;368 let container;369 let history;370 let viewer;371 beforeEach(() => {372 const {ampdoc, win} = env;373 // Make sure we have a chunk instance for testing.374 chunkInstanceForTesting(ampdoc);375 viewer = Services.viewerForDoc(ampdoc);376 env.sandbox.stub(viewer, 'sendMessage');377 if (useQuerySelector) {378 env.win.document.documentElement.setAttribute(379 'i-amphtml-binding',380 ''381 );382 }383 bind = new Bind(ampdoc);384 // Connected <div> element created by describes.js.385 container = win.document.getElementById('parent');386 history = bind.historyForTesting();387 clock = fakeTimers.withGlobal(win).install({388 toFake: ['Date', 'setTimeout'],389 });390 });391 afterEach(() => {392 clock.uninstall();393 });394 it('should send "bindReady" to viewer on init', () => {395 expect(viewer.sendMessage).to.not.be.called;396 return onBindReady(env, bind).then(() => {397 expect(viewer.sendMessage).to.be.calledOnce;398 expect(viewer.sendMessage).to.be.calledWith('bindReady');399 });400 });401 it('should not send "bindReady" until all <amp-state> are built', () => {402 const element = createElement(env, container, '', {403 tag: 'amp-state',404 amp: true,405 insertQuerySelectorAttr: useQuerySelector,406 });407 // Makes whenUpgradedToCustomElement() resolve immediately.408 element.createdCallback = () => {};409 const parseAndUpdate = env.sandbox.spy();410 element.getImpl = () => {411 expect(viewer.sendMessage).to.not.be.called;412 return Promise.resolve({parseAndUpdate});413 };414 return onBindReady(env, bind).then(() => {415 expect(parseAndUpdate).to.be.calledOnce;416 expect(viewer.sendMessage).to.be.calledOnce;417 expect(viewer.sendMessage).to.be.calledWith('bindReady');418 });419 });420 it('should scan for bindings when ampdoc is ready', () => {421 createElement(env, container, '[text]="1+1"', {422 insertQuerySelectorAttr: useQuerySelector,423 });424 expect(bind.numberOfBindings()).to.equal(0);425 return onBindReady(env, bind).then(() => {426 expect(bind.numberOfBindings()).to.equal(1);427 });428 });429 it('should skip amp-list children during scan', () => {430 // <div>431 // <amp-list [foo]="1+1">432 // <h1 [text]="2+2"></h1>433 // </amp-list>434 // </div>435 // <p>436 // <span [text]="3+3"></span>437 // </p>438 const parent = document.createElement('div');439 container.appendChild(parent);440 const uncle = document.createElement('p');441 container.appendChild(uncle);442 const list = createElement(env, parent, `[class]="'x'"`, {443 tag: 'amp-list',444 insertQuerySelectorAttr: useQuerySelector,445 });446 const child = createElement(env, list, '[text]="2+2"', {447 tag: 'h1',448 insertQuerySelectorAttr: useQuerySelector,449 });450 const cousin = createElement(env, uncle, '[text]="3+3"', {451 tag: 'span',452 insertQuerySelectorAttr: useQuerySelector,453 });454 expect(bind.numberOfBindings()).to.equal(0);455 return onBindReadyAndSetState(env, bind, {}).then(() => {456 // Children of amp-list should be skipped.457 expect(child.textContent).to.equal('');458 // But cousins and the amp-list itself shouldn't be skipped.459 expect(list.className).to.equal('x');460 expect(cousin.textContent).to.equal('6');461 });462 });463 it('should not update attribute for non-primitive new values', () => {464 const list = createElement(env, container, `[src]="x"`, {465 tag: 'amp-list',466 insertQuerySelectorAttr: useQuerySelector,467 });468 list.setAttribute('src', 'https://foo.example/data.json');469 return onBindReadyAndSetState(env, bind, {470 x: [1, 2, 3],471 }).then(() => {472 expect(list.getAttribute('src')).to.equal(473 'https://foo.example/data.json'474 );475 });476 });477 it('should scan fixed layer for bindings', () => {478 // Mimic FixedLayer by creating a sibling <body> element.479 const doc = env.win.document;480 const pseudoFixedLayer = doc.body.cloneNode(false);481 doc.documentElement.appendChild(pseudoFixedLayer);482 // Make sure that the sibling <body> is scanned for bindings.483 createElement(env, pseudoFixedLayer, '[text]="1+1"', {484 insertQuerySelectorAttr: useQuerySelector,485 });486 return onBindReady(env, bind).then(() => {487 expect(bind.numberOfBindings()).to.equal(1);488 });489 });490 it('should support data-amp-bind-* syntax', () => {491 const element = createElement(492 env,493 container,494 'data-amp-bind-text="1+1"',495 {insertQuerySelectorAttr: useQuerySelector}496 );497 expect(bind.numberOfBindings()).to.equal(0);498 expect(element.textContent).to.equal('');499 return onBindReadyAndSetState(env, bind, {}).then(() => {500 expect(bind.numberOfBindings()).to.equal(1);501 expect(element.textContent).to.equal('2');502 });503 });504 it('should prefer [foo] over data-amp-bind-foo', () => {505 const element = createElement(506 env,507 container,508 '[text]="1+1" data-amp-bind-text="2+2"',509 {insertQuerySelectorAttr: useQuerySelector}510 );511 expect(bind.numberOfBindings()).to.equal(0);512 expect(element.textContent).to.equal('');513 return onBindReadyAndSetState(env, bind, {}).then(() => {514 expect(bind.numberOfBindings()).to.equal(1);515 expect(element.textContent).to.equal('2');516 });517 });518 if (!useQuerySelector) {519 it('should call createTreeWalker() with all params', () => {520 const spy = env.sandbox.spy(env.win.document, 'createTreeWalker');521 createElement(env, container, '[text]="1+1"', {522 insertQuerySelectorAttr: useQuerySelector,523 });524 return onBindReady(env, bind).then(() => {525 // createTreeWalker() on IE does not support optional arguments.526 expect(spy.callCount).to.equal(1);527 expect(spy.firstCall.args.length).to.equal(4);528 });529 });530 }531 it('should have same state after removing + re-adding a subtree', () => {532 for (let i = 0; i < 5; i++) {533 createElement(env, container, '[text]="1+1"', {534 insertQuerySelectorAttr: useQuerySelector,535 });536 }537 expect(bind.numberOfBindings()).to.equal(0);538 return onBindReady(env, bind)539 .then(() => {540 expect(bind.numberOfBindings()).to.equal(5);541 return bind.removeBindingsForNodes_([container]);542 })543 .then(() => {544 expect(bind.numberOfBindings()).to.equal(0);545 return bind.addBindingsForNodes_([container]);546 })547 .then(() => {548 expect(bind.numberOfBindings()).to.equal(5);549 });550 });551 it('should dynamically detect new bindings under dynamic tags', () => {552 const doc = env.win.document;553 const dynamicTag = doc.createElement('div');554 container.appendChild(dynamicTag);555 return onBindReady(env, bind)556 .then(() => {557 expect(bind.numberOfBindings()).to.equal(0);558 const element = createElement(env, container, '[text]="1+1"', {559 insertQuerySelectorAttr: useQuerySelector,560 });561 dynamicTag.appendChild(element);562 dynamicTag.dispatchEvent(563 new Event(AmpEvents.DOM_UPDATE, {bubbles: true})564 );565 return waitForEvent(env, BindEvents.RESCAN_TEMPLATE);566 })567 .then(() => {568 expect(bind.numberOfBindings()).to.equal(1);569 });570 });571 it('should NOT apply expressions on first load', () => {572 const element = createElement(env, container, '[text]="1+1"', {573 insertQuerySelectorAttr: useQuerySelector,574 });575 expect(element.textContent).to.equal('');576 return onBindReady(env, bind).then(() => {577 expect(element.textContent).to.equal('');578 });579 });580 it('should verify class bindings in dev mode', () => {581 window.__AMP_MODE = {582 development: true,583 test: true,584 };585 createElement(env, container, '[class]="\'foo\'" class="foo"', {586 insertQuerySelectorAttr: useQuerySelector,587 });588 createElement(env, container, '[class]="\'foo\'" class=" foo "', {589 insertQuerySelectorAttr: useQuerySelector,590 });591 createElement(env, container, '[class]="\'\'"', {592 insertQuerySelectorAttr: useQuerySelector,593 });594 createElement(env, container, '[class]="\'bar\'" class="qux"', {595 insertQuerySelectorAttr: useQuerySelector,596 });597 // Error598 const warnSpy = env.sandbox.spy(user(), 'warn');599 return onBindReady(env, bind).then(() => {600 expect(warnSpy).to.be.calledOnce;601 expect(warnSpy).calledWithMatch('amp-bind', /\[class\]/);602 });603 });604 it('should verify string attribute bindings in dev mode', () => {605 window.__AMP_MODE = {606 development: true,607 test: true,608 };609 // Only the initial value for [a] binding does not match.610 createElement(611 env,612 container,613 '[text]="\'a\'" [class]="\'b\'" class="b"',614 {insertQuerySelectorAttr: useQuerySelector}615 );616 const warnSpy = env.sandbox.spy(user(), 'warn');617 return onBindReady(env, bind).then(() => {618 expect(warnSpy).to.be.calledOnce;619 expect(warnSpy).calledWithMatch('amp-bind', /\[text\]/);620 });621 });622 it('should verify boolean attribute bindings in dev mode', () => {623 window.__AMP_MODE = {624 development: true,625 test: true,626 };627 createElement(env, container, '[disabled]="true" disabled', {628 tag: 'button',629 insertQuerySelectorAttr: useQuerySelector,630 });631 createElement(env, container, '[disabled]="false"', {632 tag: 'button',633 insertQuerySelectorAttr: useQuerySelector,634 });635 createElement(env, container, '[disabled]="true"', {636 tag: 'button',637 insertQuerySelectorAttr: useQuerySelector,638 });639 // Mismatch.640 const warnSpy = env.sandbox.spy(user(), 'warn');641 return onBindReady(env, bind).then(() => {642 expect(warnSpy).to.be.calledOnce;643 expect(warnSpy).calledWithMatch('amp-bind', /\[disabled\]/);644 });645 });646 it('should skip digest if specified in setState()', () => {647 const element = createElement(env, container, '[text]="1+1"', {648 insertQuerySelectorAttr: useQuerySelector,649 });650 expect(element.textContent).to.equal('');651 return onBindReady(env, bind).then(() => {652 bind.setState({}, /* opt_skipDigest */ true);653 env.flushVsync();654 expect(element.textContent).to.equal('');655 });656 });657 it('should support binding to string attributes', () => {658 const element = createElement(env, container, '[text]="1+1"', {659 insertQuerySelectorAttr: useQuerySelector,660 });661 expect(element.textContent).to.equal('');662 return onBindReadyAndSetState(env, bind, {}).then(() => {663 expect(element.textContent).to.equal('2');664 });665 });666 it('should support binding to boolean attributes', () => {667 const element = createElement(668 env,669 container,670 '[checked]="true" [disabled]="false" disabled',671 {672 tag: 'input',673 insertQuerySelectorAttr: useQuerySelector,674 }675 );676 expect(element.getAttribute('checked')).to.equal(null);677 expect(element.getAttribute('disabled')).to.equal('');678 return onBindReadyAndSetState(env, bind, {}).then(() => {679 expect(element.getAttribute('checked')).to.equal('');680 expect(element.getAttribute('disabled')).to.equal(null);681 });682 });683 it('should update values first, then attributes', () => {684 const spy = env.sandbox.spy();685 const element = createElement(env, container, '[value]="foo"', {686 tag: 'input',687 insertQuerySelectorAttr: useQuerySelector,688 });689 env.sandbox.stub(element, 'value').set(spy);690 env.sandbox.stub(element, 'setAttribute').callsFake(spy);691 return onBindReadyAndSetState(env, bind, {'foo': '2'}).then(() => {692 // Note: This tests a workaround for a browser bug. There is nothing693 // about the element itself we can verify. Only the order of operations694 // matters.695 expect(spy.firstCall).to.be.calledWithExactly('2');696 expect(spy.secondCall).to.be.calledWithExactly('value', '2');697 });698 });699 it('should update properties for empty strings', function* () {700 const element = createElement(env, container, '[value]="foo"', {701 tag: 'input',702 insertQuerySelectorAttr: useQuerySelector,703 });704 yield onBindReadyAndSetState(env, bind, {'foo': 'bar'});705 expect(element.value).to.equal('bar');706 yield onBindReadyAndSetState(env, bind, {'foo': ''});707 expect(element.value).to.equal('');708 });709 it('should support binding to Node.textContent', () => {710 const element = createElement(env, container, '[text]="\'abc\'"', {711 insertQuerySelectorAttr: useQuerySelector,712 });713 expect(element.textContent).to.equal('');714 return onBindReadyAndSetState(env, bind, {}).then(() => {715 expect(element.textContent).to.equal('abc');716 });717 });718 it('should set value for [text] in <textarea>', () => {719 const element = createElement(env, container, '[text]="\'abc\'"', {720 tag: 'textarea',721 insertQuerySelectorAttr: useQuerySelector,722 });723 expect(element.textContent).to.equal('');724 expect(element.value).to.equal('');725 return onBindReadyAndSetState(env, bind, {}).then(() => {726 expect(element.textContent).to.equal('');727 expect(element.value).to.equal('abc');728 });729 });730 it('should set textContent for [defaultText] in <textarea>', () => {731 const element = createElement(732 env,733 container,734 '[defaultText]="\'abc\'"',735 {736 tag: 'textarea',737 insertQuerySelectorAttr: useQuerySelector,738 }739 );740 expect(element.textContent).to.equal('');741 expect(element.value).to.equal('');742 // Setting `textContent` will also update `value` before interaction.743 element.value = '123';744 return onBindReadyAndSetState(env, bind, {}).then(() => {745 expect(element.textContent).to.equal('abc');746 expect(element.value).to.equal('123');747 });748 });749 it('should update document title for <title> elements', () => {750 const element = createElement(env, container, '[text]="\'bar\'"', {751 tag: 'title',752 amp: false,753 insertInHead: true,754 insertQuerySelectorAttr: useQuerySelector,755 });756 element.textContent = 'foo';757 env.win.document.title = 'foo';758 return onBindReadyAndSetState(env, bind, {}).then(() => {759 expect(element.textContent).to.equal('bar');760 expect(env.win.document.title).to.equal('bar');761 });762 });763 it('should not update document title for <title> elements in body', () => {764 // Add a <title> element to <head> because if we don't, setting765 // `textContent` on a <title> element in the <body> will strangely update766 // `document.title`.767 const title = env.win.document.createElement('title');768 title.textContent = 'foo';769 env.win.document.head.appendChild(title);770 // Add <title [text]="'bar'"> to <body>.771 createElement(env, container, '[text]="\'bar\'"', {772 tag: 'title',773 insertQuerySelectorAttr: useQuerySelector,774 });775 return onBindReadyAndSetState(env, bind, {}).then(() => {776 expect(env.win.document.title).to.equal('foo');777 });778 });779 it('should support binding to CSS classes with strings', () => {780 const element = createElement(env, container, '[class]="[\'abc\']"', {781 insertQuerySelectorAttr: useQuerySelector,782 });783 expect(toArray(element.classList)).to.deep.equal([]);784 return onBindReadyAndSetState(env, bind, {}).then(() => {785 expect(toArray(element.classList)).to.deep.equal(['abc']);786 });787 });788 it('should support binding to CSS classes with arrays', () => {789 const element = createElement(790 env,791 container,792 "[class]=\"['a','b']\"",793 {insertQuerySelectorAttr: useQuerySelector}794 );795 expect(toArray(element.classList)).to.deep.equal([]);796 return onBindReadyAndSetState(env, bind, {}).then(() => {797 expect(toArray(element.classList)).to.deep.equal(['a', 'b']);798 });799 });800 it('should support binding to CSS classes with a null value', () => {801 const element = createElement(env, container, '[class]="null"', {802 insertQuerySelectorAttr: useQuerySelector,803 });804 expect(toArray(element.classList)).to.deep.equal([]);805 return onBindReadyAndSetState(env, bind, {}).then(() => {806 expect(toArray(element.classList)).to.deep.equal([]);807 });808 });809 it('should support binding to CSS classes for svg tags', () => {810 const element = createElement(env, container, '[class]="[\'abc\']"', {811 tag: 'svg',812 insertQuerySelectorAttr: useQuerySelector,813 });814 expect(toArray(element.classList)).to.deep.equal([]);815 return onBindReadyAndSetState(env, bind, {}).then(() => {816 expect(toArray(element.classList)).to.deep.equal(['abc']);817 });818 });819 it('supports binding to CSS classes for svg tags with a null value', () => {820 const element = createElement(env, container, '[class]="null"', {821 tag: 'svg',822 insertQuerySelectorAttr: useQuerySelector,823 });824 expect(toArray(element.classList)).to.deep.equal([]);825 return onBindReadyAndSetState(env, bind, {}).then(() => {826 expect(toArray(element.classList)).to.deep.equal([]);827 });828 });829 it('should support handling actions with invoke()', () => {830 env.sandbox.stub(bind, 'setStateWithExpression');831 env.sandbox.stub(bind, 'pushStateWithExpression');832 const invocation = {833 method: 'setState',834 args: {835 [RAW_OBJECT_ARGS_KEY]: '{foo: bar}',836 },837 event: {838 detail: {bar: 123},839 },840 sequenceId: 0,841 };842 bind.invoke(invocation);843 expect(bind.setStateWithExpression).to.be.calledOnce;844 expect(bind.setStateWithExpression).to.be.calledWithExactly(845 '{foo: bar}',846 env.sandbox.match({event: {bar: 123}})847 );848 invocation.method = 'pushState';849 invocation.sequenceId++;850 bind.invoke(invocation);851 expect(bind.pushStateWithExpression).to.be.calledOnce;852 expect(bind.pushStateWithExpression).to.be.calledWithExactly(853 '{foo: bar}',854 env.sandbox.match({event: {bar: 123}})855 );856 });857 // TODO(choumx, #16721): Causes browser crash for some reason.858 it.skip('should only allow one action per event in invoke()', () => {859 env.sandbox.stub(bind, 'setStateWithExpression');860 const userError = env.sandbox.stub(user(), 'error');861 const invocation = {862 method: 'setState',863 args: {864 [RAW_OBJECT_ARGS_KEY]: '{foo: bar}',865 },866 event: {867 detail: {bar: 123},868 },869 sequenceId: 0,870 };871 bind.invoke(invocation);872 expect(bind.setStateWithExpression).to.be.calledOnce;873 expect(bind.setStateWithExpression).to.be.calledWithExactly(874 '{foo: bar}',875 env.sandbox.match({event: {bar: 123}})876 );877 // Second invocation with the same sequenceId should fail.878 bind.invoke(invocation);879 expect(bind.setStateWithExpression).to.be.calledOnce;880 expect(userError).to.be.calledWith(881 'amp-bind',882 'One state action allowed per event.'883 );884 // Invocation with the same sequenceid will be allowed after 5 seconds,885 // which is how long it takes for stored sequenceIds to be purged.886 clock.tick(5000);887 bind.invoke(invocation);888 expect(bind.setStateWithExpression).to.be.calledTwice;889 });890 it('should support parsing exprs in setStateWithExpression()', () => {891 const element = createElement(env, container, '[text]="onePlusOne"', {892 insertQuerySelectorAttr: useQuerySelector,893 });894 expect(element.textContent).to.equal('');895 const promise = onBindReadyAndSetStateWithExpression(896 env,897 bind,898 '{"onePlusOne": one + one}',899 {one: 1}900 );901 return promise.then(() => {902 expect(element.textContent).to.equal('2');903 });904 });905 describe('history', () => {906 beforeEach(() => {907 env.sandbox.spy(history, 'replace');908 env.sandbox.spy(history, 'push');909 env.sandbox.stub(viewer, 'isEmbedded').returns(true);910 });911 describe('with untrusted viewer', () => {912 it('should not replace history on AMP.setState()', () => {913 const promise = onBindReadyAndSetStateWithExpression(914 env,915 bind,916 '{"onePlusOne": one + one}',917 {one: 1}918 );919 return promise.then(() => {920 // Shouldn't call replace() with null `data`.921 expect(history.replace).to.not.be.called;922 });923 });924 it('should push history (no data) on AMP.pushState()', () => {925 const promise = bind.pushStateWithExpression(926 '{"foo": "bar"}',927 {}928 );929 return promise.then(() => {930 expect(history.push).to.be.called;931 // `data` param should be null on untrusted viewers.932 expect(history.push).to.be.calledWith(933 env.sandbox.match.func,934 null935 );936 });937 });938 });939 describe('with trusted viewer', () => {940 beforeEach(() => {941 env.sandbox942 .stub(viewer, 'isTrustedViewer')943 .returns(Promise.resolve(true));944 });945 it('should replace history on AMP.setState()', () => {946 const promise = onBindReadyAndSetStateWithExpression(947 env,948 bind,949 '{"onePlusOne": one + one}',950 {one: 1}951 );952 return promise.then(() => {953 expect(history.replace).calledOnce;954 // `data` param should exist on trusted viewers.955 expect(history.replace).calledWith({956 data: {'amp-bind': {'onePlusOne': 2}},957 title: '',958 });959 });960 });961 it('should push history on AMP.pushState()', () => {962 const promise = bind.pushStateWithExpression(963 '{"foo": "bar"}',964 {}965 );966 return promise.then(() => {967 expect(history.push).calledOnce;968 // `data` param should exist on trusted viewers.969 expect(history.push).calledWith(env.sandbox.match.func, {970 data: {'amp-bind': {foo: 'bar'}},971 title: '',972 });973 });974 });975 });976 });977 it('should support setting object state in setStateWithObject()', () => {978 const element = createElement(979 env,980 container,981 '[text]="mystate.mykey"',982 {insertQuerySelectorAttr: useQuerySelector}983 );984 expect(element.textContent).to.equal('');985 const promise = onBindReadyAndSetStateWithObject(env, bind, {986 mystate: {mykey: 'myval'},987 });988 return promise.then(() => {989 expect(element.textContent).to.equal('myval');990 });991 });992 it('should support getting state with getState()', () => {993 const promise = onBindReadyAndSetStateWithObject(env, bind, {994 mystate: {mykey: 'myval'},995 });996 return promise.then(() => {997 return onBindReadyAndGetState(env, bind, 'mystate.mykey').then(998 (result) => {999 expect(result).to.equal('myval');1000 }1001 );1002 });1003 });1004 describe('getStateAsync', () => {1005 it('should reject if there is no associated "amp-state"', async () => {1006 await onBindReadyAndSetState(env, bind, {1007 mystate: {mykey: 'myval'},1008 });1009 const state = bind.getStateAsync('mystate.mykey');1010 return expect(state).to.eventually.rejectedWith(/#mystate/);1011 });1012 it('should not wait if the still-loading state is irrelevant', async () => {1013 await onBindReadyAndSetState(env, bind, {1014 mystate: {myKey: 'myval'},1015 });1016 addAmpState(env, container, 'mystate', Promise.resolve());1017 addAmpState(1018 // never resolves1019 env,1020 container,1021 'irrelevant',1022 new Promise((unused) => {})1023 );1024 const state = await bind.getStateAsync('mystate.myKey');1025 expect(state).to.equal('myval');1026 });1027 it('should wait for a relevant key', async () => {1028 const {promise, resolve} = new Deferred();1029 addAmpState(env, container, 'mystate', promise);1030 await onBindReady(env, bind);1031 const statePromise = bind.getStateAsync('mystate.mykey');1032 await bind.setState({mystate: {mykey: 'myval'}}).then(resolve);1033 expect(await statePromise).to.equal('myval');1034 });1035 it('should stop waiting for a key if its fetch rejects', async () => {1036 const {promise, reject} = new Deferred();1037 addAmpState(env, container, 'mystate', promise);1038 await onBindReady(env, bind);1039 const statePromise = bind.getStateAsync('mystate.mykey');1040 reject();1041 expect(await statePromise).to.equal(undefined);1042 });1043 });1044 it('should support pushStateWithExpression()', () => {1045 env.sandbox.spy(history, 'push');1046 const element = createElement(env, container, '[text]="foo"', {1047 insertQuerySelectorAttr: useQuerySelector,1048 });1049 expect(element.textContent).to.equal('');1050 const promise = bind.pushStateWithExpression('{"foo": "bar"}', {});1051 return promise1052 .then(() => {1053 env.flushVsync();1054 expect(element.textContent).to.equal('bar');1055 expect(history.push).calledOnce;1056 // Pop callback should restore `foo` to original value (null).1057 const onPopCallback = history.push.firstCall.args[0];1058 return onPopCallback();1059 })1060 .then(() => {1061 expect(element.textContent).to.equal('null');1062 });1063 });1064 it('pushStateWithExpression() should work with nested objects', () => {1065 env.sandbox.spy(history, 'push');1066 const element = createElement(env, container, '[text]="foo.bar"', {1067 insertQuerySelectorAttr: useQuerySelector,1068 });1069 expect(element.textContent).to.equal('');1070 return bind1071 .pushStateWithExpression('{foo: {bar: 0}}', {})1072 .then(() => {1073 env.flushVsync();1074 expect(element.textContent).to.equal('0');1075 return bind.pushStateWithExpression('{foo: {bar: 1}}', {});1076 })1077 .then(() => {1078 env.flushVsync();1079 expect(element.textContent).to.equal('1');1080 expect(history.push).calledTwice;1081 // Pop callback should restore `foo.bar` to second pushed value (0).1082 const onPopCallback = history.push.secondCall.args[0];1083 return onPopCallback();1084 })1085 .then(() => {1086 expect(element.textContent).to.equal('0');1087 });1088 });1089 it('should ignore <amp-state> updates if specified in setState()', () => {1090 const element = createElement(env, container, '[src]="foo"', {1091 tag: 'amp-state',1092 insertQuerySelectorAttr: useQuerySelector,1093 });1094 // Makes whenUpgradedToCustomElement() resolve immediately.1095 element.createdCallback = () => {};1096 element.getImpl = () =>1097 Promise.resolve({parseAndUpdate: env.sandbox.spy()});1098 expect(element.getAttribute('src')).to.be.null;1099 const promise = onBindReadyAndSetState(1100 env,1101 bind,1102 {foo: '/foo'},1103 /* opt_isAmpStateMutation */ true1104 );1105 return promise.then(() => {1106 // Should _not_ be updated if `opt_isAmpStateMutation` is true.1107 expect(element.getAttribute('src')).to.be.null;1108 });1109 });1110 it('should support NOT override internal AMP CSS classes', () => {1111 const element = createElement(env, container, '[class]="[\'abc\']"', {1112 amp: true,1113 insertQuerySelectorAttr: useQuerySelector,1114 });1115 expect(toArray(element.classList)).to.deep.equal([1116 'i-amphtml-foo',1117 '-amp-foo',1118 'amp-foo',1119 ]);1120 return onBindReadyAndSetState(env, bind, {}).then(() => {1121 expect(toArray(element.classList)).to.deep.equal([1122 'i-amphtml-foo',1123 '-amp-foo',1124 'amp-foo',1125 'abc',1126 ]);1127 });1128 });1129 it('should call mutatedAttributesCallback on AMP elements', () => {1130 const binding =1131 '[text]="1+1" [value]="\'4\'" value="4" ' +1132 'checked [checked]="false" [disabled]="true" [multiple]="false"';1133 const element = createElement(env, container, binding, {1134 tag: 'input',1135 amp: true,1136 insertQuerySelectorAttr: useQuerySelector,1137 });1138 const spy = env.sandbox.spy(element, 'mutatedAttributesCallback');1139 return onBindReadyAndSetState(env, bind, {}).then(() => {1140 expect(spy).calledWithMatch({1141 checked: false,1142 disabled: true,1143 });1144 // Callback shouldn't include global attributes (text, class) or those1145 // whose values haven't changed.1146 expect(1147 spy.neverCalledWithMatch({1148 text: 2,1149 value: 4,1150 multiple: false,1151 })1152 ).to.be.true;1153 });1154 });1155 it('should support scope variable references', () => {1156 const binding = '[text]="foo + bar + baz.qux.join(\',\')"';1157 const element = createElement(env, container, binding, {1158 insertQuerySelectorAttr: useQuerySelector,1159 });1160 expect(element.textContent).to.equal('');1161 return onBindReadyAndSetState(env, bind, {1162 foo: 'abc',1163 bar: 123,1164 baz: {1165 qux: ['x', 'y', 'z'],1166 },1167 }).then(() => {1168 expect(element.textContent).to.equal('abc123x,y,z');1169 });1170 });1171 it('should NOT mutate elements if expression result is unchanged', () => {1172 const binding =1173 '[value]="foo" [class]="\'abc\'" [text]="\'a\'+\'b\'"';1174 const element = createElement(env, container, binding, {1175 tag: 'input',1176 insertQuerySelectorAttr: useQuerySelector,1177 });1178 element.mutatedAttributesCallback = env.sandbox.spy();1179 return onBindReadyAndSetState(env, bind, {foo: {bar: [1]}})1180 .then(() => {1181 expect(element.textContent.length).to.not.equal(0);1182 expect(element.classList.length).to.not.equal(0);1183 expect(element.attributes.length).to.not.equal(0);1184 expect(element.mutatedAttributesCallback).to.be.calledOnce;1185 element.textContent = '';1186 element.className = '';1187 while (element.attributes.length > 0) {1188 element.removeAttribute(element.attributes[0].name);1189 }1190 return onBindReadyAndSetState(env, bind, {});1191 })1192 .then(() => {1193 // Attributes should not be updated and mutatedAttributesCallback1194 // should not be called since the expression results haven't changed.1195 expect(element.textContent).to.equal('');1196 expect(element.className).to.equal('');1197 expect(element.attributes.length).to.equal(0);1198 expect(element.mutatedAttributesCallback).to.be.calledOnce;1199 });1200 });1201 it('should NOT evaluate expression if binding is NOT allowed', () => {1202 const element = createElement(1203 env,1204 container,1205 '[invalidBinding]="1+1"',1206 {insertQuerySelectorAttr: useQuerySelector}1207 );1208 return onBindReadyAndSetState(env, bind, {}).then(() => {1209 expect(element.getAttribute('invalidbinding')).to.be.null;1210 });1211 });1212 it('should rewrite attribute values regardless of result type', () => {1213 const withString = createElement(env, container, '[href]="foo"', {1214 tag: 'a',1215 insertQuerySelectorAttr: useQuerySelector,1216 });1217 const withArray = createElement(env, container, '[href]="bar"', {1218 tag: 'a',1219 insertQuerySelectorAttr: useQuerySelector,1220 });1221 return onBindReadyAndSetState(env, bind, {1222 foo: '?__amp_source_origin',1223 bar: ['?__amp_source_origin'],1224 }).then(() => {1225 expect(withString.getAttribute('href')).to.equal(null);1226 expect(withArray.getAttribute('href')).to.equal(null);1227 });1228 });1229 it('should stop scanning once max number of bindings is reached', () => {1230 bind.setMaxNumberOfBindingsForTesting(2);1231 const errorStub = env.sandbox.stub(dev(), 'expectedError');1232 const foo = createElement(env, container, '[text]="foo"', {1233 insertQuerySelectorAttr: useQuerySelector,1234 });1235 const bar = createElement(1236 env,1237 container,1238 '[text]="bar" [class]="baz"',1239 {insertQuerySelectorAttr: useQuerySelector}1240 );1241 const qux = createElement(env, container, '[text]="qux"', {1242 insertQuerySelectorAttr: useQuerySelector,1243 });1244 return onBindReadyAndSetState(env, bind, {1245 foo: 1,1246 bar: 2,1247 baz: 3,1248 qux: 4,1249 }).then(() => {1250 expect(foo.textContent).to.equal('1');1251 expect(bar.textContent).to.equal('2');1252 // Max number of bindings exceeded with [baz].1253 expect(bar.className).to.be.equal('');1254 expect(qux.textContent).to.be.equal('');1255 expect(errorStub).to.have.been.calledWith(1256 'amp-bind',1257 env.sandbox.match(/Maximum number of bindings reached/)1258 );1259 });1260 });1261 it('should update premutate keys that are overridable', () => {1262 bind.addOverridableKey('foo');1263 bind.addOverridableKey('bar');1264 const foo = createElement(env, container, '[text]="foo"', {1265 insertQuerySelectorAttr: useQuerySelector,1266 });1267 const bar = createElement(env, container, '[text]="bar"', {1268 insertQuerySelectorAttr: useQuerySelector,1269 });1270 const baz = createElement(env, container, '[text]="baz"', {1271 insertQuerySelectorAttr: useQuerySelector,1272 });1273 const qux = createElement(env, container, '[text]="qux"', {1274 insertQuerySelectorAttr: useQuerySelector,1275 });1276 return onBindReadyAndSetState(env, bind, {1277 foo: 1,1278 bar: 2,1279 baz: 3,1280 qux: 4,1281 }).then(() => {1282 return viewer1283 .receiveMessage('premutate', {1284 state: {1285 foo: 'foo',1286 bar: 'bar',1287 baz: 'baz',1288 qux: 'qux',1289 },1290 })1291 .then(() => {1292 expect(foo.textContent).to.equal('foo');1293 expect(bar.textContent).to.equal('bar');1294 expect(baz.textContent).to.be.equal('3');1295 expect(qux.textContent).to.be.equal('4');1296 });1297 });1298 });1299 describe('rescan()', () => {1300 let toRemove;1301 let toAdd;1302 beforeEach(async () => {1303 toRemove = createElement(env, container, '[text]="foo"', {1304 insertQuerySelectorAttr: useQuerySelector,1305 });1306 // New elements to rescan() don't need to be attached to DOM.1307 toAdd = createElement(env, /* container */ null, '[text]="1+1"', {1308 insertQuerySelectorAttr: useQuerySelector,1309 });1310 });1311 it('{update: true, fast: true}', async () => {1312 const options = {update: true, fast: true};1313 await onBindReadyAndSetState(env, bind, {foo: 'foo'});1314 expect(toRemove.textContent).to.equal('foo');1315 // [i-amphtml-binding] necessary in {fast: true}.1316 toAdd.setAttribute('i-amphtml-binding', '');1317 // `toAdd` should be scanned and updated.1318 await onBindReadyAndRescan(env, bind, [toAdd], [toRemove], options);1319 expect(toAdd.textContent).to.equal('2');1320 await onBindReadyAndSetState(env, bind, {foo: 'bar'});1321 // The `toRemove` element's bindings should have been removed.1322 expect(toRemove.textContent).to.not.equal('bar');1323 });1324 it('{update: true, fast: false}', async () => {1325 const options = {update: true, fast: false};1326 await onBindReadyAndSetState(env, bind, {foo: 'foo'});1327 expect(toRemove.textContent).to.equal('foo');1328 // `toAdd` should be scanned and updated.1329 await onBindReadyAndRescan(env, bind, [toAdd], [toRemove], options);1330 expect(toAdd.textContent).to.equal('2');1331 await onBindReadyAndSetState(env, bind, {foo: 'bar'});1332 // The `toRemove` element's bindings should have been removed.1333 expect(toRemove.textContent).to.not.equal('bar');1334 });1335 it('{update: false, fast: true}', async () => {1336 const options = {update: false, fast: true};1337 await onBindReadyAndSetState(env, bind, {foo: 'foo'});1338 expect(toRemove.textContent).to.equal('foo');1339 // [i-amphtml-binding] necessary in {fast: true}.1340 toAdd.setAttribute('i-amphtml-binding', '');1341 // `toAdd` should be scanned but not updated.1342 await onBindReadyAndRescan(env, bind, [toAdd], [toRemove], options);1343 expect(toAdd.textContent).to.equal('');1344 await onBindReadyAndSetState(env, bind, {foo: 'bar'});1345 // Now that `toAdd` is scanned, it should be updated on setState().1346 expect(toAdd.textContent).to.equal('2');1347 // The `toRemove` element's bindings should have been removed.1348 expect(toRemove.textContent).to.not.equal('bar');1349 });1350 it('{update: false, fast: false}', async () => {1351 const options = {update: false, fast: false};1352 await onBindReadyAndSetState(env, bind, {foo: 'foo'});1353 expect(toRemove.textContent).to.equal('foo');1354 // `toAdd` should be scanned but not updated.1355 await onBindReadyAndRescan(env, bind, [toAdd], [toRemove], options);1356 expect(toAdd.textContent).to.equal('');1357 await onBindReadyAndSetState(env, bind, {foo: 'bar'});1358 // Now that `toAdd` is scanned, it should be updated on setState().1359 expect(toAdd.textContent).to.equal('2');1360 // The `toRemove` element's bindings should have been removed.1361 expect(toRemove.textContent).to.not.equal('bar');1362 });1363 it('{update: "evaluate"}', async () => {1364 const options = {update: 'evaluate', fast: false};1365 toAdd = createElement(env, /* container */ null, '[text]="x"', {1366 insertQuerySelectorAttr: useQuerySelector,1367 });1368 // `toRemove` is updated normally before removal.1369 await onBindReadyAndSetState(env, bind, {foo: 'foo', x: '1'});1370 expect(toRemove.textContent).to.equal('foo');1371 // `toAdd` should be scanned but not updated. With {update: 'evaluate'},1372 // its expression "x" is now cached on the element.1373 await onBindReadyAndRescan(env, bind, [toAdd], [toRemove], options);1374 expect(toAdd.textContent).to.equal('');1375 await onBindReadyAndSetState(env, bind, {foo: 'bar', x: '1'});1376 // `toAdd` should _not_ update since the value of its expression "x"1377 // hasn't changed (due to caching).1378 expect(toAdd.textContent).to.equal('');1379 // `toRemove`'s bindings have been removed and remains unchanged.1380 expect(toRemove.textContent).to.equal('foo');1381 await onBindReadyAndSetState(env, bind, {x: '2'});1382 // toAdd changes now that its expression "x"'s value has changed.1383 expect(toAdd.textContent).to.equal('2');1384 });1385 });1386 describe('AmpEvents.FORM_VALUE_CHANGE', () => {1387 it('should dispatch FORM_VALUE_CHANGE on <input [value]> changes', () => {1388 const element = createElement(env, container, '[value]="foo"', {1389 tag: 'input',1390 insertQuerySelectorAttr: useQuerySelector,1391 });1392 const spy = env.sandbox.spy(element, 'dispatchEvent');1393 return onBindReadyAndSetState(env, bind, {foo: 'bar'}).then(() => {1394 expect(spy).to.have.been.calledOnce;1395 expect(spy).calledWithMatch(FORM_VALUE_CHANGE_EVENT_ARGUMENTS);1396 });1397 });1398 it('should dispatch FORM_VALUE_CHANGE on <input [checked]> changes', () => {1399 const element = createElement(env, container, '[checked]="foo"', {1400 tag: 'input',1401 insertQuerySelectorAttr: useQuerySelector,1402 });1403 const spy = env.sandbox.spy(element, 'dispatchEvent');1404 return onBindReadyAndSetState(env, bind, {foo: 'checked'}).then(1405 () => {1406 expect(spy).to.have.been.calledOnce;1407 expect(spy).calledWithMatch(FORM_VALUE_CHANGE_EVENT_ARGUMENTS);1408 }1409 );1410 });1411 it('should dispatch FORM_VALUE_CHANGE at parent <select> on <option [selected]> changes', () => {1412 const select = env.win.document.createElement('select');1413 const optgroup = createElement(env, select, '', {1414 tag: 'optgroup',1415 });1416 createElement(env, optgroup, '[selected]="foo"', {1417 tag: 'option',1418 insertQuerySelectorAttr: useQuerySelector,1419 });1420 container.appendChild(select);1421 const spy = env.sandbox.spy(select, 'dispatchEvent');1422 return onBindReadyAndSetState(env, bind, {foo: 'selected'}).then(1423 () => {1424 expect(spy).to.have.been.calledOnce;1425 expect(spy).calledWithMatch({1426 type: AmpEvents.FORM_VALUE_CHANGE,1427 bubbles: true,1428 });1429 }1430 );1431 });1432 it('should dispatch FORM_VALUE_CHANGE on <textarea [text]> changes', () => {1433 const element = createElement(env, container, '[text]="foo"', {1434 tag: 'textarea',1435 insertQuerySelectorAttr: useQuerySelector,1436 });1437 const spy = env.sandbox.spy(element, 'dispatchEvent');1438 return onBindReadyAndSetState(env, bind, {foo: 'bar'}).then(() => {1439 expect(spy).to.have.been.calledOnce;1440 expect(spy).calledWithMatch({1441 type: AmpEvents.FORM_VALUE_CHANGE,1442 bubbles: true,1443 });1444 });1445 });1446 it('should NOT dispatch FORM_VALUE_CHANGE on other attributes changes', () => {1447 const element = createElement(env, container, '[name]="foo"', {1448 tag: 'input',1449 insertQuerySelectorAttr: useQuerySelector,1450 });1451 const spy = env.sandbox.spy(element, 'dispatchEvent');1452 return onBindReadyAndSetState(env, bind, {foo: 'name'}).then(() => {1453 expect(spy).to.not.have.been.called;1454 });1455 });1456 it('should NOT dispatch FORM_VALUE_CHANGE on other element changes', () => {1457 const element = createElement(env, container, '[text]="foo"', {1458 tag: 'p',1459 insertQuerySelectorAttr: useQuerySelector,1460 });1461 const spy = env.sandbox.spy(element, 'dispatchEvent');1462 return onBindReadyAndSetState(env, bind, {foo: 'selected'}).then(1463 () => {1464 expect(spy).to.not.have.been.called;1465 }1466 );1467 });1468 });1469 }1470 );1471 }...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var tracetest = require('tracetest');2var query = tracetest.useQuerySelector('div');3console.log(query);4exports.useQuerySelector = function(selector) {5 return document.querySelector(selector);6};7{8 "scripts": {9 },10}11{ [Function: useQuerySelector] [Symbol(nodejs.util.promisify.custom)]: [Function] }

Full Screen

Using AI Code Generation

copy

Full Screen

1var trace = require('trace');2var tracetest = new trace.TraceTest();3tracetest.useQuerySelector();4var trace = require('trace');5var TraceTest = function() {};6TraceTest.prototype.useQuerySelector = function() {7 console.log("useQuerySelector");8};9exports.TraceTest = TraceTest;10var trace = require('trace');11var tracetest = new trace.TraceTest();12tracetest.useQuerySelector();13var trace = require('trace');14var TraceTest = function() {};15TraceTest.prototype.useQuerySelector = function() {16 console.log("useQuerySelector");17};18exports.TraceTest = TraceTest;19var trace = require('trace');20var tracetest = new trace.TraceTest();21tracetest.useQuerySelector();22var trace = require('trace');23var TraceTest = function() {};24TraceTest.prototype.useQuerySelector = function() {25 console.log("useQuerySelector");26};27exports.TraceTest = TraceTest;28var trace = require('trace');29var tracetest = new trace.TraceTest();30tracetest.useQuerySelector();31var trace = require('trace');32var TraceTest = function() {};33TraceTest.prototype.useQuerySelector = function() {34 console.log("useQuerySelector");35};

Full Screen

Using AI Code Generation

copy

Full Screen

1var tracetest = require('tracetest');2var trace = tracetest.trace;3var useQuerySelector = tracetest.useQuerySelector;4var traceResults = trace(useQuerySelector);5console.log(traceResults);6exports.useQuerySelector = function useQuerySelector() {7 var body = document.querySelector('body');8 var h1 = document.querySelector('h1');9 var p = document.querySelector('p');10 var a = document.querySelector('a');11 var img = document.querySelector('img');12 var span = document.querySelector('span');13 var input = document.querySelector('input');14 var button = document.querySelector('button');15 var div = document.querySelector('div');16}17exports.trace = function trace(fn) {18 var traceResults = [];19 var oldCall = Function.prototype.call;20 Function.prototype.call = function() {21 var functionName = this.name;22 var args = Array.prototype.slice.call(arguments);23 var element = args[0];24 var selector = args[1];25 var traceInfo = {26 };27 traceResults.push(traceInfo);28 return oldCall.apply(this, arguments);29 };30 fn();31 Function.prototype.call = oldCall;32 return traceResults;33}34[ { functionName: 'querySelector',35 selector: 'body' },36 { functionName: 'querySelector',37 selector: 'h1' },38 { functionName: 'querySelector',39 selector: 'p' },40 { functionName: 'querySelector',41 selector: 'a' },42 { functionName: 'querySelector',43 selector: 'img' },44 { functionName: 'querySelector',45 selector: 'span' },46 { functionName: 'querySelector',47 selector: 'input' },48 { functionName: 'querySelector',49 selector: 'button' },50 { functionName: 'querySelector',

Full Screen

Using AI Code Generation

copy

Full Screen

1var tracetest = require('tracetest');2tracetest.useQuerySelector('mySelector', 'myQuery');3var tracetest = require('tracetest');4tracetest.useQuerySelector('mySelector', 'myQuery');5var tracetest = require('tracetest');6tracetest.useQuerySelector('mySelector', 'myQuery');7var tracetest = require('tracetest');8tracetest.useQuerySelector('mySelector', 'myQuery');9var tracetest = require('tracetest');10tracetest.useQuerySelector('mySelector', 'myQuery');11var tracetest = require('tracetest');12tracetest.useQuerySelector('mySelector', 'myQuery');13var tracetest = require('tracetest');14tracetest.useQuerySelector('mySelector', 'myQuery');15var tracetest = require('tracetest');16tracetest.useQuerySelector('mySelector', 'myQuery');17var tracetest = require('tracetest');18tracetest.useQuerySelector('mySelector', 'myQuery');19var tracetest = require('tracetest');20tracetest.useQuerySelector('mySelector', 'myQuery');21var tracetest = require('tracetest');22tracetest.useQuerySelector('mySelector', 'myQuery');23var tracetest = require('tracetest');

Full Screen

Using AI Code Generation

copy

Full Screen

1const { useQuerySelector } = require('./tracetest.js');2const query = useQuerySelector('div');3console.log(query);4const useQuerySelector = (selector) => {5 const query = document.querySelector(selector);6 return query;7};8module.exports = { useQuerySelector };9const { useQuerySelector } = require('./tracetest.js');10const query = useQuerySelector('div');

Full Screen

Using AI Code Generation

copy

Full Screen

1var trace = require('trace');2var tracetest = new trace();3tracetest.useQuerySelector('div');4var trace = function() {5 this.useQuerySelector = function(selector) {6 return document.querySelector(selector);7 }8}9module.exports = trace;

Full Screen

Using AI Code Generation

copy

Full Screen

1var element = tracetest.useQuerySelector("#myElement");2var element2 = tracetest.useGetElementById("myElement");3var element3 = tracetest.useGetElementByTagName("myElement");4var element4 = tracetest.useGetElementByName("myElement");5var element5 = tracetest.useGetElementByClassName("myElement");6var element6 = tracetest.useGetElementsByTagName("myElement");7var element7 = tracetest.useGetElementsByClassName("myElement");8var element8 = tracetest.useGetElementsByName("myElement");9var element9 = tracetest.useGetElementsBySelector("#myElement");10var element = tracetest.useQuerySelector("#myElement");11var element2 = tracetest.useGetElementById("myElement");

Full Screen

Using AI Code Generation

copy

Full Screen

1var tracetest = require('tracetest');2var query = "SELECT * FROM traces WHERE id = 1";3var result = tracetest.useQuerySelector(query);4result;5var trace = require('trace');6var traceDB = trace.openDatabase('trace.db');7var useQuerySelector = function(query) {8 var result = traceDB.querySelector(query);9 return result;10};11exports.useQuerySelector = useQuerySelector;12var trace = {};13trace.openDatabase = function(dbName) {14 var traceDB = {};15 traceDB.querySelector = function(query) {16 var result = "SELECT * FROM traces WHERE id = 1";17 return result;18 };19 return traceDB;20};21module.exports = trace;22var tracetest = require('tracetest');23var query = "SELECT * FROM traces WHERE id = 1";24var result = tracetest.useQuerySelector(query);25result;26var trace = require('trace');27var traceDB = trace.openDatabase('trace.db');28var useQuerySelector = function(query) {29 var result = traceDB.querySelector(query);30 return result;31};32exports.useQuerySelector = useQuerySelector;33var trace = {};34trace.openDatabase = function(dbName) {35 var traceDB = {};36 traceDB.querySelector = function(query) {37 var result = "SELECT * FROM traces WHERE id = 1";38 return result;39 };40 return traceDB;41};42module.exports = trace;

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run tracetest automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful