diff --git a/src/amo/components/ScreenShots.js b/src/amo/components/ScreenShots.js
deleted file mode 100644
index 348593aa411..00000000000
--- a/src/amo/components/ScreenShots.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* global document, window */
-/* eslint-disable jsx-a11y/href-no-hash */
-
-import * as React from 'react';
-import PropTypes from 'prop-types';
-import { PhotoSwipeGallery } from 'react-photoswipe';
-import 'react-photoswipe/lib/photoswipe.css';
-
-import 'amo/css/ScreenShots.scss';
-
-const PHOTO_SWIPE_OPTIONS = {
- closeEl: true,
- captionEl: true,
- fullscreenEl: false,
- zoomEl: false,
- shareEl: false,
- counterEl: true,
- arrowEl: true,
- preloaderEl: true,
- // Overload getThumbsBoundsFn as workaround to
- // https://github.com/minhtranite/react-photoswipe/issues/23
- getThumbBoundsFn: /* istanbul ignore next */ function getThumbBoundsFn(index) {
- const thumbnail = document.querySelectorAll('.pswp-thumbnails')[index];
- if (thumbnail && thumbnail.getElementsByTagName) {
- const img = thumbnail.getElementsByTagName('img')[0];
- const pageYScroll = window.pageYOffset || document.documentElement.scrollTop;
- const rect = img.getBoundingClientRect();
- return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
- }
- return false;
- },
-};
-
-const formatPreviews = (previews) => (
- previews.map((preview) => ({
- src: preview.image_url,
- thumbnail_src: preview.thumbnail_url,
- h: preview.image_size[1],
- w: preview.image_size[0],
- title: preview.caption,
- }))
-);
-
-export const thumbnailContent = (item) => (
-
-);
-
-export default class ScreenShots extends React.Component {
- static propTypes = {
- previews: PropTypes.array.isRequired,
- }
-
- onClose = (photoswipe) => {
- const index = photoswipe.getCurrentIndex();
- const list = this.viewport.querySelector('.pswp-thumbnails');
- const currentItem = list.children[index];
- const offset = currentItem.getBoundingClientRect().x;
- list.scrollLeft += offset - list.getBoundingClientRect().x;
- }
-
- render() {
- const { previews } = this.props;
- return (
-
-
{ this.viewport = el; }}>
-
-
-
- );
- }
-}
diff --git a/src/amo/components/ScreenShots/index.js b/src/amo/components/ScreenShots/index.js
new file mode 100644
index 00000000000..4adc0d6d5b8
--- /dev/null
+++ b/src/amo/components/ScreenShots/index.js
@@ -0,0 +1,140 @@
+/* @flow */
+/* global document, window */
+/* eslint-disable jsx-a11y/href-no-hash */
+import invariant from 'invariant';
+import * as React from 'react';
+import { PhotoSwipeGallery } from 'react-photoswipe';
+import 'react-photoswipe/lib/photoswipe.css';
+
+import './styles.scss';
+
+type ThumbBounds = false | {|
+ w: number,
+ x: number,
+ y: number,
+|};
+
+type GetThumbBoundsExtraParams = {|
+ _document: typeof document | null,
+ _window: typeof window | null,
+|};
+
+export const PHOTO_SWIPE_OPTIONS = {
+ closeEl: true,
+ captionEl: true,
+ fullscreenEl: false,
+ zoomEl: false,
+ shareEl: false,
+ counterEl: true,
+ arrowEl: true,
+ preloaderEl: true,
+ // Overload getThumbBoundsFn as workaround to
+ // https://github.com/minhtranite/react-photoswipe/issues/23
+ getThumbBoundsFn: (index: number, {
+ // $FLOW_FIXME: see https://github.com/facebook/flow/issues/183
+ _document = typeof document !== 'undefined' ? document : null,
+ _window = typeof window !== 'undefined' ? window : null,
+ }: GetThumbBoundsExtraParams = {}): ThumbBounds => {
+ if (!_document || !_window) {
+ return false;
+ }
+
+ const thumbnail = _document.querySelectorAll('.pswp-thumbnails')[index];
+
+ if (thumbnail && thumbnail.getElementsByTagName) {
+ const img = thumbnail.getElementsByTagName('img')[0];
+ const pageYScroll = _window.pageYOffset || (
+ _document.documentElement ? _document.documentElement.scrollTop : 0
+ );
+ const rect = img.getBoundingClientRect();
+
+ return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
+ }
+
+ return false;
+ },
+};
+
+type ExternalPreview = {|
+ caption: string,
+ image_size: [number, number],
+ image_url: string,
+ thumbnail_size: [number, number],
+ thumbnail_url: string,
+|};
+
+type Preview = {|
+ h: number,
+ src: string,
+ thumbnail_h: number,
+ thumbnail_src: string,
+ thumbnail_w: number,
+ title: string,
+ w: number,
+|};
+
+const formatPreviews = (previews: Array): Array => (
+ previews.map((preview) => ({
+ h: preview.image_size[1],
+ src: preview.image_url,
+ thumbnail_h: preview.thumbnail_size[1],
+ thumbnail_src: preview.thumbnail_url,
+ thumbnail_w: preview.thumbnail_size[0],
+ title: preview.caption,
+ w: preview.image_size[0],
+ }))
+);
+
+export const thumbnailContent = (item: Preview): React.Node => (
+
+);
+
+type Props = {|
+ previews: Array,
+|};
+
+export default class ScreenShots extends React.Component {
+ onClose = (photoswipe: Object) => {
+ const index = photoswipe.getCurrentIndex();
+
+ invariant(this.viewport, 'viewport ref is required');
+
+ const list = this.viewport.querySelector('.pswp-thumbnails');
+
+ invariant(list, 'list is required');
+
+ const currentItem = list.children[index];
+ const offset = currentItem.getBoundingClientRect().left;
+ list.scrollLeft += offset - list.getBoundingClientRect().left;
+ }
+
+ viewport: HTMLElement | null;
+
+ render() {
+ const { previews } = this.props;
+
+ return (
+
+
{ this.viewport = el; }}
+ >
+
+
+
+ );
+ }
+}
diff --git a/src/amo/css/ScreenShots.scss b/src/amo/components/ScreenShots/styles.scss
similarity index 100%
rename from src/amo/css/ScreenShots.scss
rename to src/amo/components/ScreenShots/styles.scss
diff --git a/tests/unit/amo/components/TestScreenShots.js b/tests/unit/amo/components/TestScreenShots.js
index c97afa722e2..8eb45c35916 100644
--- a/tests/unit/amo/components/TestScreenShots.js
+++ b/tests/unit/amo/components/TestScreenShots.js
@@ -3,6 +3,7 @@ import * as React from 'react';
import { PhotoSwipeGallery } from 'react-photoswipe';
import ScreenShots, {
+ PHOTO_SWIPE_OPTIONS,
thumbnailContent,
} from 'amo/components/ScreenShots';
@@ -16,14 +17,14 @@ describe(__filename, () => {
image_url: 'http://img.com/one',
thumbnail_url: 'http://img.com/1',
image_size: [WIDTH, HEIGHT],
- thumbnail_size: [WIDTH, HEIGHT],
+ thumbnail_size: [WIDTH - 100, HEIGHT - 100],
},
{
caption: 'Another screenshot',
image_url: 'http://img.com/two',
thumbnail_url: 'http://img.com/2',
image_size: [WIDTH, HEIGHT],
- thumbnail_size: [WIDTH, HEIGHT],
+ thumbnail_size: [WIDTH - 100, HEIGHT - 100],
},
];
@@ -33,6 +34,8 @@ describe(__filename, () => {
title: 'A screenshot',
src: 'http://img.com/one',
thumbnail_src: 'http://img.com/1',
+ thumbnail_w: WIDTH - 100,
+ thumbnail_h: HEIGHT - 100,
h: HEIGHT,
w: WIDTH,
},
@@ -40,6 +43,8 @@ describe(__filename, () => {
title: 'Another screenshot',
src: 'http://img.com/two',
thumbnail_src: 'http://img.com/2',
+ thumbnail_w: WIDTH - 100,
+ thumbnail_h: HEIGHT - 100,
h: HEIGHT,
w: WIDTH,
},
@@ -53,16 +58,26 @@ describe(__filename, () => {
});
it('renders custom thumbnail', () => {
- const h = 123;
- const w = 1234;
+ const thumbnailSrc = 'http://example.com/thumbnail.png';
+ const thumbnailHeight = 123;
+ const thumbnailWidth = 200;
+
+ const item = {
+ src: 'https://foo.com/img.png',
+ title: 'test title',
+ h: HEIGHT,
+ w: WIDTH,
+ thumbnail_src: thumbnailSrc,
+ thumbnail_h: thumbnailHeight,
+ thumbnail_w: thumbnailWidth,
+ };
- const item = { src: 'https://foo.com/img.png', title: 'test title', h, w };
const thumbnail = shallow(thumbnailContent(item));
expect(thumbnail.type()).toEqual('img');
- expect(thumbnail.prop('src')).toEqual('https://foo.com/img.png');
- expect(thumbnail.prop('height')).toEqual(h);
- expect(thumbnail.prop('width')).toEqual(w);
+ expect(thumbnail.prop('src')).toEqual(thumbnailSrc);
+ expect(thumbnail.prop('height')).toEqual(thumbnailHeight);
+ expect(thumbnail.prop('width')).toEqual(thumbnailWidth);
expect(thumbnail.prop('alt')).toEqual('test title');
expect(thumbnail.prop('title')).toEqual('test title');
});
@@ -74,16 +89,122 @@ describe(__filename, () => {
));
const root = mount();
- const item = { getBoundingClientRect: () => ({ x: 500 }) };
+ const item = { getBoundingClientRect: () => ({ left: 500 }) };
const list = {
children: [null, item],
- getBoundingClientRect: () => ({ x: 55 }),
+ getBoundingClientRect: () => ({ left: 55 }),
scrollLeft: 0,
};
sinon.stub(root.instance().viewport, 'querySelector').returns(list);
+
const photoswipe = { getCurrentIndex: () => 1 };
root.instance().onClose(photoswipe);
// 0 += 500 - 55
expect(list.scrollLeft).toEqual(445);
});
+
+ describe('PHOTO_SWIPE_OPTIONS.getThumbBoundsFn', () => {
+ const { getThumbBoundsFn } = PHOTO_SWIPE_OPTIONS;
+
+ const getFakeDocument = ({ left, top, width }) => {
+ const fakeImg = {
+ getBoundingClientRect: () => ({
+ height: 123,
+ left,
+ top,
+ width,
+ }),
+ };
+
+ const fakeThumbnail = {
+ getElementsByTagName: () => [fakeImg],
+ };
+
+ const fakeDocument = {
+ querySelectorAll: () => [fakeThumbnail],
+ };
+
+ return fakeDocument;
+ };
+
+ it('returns false if thumbnail does not exist', () => {
+ const bounds = getThumbBoundsFn(0);
+
+ expect(bounds).toEqual(false);
+ });
+
+ it('returns false if _document is null', () => {
+ const bounds = getThumbBoundsFn(0, { _document: null });
+
+ expect(bounds).toEqual(false);
+ });
+
+ it('returns false if _window is null', () => {
+ const bounds = getThumbBoundsFn(0, {
+ _document: getFakeDocument({ left: 1, top: 2, width: 3 }),
+ _window: null,
+ });
+
+ expect(bounds).toEqual(false);
+ });
+
+ it('returns an object with x, y and w values', () => {
+ const left = 123;
+ const top = 124;
+ const width = 100;
+
+ const fakeDocument = getFakeDocument({ left, top, width });
+
+ const bounds = getThumbBoundsFn(0, { _document: fakeDocument });
+
+ expect(bounds).toEqual({
+ w: width,
+ x: left,
+ y: top,
+ });
+ });
+
+ it('uses window.pageYOffset to compute `y` if available', () => {
+ const left = 123;
+ const top = 124;
+ const width = 100;
+
+ const fakeDocument = getFakeDocument({ left, top, width });
+
+ const fakeWindow = {
+ pageYOffset: 20,
+ };
+
+ const bounds = getThumbBoundsFn(0, {
+ _document: fakeDocument,
+ _window: fakeWindow,
+ });
+
+ expect(bounds).toEqual({
+ w: width,
+ x: left,
+ y: top + fakeWindow.pageYOffset,
+ });
+ });
+
+ it('uses document.documentElement.scrollTop to compute `y` if available', () => {
+ const left = 123;
+ const top = 124;
+ const width = 100;
+ const scrollTop = 30;
+
+ const fakeDocument = getFakeDocument({ left, top, width });
+ fakeDocument.documentElement = {
+ scrollTop,
+ };
+
+ const bounds = getThumbBoundsFn(0, { _document: fakeDocument });
+
+ expect(bounds).toEqual({
+ w: width,
+ x: left,
+ y: top + scrollTop,
+ });
+ });
+ });
});