/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/integration/test-amp-bind.js
364 строки
12 KB
Daniel Rozenberg
❄️ Fix flaky async integration tests for `amp-bind` (#40372)
14 авг 2025, 21:58
Не верифицирован
14 авг 2025, 21:58
a11700f
Код
Авторство
О чём код?
import {BrowserController} from '#testing/helpers/service'; import {poll as classicPoll} from '#testing/iframe'; const TIMEOUT = 2000; describes.sandboxed('amp-bind', {}, function () { this.timeout(TIMEOUT); // Helper that sets the poll timeout. function poll(desc, condition, onError) { return classicPoll(desc, condition, onError, 800); } describes.integration( 'basic', { body: ` <button on="tap:AMP.setState({t: 'after_text'})" id="changeText"></button> <button on="tap:AMP.setState({c: 'after_class'})" id="changeClass"></button> <p class="before_class" [class]="c" [text]="t">before_text</p> `, extensions: ['amp-bind'], }, (env) => { let browser; let doc; let text; beforeEach(() => { doc = env.win.document; text = doc.querySelector('p'); browser = new BrowserController(env.win); }); it('[text]', async () => { expect(text.textContent).to.equal('before_text'); await browser.wait(200); browser.click('#changeText'); await poll('[text]', () => text.textContent === 'after_text').should.be .fulfilled; }); it('[class]', async () => { expect(text.className).to.equal('before_class'); await browser.wait(200); browser.click('#changeClass'); await poll('[class]', () => text.className === 'after_class').should.be .fulfilled; }); } ); const BEFORE_IMG_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAABGdBTUEAALGPC/xhBQAAABtJREFUWAntwYEAAAAAw6D7Uw/hAtUAAAAAjgAQQAABWWUZmwAAAABJRU5ErkJggg=='; const AFTER_IMG_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAABGdBTUEAALGPC/xhBQAAAClJREFUWMPtzEERAAAMAiD7l9YQ++0gAOlRBAKBQCAQCAQCgUAgEHwPBqcL8OI+wmxEAAAAAElFTkSuQmCC'; describes.integration( '+ amp-img', { body: ` <amp-img id="image" layout="responsive" src="${BEFORE_IMG_DATA_URL}" [src]="src" alt="before_alt" [alt]="alt" width="1" [width]="w" height="1" [height]="h"></amp-img> <button on="tap:AMP.setState({src: '${AFTER_IMG_DATA_URL}'})" id="changeSrc"></button> <button on="tap:AMP.setState({alt: 'after_alt'})" id="changeAlt"></button> <button on="tap:AMP.setState({w: 2, h: 2})" id="changeSize"></button> `, extensions: ['amp-bind'], }, (env) => { let doc, img; beforeEach(() => { doc = env.win.document; img = doc.querySelector('amp-img'); }); it('[src] with valid URL', async () => { const button = doc.getElementById('changeSrc'); expect(img.getAttribute('src')).to.equal(BEFORE_IMG_DATA_URL); button.click(); await poll( '[src]', () => img.getAttribute('src') === AFTER_IMG_DATA_URL ).should.be.fulfilled; }); it('[alt]', async () => { const button = doc.getElementById('changeAlt'); expect(img.getAttribute('alt')).to.equal('before_alt'); button.click(); await poll('[src]', () => img.getAttribute('alt') === 'after_alt') .should.be.fulfilled; }); it('[width] and [height]', async () => { const button = doc.getElementById('changeSize'); expect(img.getAttribute('width')).to.equal('1'); expect(img.getAttribute('height')).to.equal('1'); button.click(); await Promise.all([ poll('[width]', () => img.getAttribute('width') === '2'), poll('[height]', () => img.getAttribute('height') === '2'), ]).should.be.fulfilled; }); } ); describes.integration( '+ forms', { body: ` <input type="range" min=0 max=100 value=0 on="change:AMP.setState({rangeChange: event})"> <p id="range" [text]="rangeChange.min + ' <= ' + rangeChange.value + ' <= ' + rangeChange.max">before_range</p> <input type="checkbox" on="change:AMP.setState({checkboxChange: event})" [checked]="isChecked"> <p id="checkbox" [text]="'checked: ' + checkboxChange.checked" id="checkboxText">before_check</p> <button on="tap:AMP.setState({isChecked: isChecked != null ? !isChecked : false})"></button> <input type="radio" on="change:AMP.setState({radioChange: event})"> <p id="radio" [text]="'checked: ' + radioChange.checked" id="radioText">before_radio</p> `, extensions: ['amp-bind'], }, (env) => { let doc; beforeEach(() => { doc = env.win.document; }); it('input[type=range] on:change', async () => { const rangeText = doc.getElementById('range'); const range = doc.querySelector('input[type="range"]'); expect(rangeText.textContent).to.equal('before_range'); // Calling #click() on the range element will not generate a change event, // so it must be generated manually. range.value = 47; range.dispatchEvent(new Event('change', {bubbles: true})); await poll('[text]', () => rangeText.textContent === '0 <= 47 <= 100') .should.be.fulfilled; }); it('input[type=checkbox] on:change', async () => { const checkboxText = doc.getElementById('checkbox'); const checkbox = doc.querySelector('input[type="checkbox"]'); expect(checkboxText.textContent).to.equal('before_check'); checkbox.click(); await poll('[text]', () => checkboxText.textContent === 'checked: true') .should.be.fulfilled; }); it('[checked]', async () => { const checkbox = doc.querySelector('input[type="checkbox"]'); const button = doc.querySelector('button'); checkbox.click(); // Note that attributes are initial values, properties are current values. expect(checkbox.hasAttribute('checked')).to.be.false; expect(checkbox.checked).to.be.true; button.click(); await poll('[checked]', () => !checkbox.checked).should.be.fulfilled; expect(checkbox.hasAttribute('checked')).to.be.false; button.click(); await poll('[checked]', () => checkbox.checked).should.be.fulfilled; // amp-bind sets both the attribute and property. expect(checkbox.hasAttribute('checked')).to.be.true; }); it('input[type=radio] on:change', async () => { const radioText = doc.getElementById('radio'); const radio = doc.querySelector('input[type="radio"]'); expect(radioText.textContent).to.equal('before_radio'); radio.click(); await poll('[text]', () => radioText.textContent === 'checked: true') .should.be.fulfilled; }); } ); describes.integration( '+ amp-carousel', { body: ` <button on="tap:AMP.setState({slide: 1})" id="goToSlideOne"></button> <p [text]="slide">0</p> <amp-carousel type="slides" width=10 height=10 on="slideChange:AMP.setState({slide: event.index})" [slide]="slide"> <amp-img src="http://example.com/foo.jpg" width=10 height=10></amp-img> <amp-img src="http://example.com/bar.jpg" width=10 height=10></amp-img> </amp-carousel> `, extensions: ['amp-bind', 'amp-carousel'], }, (env) => { let doc, carousel, slideText; beforeEach(async () => { doc = env.win.document; carousel = doc.querySelector('amp-carousel'); slideText = doc.querySelector('p'); const browserController = new BrowserController(env.win); await browserController.waitForElementLayout('amp-carousel'); }); it('on:slideChange', async () => { expect(slideText.textContent).to.equal('0'); const nextSlide = carousel.querySelector( 'div.amp-carousel-button-next' ); nextSlide.click(); await poll('[slide]', () => slideText.textContent === '1').should.be .fulfilled; }); it('[slide]', async () => { const slides = carousel.querySelectorAll( '.i-amphtml-slide-item > amp-img' ); const first = slides[0]; const second = slides[1]; expect(first.getAttribute('aria-hidden')).to.equal('false'); expect(second.getAttribute('aria-hidden')).to.be.equal('true'); const button = doc.getElementById('goToSlideOne'); button.click(); await poll( '[slide]', () => first.getAttribute('aria-hidden') === 'true' ).should.be.fulfilled; await poll( '[slide]', () => second.getAttribute('aria-hidden') === 'false' ).should.be.fulfilled; }); } ); const list = ` <amp-state id="foo"> <script type="application/json"> {"bar": "123"} </script> </amp-state> <button on="tap:AMP.setState({src: 'https://example.com/data'})"></button> <amp-list width=200 height=50 template=mustache src="/list/fruit-data/get?cors=0" [src]="src"> </amp-list> <template id=mustache type="amp-mustache"> <p [text]="foo.bar"></p> </template> `; const listTests = (env) => { let doc; let list; let browser; beforeEach(() => { doc = env.win.document; list = doc.querySelector('amp-list'); browser = new BrowserController(env.win); }); it('[src]', async () => { expect(list.getAttribute('src')).to.equal('/list/fruit-data/get?cors=0'); browser.click('button'); await poll( '[src]', () => list.getAttribute('src') === 'https://example.com/data' ).should.be.fulfilled; }); it('evaluate bindings in children', async () => { await browser.waitForElementLayout('amp-list'); const children = list.querySelectorAll('p'); expect(children.length).to.equal(3); children.forEach((span) => { expect(span.textContent).to.equal('123'); }); }); }; describes.integration( '+ amp-list, amp-mustache:0.1', { body: list, extensions: ['amp-bind', 'amp-list', 'amp-mustache:0.1'], }, listTests ); describes.integration( '+ amp-list, amp-mustache:0.2', { body: list, extensions: ['amp-bind', 'amp-list', 'amp-mustache:0.2'], }, listTests ); describes.integration( '+ amp-selector', { body: ` <button on="tap:AMP.setState({selected: 2})"></button> <p [text]="selected"></p> <amp-selector layout="container" [selected]="selected" on="select:AMP.setState({selected: event.targetOption})"> <amp-img src="/0.jpg" width=10 height=10 option=0></amp-img> <amp-img src="/1.jpg" width=10 height=10 option=1></amp-img> <amp-img src="/2.jpg" width=10 height=10 option=2></amp-img> </amp-selector> `, extensions: ['amp-bind', 'amp-selector'], }, (env) => { let doc, images, selectedText; beforeEach(async () => { doc = env.win.document; images = doc.getElementsByTagName('amp-img'); selectedText = doc.querySelector('p'); const browserController = new BrowserController(env.win); await browserController.waitForElementLayout('amp-selector'); }); it('on:select', async () => { expect(images[0].hasAttribute('selected')).to.be.false; expect(images[1].hasAttribute('selected')).to.be.false; expect(images[2].hasAttribute('selected')).to.be.false; expect(selectedText.textContent).to.equal(''); images[1].click(); await poll('[text]', () => selectedText.textContent === '1').should.be .fulfilled; expect(images[0].hasAttribute('selected')).to.be.false; expect(images[1].hasAttribute('selected')).to.be.true; expect(images[2].hasAttribute('selected')).to.be.false; }); it('[selected]', async () => { const button = doc.querySelector('button'); expect(images[0].hasAttribute('selected')).to.be.false; expect(images[1].hasAttribute('selected')).to.be.false; expect(images[2].hasAttribute('selected')).to.be.false; expect(selectedText.textContent).to.equal(''); button.click(); await poll('[text]', () => selectedText.textContent === '2').should.be .fulfilled; expect(images[0].hasAttribute('selected')).to.be.false; expect(images[1].hasAttribute('selected')).to.be.false; expect(images[2].hasAttribute('selected')).to.be.true; }); } ); });