/
githubmirror
/
amphtml
Обзор
Документация
Войти
/
githubmirror
/
amphtml
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/unit/test-navigation.js
987 строк
35 KB
Daniel Rozenberg
❄️ Unskip some tests that no longer fail (#39329)
09 авг 2023, 22:33
Не верифицирован
09 авг 2023, 22:33
1986291
Код
Авторство
О чём код?
import {createElementWithAttributes} from '#core/dom'; import {Services} from '#service'; import {maybeExpandUrlParamsForTesting} from '#service/navigation'; import {installUrlReplacementsServiceForDoc} from '#service/url-replacements-impl'; import {macroTask} from '#testing/helpers'; import * as Impression from '../../src/impression'; import {addParamToUrl} from '../../src/url'; describes.sandboxed('Navigation', {}, () => { let event; beforeEach(() => { event = { target: null, defaultPrevented: false, type: 'click', }; event.preventDefault = function () { event.defaultPrevented = true; }; }); describes.fakeWin( 'non-embed', { win: { location: 'https://www.google.com/some-path?hello=world#link', }, amp: true, }, (env) => { let win, doc; let handler; let decorationSpy; let handleNavSpy; let handleCustomProtocolSpy; let winOpenStub; let scrollIntoViewStub; let replaceStateForTargetStub; let replaceStateForTargetPromise; let anchor; let elementWithId; let anchorWithName; let customAnchor; beforeEach(() => { win = env.win; doc = win.document; const {documentElement} = doc; handler = Services.navigationForDoc(documentElement); handler.isIframed_ = true; decorationSpy = env.sandbox.spy(Impression, 'getExtraParamsUrl'); handleNavSpy = env.sandbox.spy(handler, 'handleNavigation_'); handleCustomProtocolSpy = env.sandbox.spy( handler, 'handleCustomProtocolClick_' ); win.open = function () {}; winOpenStub = env.sandbox.stub(win, 'open').callsFake(() => { return {}; }); const viewport = Services.viewportForDoc(doc); scrollIntoViewStub = env.sandbox.stub(viewport, 'scrollIntoView'); const history = Services.historyForDoc(doc); replaceStateForTargetPromise = Promise.resolve(); replaceStateForTargetStub = env.sandbox .stub(history, 'replaceStateForTarget') .callsFake(() => replaceStateForTargetPromise); anchor = doc.createElement('a'); anchor.href = 'https://www.google.com/other'; doc.body.appendChild(anchor); event.target = anchor; customAnchor = doc.createElement('a'); customAnchor.href = 'https://www.google.com/custom'; doc.body.appendChild(customAnchor); const urlReplacements = Services.urlReplacementsForDoc(documentElement); const urlReplacementStub = env.sandbox.stub( Services, 'urlReplacementsForDoc' ); urlReplacementStub.withArgs(anchor).returns(urlReplacements); urlReplacementStub.withArgs(customAnchor).returns(urlReplacements); elementWithId = doc.createElement('div'); elementWithId.id = 'test'; doc.body.appendChild(elementWithId); anchorWithName = doc.createElement('a'); anchorWithName.setAttribute('name', 'test2'); doc.body.appendChild(anchorWithName); }); describe('discovery', () => { it('should select a direct link', () => { // TODO(alabiaga): throughout the file -- invoke the handler via the // document used to get the navigation service. e.g document.click(). handler.handle_(event); expect(handleNavSpy).to.be.calledOnce; expect(handleNavSpy).to.be.calledWith(event, anchor); expect(handleCustomProtocolSpy).to.be.calledOnce; expect(handleCustomProtocolSpy).to.be.calledWith(event, anchor); }); it('should select a custom linker target', () => { event.target = null; event['__AMP_CUSTOM_LINKER_TARGET__'] = customAnchor; handler.handle_(event); expect(handleNavSpy).to.be.calledOnce; expect(handleNavSpy).to.be.calledWith(event, customAnchor); }); it('should NOT handle custom protocol when not iframed', () => { handler.isIframed_ = false; handler.handle_(event); expect(handleCustomProtocolSpy).to.be.calledOnce; expect(handleCustomProtocolSpy).to.have.returned(false); }); it('should discover a link from a nested target', () => { const target = doc.createElement('span'); anchor.appendChild(target); event.target = target; handler.handle_(event); expect(handleNavSpy).to.be.calledOnce; expect(handleNavSpy).to.be.calledWith(event, anchor); expect(handleCustomProtocolSpy).to.be.calledOnce; expect(handleCustomProtocolSpy).to.be.calledWith(event, anchor); }); it('should NOT proceed if event is cancelled', () => { event.preventDefault(); handler.handle_(event); expect(handleNavSpy).to.not.be.called; expect(handleCustomProtocolSpy).to.not.be.called; }); it('should ignore a target without link', () => { const target = doc.createElement('span'); doc.body.appendChild(target); event.target = target; handler.handle_(event); expect(handleNavSpy).to.not.be.called; expect(handleCustomProtocolSpy).to.not.be.called; }); it('should ignore a link without href', () => { anchor.removeAttribute('href'); handler.handle_(event); expect(handleNavSpy).to.not.be.called; expect(handleCustomProtocolSpy).to.not.be.called; }); }); describe('anchor mutators', () => { it('should not throw error if priority is already in use', () => { const priority = 10; handler.registerAnchorMutator((element) => { element.href += '?am=1'; }, priority); expect(() => handler.registerAnchorMutator((element) => { element.href += '?am=2'; }, priority) ).to.not.throw(); }); it('should execute in order', () => { anchor.href = 'https://www.testing-1-2-3.org'; let transformedHref; handler.registerAnchorMutator((element) => { element.href += '&second=2'; transformedHref = element.href; }, 2); handler.registerAnchorMutator((element) => { element.href += '&first=1'; transformedHref = element.href; }, 1); handler.registerAnchorMutator((element) => { element.href += '?third=3'; transformedHref = element.href; }, 3); // If using a same priority, the order of registration is respected. handler.registerAnchorMutator((element) => { element.href += '&third=3-1'; transformedHref = element.href; }, 3); handler.handle_(event); expect(transformedHref).to.equal( 'https://www.testing-1-2-3.org/?third=3&third=3-1&second=2' + '&first=1' ); }); it('verify order of operations', () => { const expandVars = env.sandbox.spy(handler, 'expandVarsForAnchor_'); const parseUrl = env.sandbox.spy(handler, 'parseUrl_'); const obj = { callback: () => {}, }; const linkRuleSpy = env.sandbox.spy(obj, 'callback'); handler.registerAnchorMutator(linkRuleSpy, 1); handler.handle_(event); // Verify that the expansion of variables occurs first // followed by the anchor transformation and then the parsing // of the possibly mutated anchor href into the location object // for navigation.handleNavClick. env.sandbox.assert.callOrder(expandVars, linkRuleSpy, parseUrl); expect(expandVars).to.be.calledOnce; // Verify that parseUrl is called once when the variables are // expanded, then after the anchor mutators and then once more // in handleNavClick expect(parseUrl).to.be.calledThrice; }); }); describe('link expansion', () => { it('should expand a link', () => { anchor.href = 'https://www.google.com/link?out=QUERY_PARAM(hello)'; anchor.setAttribute('data-amp-replace', 'QUERY_PARAM'); handler.handle_(event); expect(anchor.href).to.equal('https://www.google.com/link?out=world'); expect(handleNavSpy).to.be.calledOnce; }); it('should only expand with allowlist', () => { anchor.href = 'https://www.google.com/link?out=QUERY_PARAM(hello)'; handler.handle_(event); expect(anchor.href).to.equal( 'https://www.google.com/link?out=QUERY_PARAM(hello)' ); expect(handleNavSpy).to.be.calledOnce; }); it('should expand link if event type is right click', () => { anchor.href = 'https://www.google.com/link?out=QUERY_PARAM(hello)'; anchor.setAttribute('data-amp-replace', 'QUERY_PARAM'); event.type = 'contextmenu'; handler.handle_(event); expect(anchor.href).to.equal('https://www.google.com/link?out=world'); }); }); describe('link decoration', () => { let originLocation; let test1Url; let test2Url; beforeEach(() => { // set canonical url; handler.isEmbed_ = false; handler.appendExtraParams_ = true; originLocation = win.location.href; test1Url = addParamToUrl(originLocation, 'gclid', '123'); test2Url = addParamToUrl(test1Url, 'gclsrc', 'abcd'); const ga = win.document.createElement('amp-analytics'); ga.setAttribute('type', 'googleanalytics'); win.document.body.appendChild(ga); }); afterEach(() => { win.location.href = originLocation; }); it('should decorate for page w/ ga tag', function* () { handler.isEmbed_ = false; yield macroTask(); handler.handle_(event); expect(decorationSpy).to.be.calledOnce; }); it('should not decorate for page w/o ga tag', function* () { handler.isEmbed_ = false; const ga = win.document.getElementsByTagName('amp-analytics'); ga[0].parentNode.removeChild(ga[0]); yield macroTask(); handler.handle_(event); expect(decorationSpy).to.not.be.called; }); it('should not decorate for embed', () => { handler.isEmbed_ = true; handler.handle_(event); expect(decorationSpy).to.not.be.called; }); it('should only decorate w/ params exists in sourceUrl', () => { win.location.href = test1Url; handler.handle_(event); expect(decorationSpy).to.be.called; expect(anchor.href).to.equal( 'https://www.google.com/other?gclid=123' ); expect(handleNavSpy).to.be.calledOnce; }); it('should append gclid and gclsrc to outgoing link', () => { win.location.href = test2Url; handler.handle_(event); expect(anchor.href).to.equal( 'https://www.google.com/other?gclid=123&gclsrc=abcd' ); expect(handleNavSpy).to.be.calledOnce; }); it('should respect params in outgoing link', () => { anchor.href = 'https://www.google.com/other?gclid=456'; win.location.href = test2Url; handler.handle_(event); expect(anchor.href).to.equal( 'https://www.google.com/other?gclid=456&gclsrc=abcd' ); expect(handleNavSpy).to.be.calledOnce; }); it('should repsect data-amp-addparams', () => { anchor.setAttribute('data-amp-addparams', 'gclsrc=test'); win.location.href = test2Url; handler.handle_(event); expect(anchor.href).to.equal( 'https://www.google.com/other?gclsrc=test&gclid=123' ); expect(handleNavSpy).to.be.calledOnce; }); it('should respect async gclid and gclsrc assignment', () => { handler.handle_(event); expect(anchor.href).to.equal('https://www.google.com/other'); expect(handleNavSpy).to.be.calledOnce; win.location.href = test2Url; handler.handle_(event); expect(anchor.href).to.equal( 'https://www.google.com/other?gclid=123&gclsrc=abcd' ); expect(handleNavSpy).to.be.calledTwice; }); }); describe('when linking to ftp: protocol', () => { beforeEach(() => { anchor.href = 'ftp://example.com/a'; }); it('should always open in _blank when embedded', () => { handler.handle_(event); expect(winOpenStub).to.be.calledOnce; expect(winOpenStub).to.be.calledWith('ftp://example.com/a', '_blank'); expect(event.defaultPrevented).to.be.true; }); it('should not do anything not embedded', () => { handler.isIframed_ = false; handler.handle_(event); expect(winOpenStub).to.not.be.called; expect(winOpenStub).to.not.be.calledWith( 'ftp://example.com/a', '_blank' ); expect(event.defaultPrevented).to.be.false; }); }); describe('when linking to custom protocols e.g. whatsapp:', () => { beforeEach(() => { handler.isIosSafari_ = true; anchor.href = 'whatsapp://send?text=hello'; }); it('should open link in _top on Safari iOS when embedded', () => { handler.handle_(event); expect(winOpenStub).to.be.calledOnce; expect(winOpenStub.calledWith('whatsapp://send?text=hello', '_top')) .to.be.true; expect(event.defaultPrevented).to.be.true; }); it('should not do anything on when not embedded', () => { handler.isIframed_ = false; handler.handle_(event); expect(winOpenStub).to.not.be.called; expect(winOpenStub).to.not.be.calledWith( 'whatsapp://send?text=hello', '_top' ); expect(event.defaultPrevented).to.be.false; }); it('should not do anything for mailto: protocol', () => { anchor.href = 'mailto:hello@example.com'; handler.handle_(event); expect(winOpenStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); it('should not do anything on other non-safari iOS', () => { handler.isIosSafari_ = false; handler.handle_(event); expect(winOpenStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); it('should not do anything on other platforms', () => { handler.isIosSafari_ = false; handler.handle_(event); expect(winOpenStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); }); describe('when linking to a different origin or path', () => { it('should not do anything on path change', () => { anchor.href = 'https://www.google.com/some-other-path'; handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.be.null; }); it('should not do anything on origin change', () => { anchor.href = 'https://maps.google.com/some-path#link'; handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.be.null; }); it('should not do anything when there is no hash', () => { anchor.href = 'https://www.google.com/some-path'; handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.be.null; }); it('should not do anything on a query change', () => { anchor.href = 'https://www.google.com/some-path?hello=foo#link'; handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.be.null; }); }); describe('when linking to identifier', () => { beforeEach(() => { anchor.href = 'https://www.google.com/some-path?hello=world#test'; }); it('should find element by id', () => { handler.handle_(event); expect(event.defaultPrevented).to.be.true; expect(replaceStateForTargetStub).to.be.calledOnce; expect(replaceStateForTargetStub).to.be.calledWith('#test'); expect(scrollIntoViewStub).to.not.be.called; return replaceStateForTargetPromise.then(() => { expect(scrollIntoViewStub).to.be.called; expect(scrollIntoViewStub).to.be.calledWith(elementWithId); }); }); it('should always call preventDefault', () => { elementWithId.id = 'something-else'; handler.handle_(event); expect(event.defaultPrevented).to.be.true; expect(replaceStateForTargetStub).to.be.calledOnce; expect(replaceStateForTargetStub).to.be.calledWith('#test'); return replaceStateForTargetPromise.then(() => { expect(scrollIntoViewStub).to.not.be.called; }); }); it( 'should call querySelector on document if element with id is not ' + 'found', () => { anchor.href = 'https://www.google.com/some-path?hello=world#test2'; handler.handle_(event); expect(replaceStateForTargetStub).to.be.calledOnce; expect(replaceStateForTargetStub).to.be.calledWith('#test2'); expect(scrollIntoViewStub).to.not.be.called; return replaceStateForTargetPromise.then(() => { expect(scrollIntoViewStub).to.be.called; expect(scrollIntoViewStub).to.be.calledWith(anchorWithName); }); } ); it('should call scrollIntoView twice if element with id is found', () => { handler.handle_(event); expect(replaceStateForTargetStub).to.be.calledOnce; expect(replaceStateForTargetStub).to.be.calledWith('#test'); return replaceStateForTargetPromise .then(() => { expect(scrollIntoViewStub).to.have.callCount(1); return new Promise((resolve) => { setTimeout(resolve, 2); }); }) .then(() => { expect(scrollIntoViewStub).to.have.callCount(2); }); }); it('should use escaped css selectors with spaces', () => { anchor.href = 'https://www.google.com/some-path?hello=world#test%20hello'; anchorWithName.setAttribute('name', 'test%20hello'); handler.handle_(event); expect(replaceStateForTargetStub).to.be.calledWith('#test%20hello'); return replaceStateForTargetPromise.then(() => { expect(scrollIntoViewStub).to.be.calledWith(anchorWithName); }); }); it('should use escaped css selectors with quotes', () => { anchor.href = 'https://www.google.com/some-path?hello=world#test%22hello'; anchorWithName.setAttribute('name', 'test%22hello'); handler.handle_(event); expect(replaceStateForTargetStub).to.be.calledWith('#test%22hello'); return replaceStateForTargetPromise.then(() => { expect(scrollIntoViewStub).to.be.calledWith(anchorWithName); }); }); it('should push and pop history state with pre-existing hash', () => { win.location.href = 'https://www.google.com/some-path?hello=world#first'; handler.isIosSafari_ = true; handler.isIframed_ = false; handler.handle_(event); expect(replaceStateForTargetStub).to.be.calledOnce; expect(replaceStateForTargetStub).to.be.calledWith('#test'); }); it('should only scroll same hash, no history changes', () => { win.location.href = 'https://www.google.com/some-path?hello=world#test'; handler.handle_(event); expect(replaceStateForTargetStub).to.not.be.called; expect(scrollIntoViewStub).to.be.calledOnce; expect(scrollIntoViewStub).to.be.calledWith(elementWithId); }); }); describe('when linking to rel=amphtml', () => { beforeEach(() => { anchor.href = 'https://amp.pub.com/amp_page'; anchor.setAttribute('rel', 'unused amphtml unused'); }); it('should delegate navigation if viewer supports A2A', () => { const stub = env.sandbox .stub(handler, 'navigateToAmpUrl') .returns(true); handler.handle_(event); expect(stub).to.be.calledOnce; expect(stub).calledWithExactly( 'https://amp.pub.com/amp_page', '<a rel=amphtml>' ); // If viewer handles it, we should prevent default and not handle nav // ourselves. expect(event.defaultPrevented).to.be.true; expect(handleNavSpy).to.not.be.called; }); it('should behave normally if viewer does not support A2A', () => { const stub = env.sandbox .stub(handler, 'navigateToAmpUrl') .returns(false); handler.handle_(event); expect(stub).to.be.calledOnce; expect(stub).calledWithExactly( 'https://amp.pub.com/amp_page', '<a rel=amphtml>' ); // If viewer doesn't handles it, we should not prevent default and // handle nav ourselves. expect(event.defaultPrevented).to.be.false; expect(handleNavSpy).to.be.calledOnce; }); }); describe('navigateTo', () => { let ampdoc; beforeEach(() => { ampdoc = Services.ampdoc(doc); win.location.href = 'https://www.pub.com/'; }); it('should reject invalid protocols', () => { const newUrl = /*eslint no-script-url: 0*/ 'javascript:alert(1)'; expect(win.location.href).to.equal('https://www.pub.com/'); allowConsoleError(() => { handler.navigateTo(win, newUrl); }); // No navigation so window location should be unchanged. expect(win.location.href).to.equal('https://www.pub.com/'); }); it('should navigate relative to source url', () => { // URLs relative to root. win.location.href = 'https://cdn.ampproject.org/c/s/www.pub.com/dir/page.html'; handler.navigateTo(win, '/abc.html'); expect(win.location.href).to.equal('https://www.pub.com/abc.html'); // URLs relative to current directory. win.location.href = 'https://cdn.ampproject.org/c/s/www.pub.com/dir/page.html'; handler.navigateTo(win, 'abc.html'); expect(win.location.href).to.equal( 'https://www.pub.com/dir/abc.html' ); }); it('should delegate navigation to viewer if necessary', () => { const meta = doc.createElement('meta'); meta.setAttribute('name', 'amp-to-amp-navigation'); meta.setAttribute('content', 'feature-foo, action-bar'); ampdoc.getRootNode().head.appendChild(meta); const send = env.sandbox.stub(handler.viewer_, 'sendMessage'); const hasCapability = env.sandbox.stub( handler.viewer_, 'hasCapability' ); hasCapability.returns(true); expect(win.location.href).to.equal('https://www.pub.com/'); // Delegate to viewer if opt_requestedBy matches the <meta> tag content // and the viewer supports A2A. handler.navigateTo( win, 'https://amp.pub.com/amp_page', 'feature-foo' ); expect(hasCapability).to.be.calledWithExactly('a2a'); expect(send).to.be.calledOnce; expect(send).to.be.calledWithExactly('a2aNavigate', { requestedBy: 'feature-foo', url: 'https://amp.pub.com/amp_page', }); expect(win.location.href).to.equal('https://www.pub.com/'); // If opt_requestedBy doesn't match, navigate top normally. handler.navigateTo(win, 'https://amp.pub.com/amp_page', 'no-match'); expect(send).to.be.calledOnce; expect(win.location.href).to.equal('https://amp.pub.com/amp_page'); // If opt_requestedBy matches but viewer doesn't support A2A, navigate // top normally. send.reset(); hasCapability.returns(false); handler.navigateTo( win, 'https://amp.pub.com/different', 'action-bar' ); expect(send).to.not.be.called; expect(win.location.href).to.equal('https://amp.pub.com/different'); }); }); describe('viewer intercept navigation', () => { let ampdoc; let viewerInterceptsNavigationSpy; let sendMessageStub; let hasCapabilityStub; beforeEach(() => { ampdoc = Services.ampdoc(doc); viewerInterceptsNavigationSpy = env.sandbox.spy( handler, 'viewerInterceptsNavigation' ); sendMessageStub = env.sandbox.stub(handler.viewer_, 'sendMessage'); hasCapabilityStub = env.sandbox.stub( handler.viewer_, 'hasCapability' ); handler.isTrustedViewer_ = true; handler.isLocalViewer_ = false; hasCapabilityStub.returns(true); ampdoc .getRootNode() .documentElement.setAttribute('allow-navigation-interception', ''); }); it('should allow with trusted viewer', () => { handler.isTrustedViewer_ = true; handler.isLocalViewer_ = false; handler.handle_(event); expect(viewerInterceptsNavigationSpy).to.be.calledOnce; expect(viewerInterceptsNavigationSpy).to.be.calledWithExactly( 'https://www.google.com/other', 'intercept_click' ); expect(sendMessageStub).to.be.calledOnce; expect(sendMessageStub).to.be.calledWithExactly('navigateTo', { url: 'https://www.google.com/other', requestedBy: 'intercept_click', }); expect(event.defaultPrevented).to.be.true; }); it('should allow with local viewer', () => { handler.isTrustedViewer_ = false; handler.isLocalViewer_ = true; handler.handle_(event); expect( ampdoc .getRootNode() .documentElement.hasAttribute('allow-navigation-interception') ).to.be.true; expect(viewerInterceptsNavigationSpy).to.be.calledOnce; expect(viewerInterceptsNavigationSpy).to.be.calledWithExactly( 'https://www.google.com/other', 'intercept_click' ); expect(sendMessageStub).to.be.calledOnce; expect(sendMessageStub).to.be.calledWithExactly('navigateTo', { url: 'https://www.google.com/other', requestedBy: 'intercept_click', }); expect(event.defaultPrevented).to.be.true; }); it('should require trusted or local viewer', () => { handler.isTrustedViewer_ = false; handler.isLocalViewer_ = false; handler.handle_(event); expect(viewerInterceptsNavigationSpy).to.be.calledOnce; expect(viewerInterceptsNavigationSpy).to.be.calledWithExactly( 'https://www.google.com/other', 'intercept_click' ); expect(sendMessageStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); it('should require interceptNavigation viewer capability', () => { hasCapabilityStub.returns(false); handler.handle_(event); expect(viewerInterceptsNavigationSpy).to.be.calledOnce; expect(viewerInterceptsNavigationSpy).to.be.calledWithExactly( 'https://www.google.com/other', 'intercept_click' ); expect(sendMessageStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); it('should not intercept requests a shadow doc', () => { handler.isTrustedViewer_ = true; handler.ampdoc.isSingleDoc = () => false; // isSingleDoc affects where the services get stored. So stub the getters. env.sandbox .stub(Services, 'urlForDoc') .returns(handler.ampdoc.win.__AMP_SERVICES.url.obj); env.sandbox .stub(Services, 'viewerForDoc') .returns(handler.ampdoc.win.__AMP_SERVICES.viewer.obj); handler.handle_(event); expect(viewerInterceptsNavigationSpy).to.be.calledOnce; expect(viewerInterceptsNavigationSpy).to.be.calledWithExactly( 'https://www.google.com/other', 'intercept_click' ); expect(sendMessageStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); it('should require opted in ampdoc', () => { ampdoc .getRootNode() .documentElement.removeAttribute('allow-navigation-interception'); handler.handle_(event); expect(viewerInterceptsNavigationSpy).to.be.calledOnce; expect(viewerInterceptsNavigationSpy).to.be.calledWithExactly( 'https://www.google.com/other', 'intercept_click' ); expect(sendMessageStub).to.not.be.called; expect(event.defaultPrevented).to.be.false; }); }); } ); describes.realWin( 'fie embed', { amp: { ampdoc: 'fie', }, }, (env) => { describe('fie embed', () => { let win, doc; let parentWin; let ampdoc; let embed; let handler; let winOpenStub; let scrollIntoViewStub; let replaceStateForTargetStub; let replaceStateForTargetPromise; let anchor; let elementWithId; let anchorWithName; beforeEach(() => { win = env.win; doc = win.document; ampdoc = env.ampdoc; parentWin = env.parentWin; embed = env.embed; handler = ampdoc.__AMP_SERVICES.navigation.obj; winOpenStub = env.sandbox.stub(win, 'open').callsFake(() => { return {}; }); const viewport = parentWin.__AMP_SERVICES.viewport.obj; scrollIntoViewStub = env.sandbox.stub(viewport, 'scrollIntoView'); const history = parentWin.__AMP_SERVICES.history.obj; replaceStateForTargetPromise = Promise.resolve(); replaceStateForTargetStub = env.sandbox .stub(history, 'replaceStateForTarget') .callsFake(() => replaceStateForTargetPromise); anchor = doc.createElement('a'); anchor.href = 'http://ads.localhost:8000/example'; doc.body.appendChild(anchor); event.target = anchor; // Navigation uses the UrlReplacements service scoped to the event // target, but for testing stub in the top-level service for simplicity. const {documentElement} = parentWin.document; const urlReplacements = Services.urlReplacementsForDoc(documentElement); env.sandbox .stub(Services, 'urlReplacementsForDoc') .withArgs(anchor) .returns(urlReplacements); elementWithId = doc.createElement('div'); elementWithId.id = 'test'; doc.body.appendChild(elementWithId); anchorWithName = doc.createElement('a'); anchorWithName.setAttribute('name', 'test2'); doc.body.appendChild(anchorWithName); }); it('should adopt correctly to embed', () => { expect(handler.ampdoc).to.equal(ampdoc); expect(handler.rootNode_).to.equal(embed.win.document); expect(handler.isEmbed_).to.be.true; }); describe('when linking to a different origin or path', () => { it('should update target to _blank', () => { anchor.href = 'https://www.google.com/some-other-path'; handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.equal('_blank'); }); it('should keep the target when specified', () => { anchor.href = 'https://www.google.com/some-other-path'; anchor.setAttribute('target', '_top'); handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.equal('_top'); }); it('should reset the target when illegal specified', () => { anchor.href = 'https://www.google.com/some-other-path'; anchor.setAttribute('target', '_self'); handler.handle_(event); expect(event.defaultPrevented).to.be.false; expect(winOpenStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; expect(anchor.getAttribute('target')).to.equal('_blank'); }); }); describe('when linking to identifier', () => { beforeEach(() => { anchor.href = 'http://ads.localhost:8000/example#test'; }); it('should NOT do anything, but cancel the event', () => { handler.handle_(event); expect(event.defaultPrevented).to.be.true; expect(replaceStateForTargetStub).to.not.be.called; expect(scrollIntoViewStub).to.not.be.called; }); }); }); } ); }); describes.realWin('anchor-click-interceptor', {amp: true}, (env) => { let doc; let ampdoc; beforeEach(() => { ampdoc = env.ampdoc; doc = ampdoc.win.document; installUrlReplacementsServiceForDoc(ampdoc); }); it('should replace CLICK_X and CLICK_Y in href', () => { const a = createElementWithAttributes(doc, 'a', { href: 'http://example.com/?x=CLICK_X&y=CLICK_Y', }); const div = createElementWithAttributes(doc, 'div', {}); a.appendChild(div); doc.body.appendChild(a); // first click maybeExpandUrlParamsForTesting(ampdoc, { target: div, pageX: 12, pageY: 34, }); expect(a.href).to.equal('http://example.com/?x=12&y=34'); // second click maybeExpandUrlParamsForTesting(ampdoc, { target: div, pageX: 23, pageY: 45, }); expect(a.href).to.equal('http://example.com/?x=23&y=45'); }); });