From e181aae82cc113564e1bc11cd8145db9c9e93f51 Mon Sep 17 00:00:00 2001 From: Caroline Liu <10456171+caroqliu@users.noreply.github.com> Date: Thu, 6 May 2021 14:13:48 -0400 Subject: [PATCH] Bento: Prepare Twitter Preact implementation (#34194) * Do not override user given height until message gives one * Support tweetid directly * Support bootstrap directly * Add unit tests * Support momentid directly * Add Storybook samples for additional options * Use Sinon syntax for spying on setter --- extensions/amp-twitter/1.0/component.js | 10 +- extensions/amp-twitter/1.0/storybook/Basic.js | 54 +++++- .../amp-twitter/1.0/test/test-component.js | 157 ++++++++++++++++++ src/preact/component/3p-frame.js | 14 +- 4 files changed, 228 insertions(+), 7 deletions(-) create mode 100644 extensions/amp-twitter/1.0/test/test-component.js diff --git a/extensions/amp-twitter/1.0/component.js b/extensions/amp-twitter/1.0/component.js index 364cb2521837..932c0bef0fba 100644 --- a/extensions/amp-twitter/1.0/component.js +++ b/extensions/amp-twitter/1.0/component.js @@ -33,8 +33,11 @@ const MATCHES_MESSAGING_ORIGIN = () => true; * @param {{current: (!TwitterDef.Api|null)}} ref * @return {PreactDef.Renderable} */ -function TwitterWithRef({requestResize, title, ...rest}, ref) { - const [height, setHeight] = useState(FULL_HEIGHT); +function TwitterWithRef( + {momentid, options, requestResize, style, title, tweetid, ...rest}, + ref +) { + const [height, setHeight] = useState(null); const messageHandler = useCallback( (event) => { const data = deserializeMessage(event.data); @@ -60,8 +63,9 @@ function TwitterWithRef({requestResize, title, ...rest}, ref) { // non-overridable props matchesMessagingOrigin={MATCHES_MESSAGING_ORIGIN} messageHandler={messageHandler} + options={{tweetid, momentid, ...options}} type={TYPE} - wrapperStyle={{height}} + style={height ? {...style, height} : style} /> ); } diff --git a/extensions/amp-twitter/1.0/storybook/Basic.js b/extensions/amp-twitter/1.0/storybook/Basic.js index cd2fd2f5db52..1c0073725476 100644 --- a/extensions/amp-twitter/1.0/storybook/Basic.js +++ b/extensions/amp-twitter/1.0/storybook/Basic.js @@ -16,7 +16,7 @@ import * as Preact from '../../../../src/preact'; import {Twitter} from '../component'; -import {withKnobs} from '@storybook/addon-knobs'; +import {boolean, number, select, withKnobs} from '@storybook/addon-knobs'; export default { title: 'Twitter', @@ -25,7 +25,57 @@ export default { }; export const _default = () => { + const tweetId = select( + 'tweet id', + ['1356304203044499462', '495719809695621121', '463440424141459456'], + '1356304203044499462' + ); + const cards = boolean('show cards', true) ? undefined : 'hidden'; + const conversation = boolean('show conversation', false) ? undefined : 'none'; + return ( + + ); +}; + +export const moments = () => { + const limit = number('limit to', 2); + return ( + + ); +}; + +export const timelines = () => { + const tweetLimit = number('limit to', 5); + const timelineSourceType = select( + 'source type', + ['profile', 'likes', 'list', 'source', 'collection', 'url', 'widget'], + 'profile' + ); + const timelineScreenName = 'amphtml'; + const timelineUserId = '3450662892'; return ( - + ); }; diff --git a/extensions/amp-twitter/1.0/test/test-component.js b/extensions/amp-twitter/1.0/test/test-component.js new file mode 100644 index 000000000000..c0ba17d74fd8 --- /dev/null +++ b/extensions/amp-twitter/1.0/test/test-component.js @@ -0,0 +1,157 @@ +/** + * Copyright 2020 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Preact from '../../../../src/preact'; +import {Twitter} from '../component'; +import {WithAmpContext} from '../../../../src/preact/context'; +import {createRef} from '../../../../src/preact'; +import {mount} from 'enzyme'; +import {serializeMessage} from '../../../../src/3p-frame-messaging'; +import {waitFor} from '../../../../testing/test-helper'; + +describes.sandboxed('Twitter preact component v1.0', {}, (env) => { + it('should render', () => { + const wrapper = mount( + + ); + + const iframe = wrapper.find('iframe'); + + expect(iframe.prop('src')).to.equal( + 'http://ads.localhost:9876/dist.3p/current/frame.max.html' + ); + expect(wrapper.find('iframe').prop('style').width).to.equal('100%'); + expect(wrapper.find('iframe').prop('style').height).to.equal('100%'); + }); + + it('should call given requestResize', () => { + const requestResizeSpy = env.sandbox.spy(); + const wrapper = mount( + + ); + + const iframe = wrapper.find('iframe').getDOMNode(); + const sentinel = JSON.parse(iframe.getAttribute('name'))['attributes'][ + 'sentinel' + ]; + + const mockEvent = new CustomEvent('message'); + mockEvent.data = serializeMessage('embed-size', sentinel, { + 'height': 1000, + }); + mockEvent.source = wrapper + .getDOMNode() + .querySelector('iframe').contentWindow; + window.dispatchEvent(mockEvent); + + expect(requestResizeSpy).to.have.been.calledOnce; + }); + + it('should change height', async () => { + const wrapper = mount( + + ); + + const iframe = wrapper.find('iframe').getDOMNode(); + const sentinel = JSON.parse(iframe.getAttribute('name'))['attributes'][ + 'sentinel' + ]; + + const mockEvent = new CustomEvent('message'); + mockEvent.data = serializeMessage('embed-size', sentinel, { + 'height': 1000, + }); + mockEvent.source = wrapper + .getDOMNode() + .querySelector('iframe').contentWindow; + window.dispatchEvent(mockEvent); + + wrapper.update(); + + await waitFor( + () => wrapper.find('div').at(0).prop('style').height == 1000, + 'Height changes' + ); + + expect(wrapper.find('div').at(0).prop('style').height).to.equal(1000); + }); + + it('should dispatch load event', async () => { + const ref = createRef(); + const onReadyState = env.sandbox.spy(); + const wrapper = mount( + + ); + + let api = ref.current; + expect(api.readyState).to.equal('loading'); + expect(onReadyState).to.not.be.called; + + await wrapper.find('iframe').invoke('onLoad')(); + api = ref.current; + expect(api.readyState).to.equal('complete'); + expect(onReadyState).to.be.calledOnce.calledWith('complete'); + }); + + it('should reset iframe on pause', () => { + const ref = createRef(); + const wrapper = mount( + + + + ); + expect(wrapper.find('iframe')).to.have.lengthOf(1); + + const iframe = wrapper.find('iframe').getDOMNode(); + const spy = env.sandbox.spy(iframe, 'src', ['set']); + wrapper.setProps({playable: false}); + expect(spy.set).to.be.calledOnce; + }); +}); diff --git a/src/preact/component/3p-frame.js b/src/preact/component/3p-frame.js index 253695e86376..8cd8e71aa195 100644 --- a/src/preact/component/3p-frame.js +++ b/src/preact/component/3p-frame.js @@ -62,6 +62,7 @@ const DEFAULT_SANDBOX = function ProxyIframeEmbedWithRef( { allow = BLOCK_SYNC_XHR, + bootstrap, contextOptions, excludeSandbox, name: nameProp, @@ -122,7 +123,7 @@ function ProxyIframeEmbedWithRef( name: JSON.stringify( dict({ 'host': parseUrlDeprecated(src).hostname, - 'bootstrap': getBootstrapUrl(type, win), + 'bootstrap': bootstrap ?? getBootstrapUrl(type, win), 'type': type, // "name" must be unique across iframes, so we add a count. // See: https://github.com/ampproject/amphtml/pull/2955 @@ -132,7 +133,16 @@ function ProxyIframeEmbedWithRef( ), src, }); - }, [contextOptions, count, nameProp, options, srcProp, title, type]); + }, [ + bootstrap, + contextOptions, + count, + nameProp, + options, + srcProp, + title, + type, + ]); return (