From 7caddb90a993876c2ff512267bf7340de45fdc70 Mon Sep 17 00:00:00 2001
From: Paul Kaplan <pkaplan@media.mit.edu>
Date: Fri, 27 Apr 2018 11:42:27 -0400
Subject: [PATCH] Revert "Updates to preview modal for release (#1864)"

This reverts commit 29d5619d936d30eea9ca601f6d052aad108ca0d3.
---
 src/components/gui/gui.jsx                    |   6 +
 src/components/import-modal/import-modal.css  | 185 ++++++++++++++++++
 src/components/import-modal/import-modal.jsx  | 155 +++++++++++++++
 .../preview-modal/preview-modal.css           |  12 +-
 .../preview-modal/preview-modal.jsx           |  72 +++----
 src/containers/gui.jsx                        |   2 +
 src/containers/import-modal.jsx               | 111 +++++++++++
 src/containers/preview-modal.jsx              |  17 +-
 src/reducers/modals.js                        |  11 ++
 test/integration/project-loading.test.js      |  35 ++++
 10 files changed, 561 insertions(+), 45 deletions(-)
 create mode 100644 src/components/import-modal/import-modal.css
 create mode 100644 src/components/import-modal/import-modal.jsx
 create mode 100644 src/containers/import-modal.jsx

diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 640e07fdd..1f7ccfb20 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -19,6 +19,7 @@ import Box from '../box/box.jsx';
 import MenuBar from '../menu-bar/menu-bar.jsx';
 
 import PreviewModal from '../../containers/preview-modal.jsx';
+import ImportModal from '../../containers/import-modal.jsx';
 import WebGlModal from '../../containers/webgl-modal.jsx';
 import TipsLibrary from '../../containers/tips-library.jsx';
 import Cards from '../../containers/cards.jsx';
@@ -50,6 +51,7 @@ const GUIComponent = props => {
         cardsVisible,
         children,
         costumesTabVisible,
+        importInfoVisible,
         intl,
         loading,
         onExtensionButtonClick,
@@ -93,6 +95,9 @@ const GUIComponent = props => {
             {loading ? (
                 <Loader />
             ) : null}
+            {importInfoVisible ? (
+                <ImportModal />
+            ) : null}
             {isRendererSupported ? null : (
                 <WebGlModal />
             )}
@@ -225,6 +230,7 @@ GUIComponent.propTypes = {
     cardsVisible: PropTypes.bool,
     children: PropTypes.node,
     costumesTabVisible: PropTypes.bool,
+    importInfoVisible: PropTypes.bool,
     intl: intlShape.isRequired,
     loading: PropTypes.bool,
     onActivateCostumesTab: PropTypes.func,
diff --git a/src/components/import-modal/import-modal.css b/src/components/import-modal/import-modal.css
new file mode 100644
index 000000000..93480429b
--- /dev/null
+++ b/src/components/import-modal/import-modal.css
@@ -0,0 +1,185 @@
+@import "../../css/colors.css";
+@import "../../css/units.css";
+@import "../../css/typography.css";
+
+.modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 1000;
+    background-color: $ui-modal-overlay;
+}
+
+.modal-content {
+    margin: 100px auto;
+    outline: none;
+    border: .25rem solid $ui-white-transparent;
+    padding: 0;
+    border-radius: $space;
+    user-select: none;
+    width: 500px;
+    color: $text-primary;
+    overflow: hidden;
+}
+
+/*
+    TODO figure out how to remove filter altogether
+    since it is null...
+    Modal header has 3 items:
+    |x     title       filter|
+
+    Use the same width for both side item containers,
+    so that title remains centered
+*/
+$sides: 20rem;
+
+.header {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: nowrap;
+    justify-content: flex-start;
+    height: $library-header-height;
+
+    box-sizing: border-box;
+    width: 100%;
+    background-color: $pen-primary;
+}
+
+.header-item {
+    display: flex;
+    align-items: center;
+    padding: 1rem;
+    text-decoration: none;
+    color: white;
+    user-select: none;
+}
+
+.header-item-filter {
+    display: flex;
+    flex-basis: $sides;
+    justify-content: flex-end;
+}
+
+.header-item-title {
+    flex-grow: 1;
+    flex-shrink: 0;
+    justify-content: center;
+    user-select: none;
+    letter-spacing: 0.4px;
+    cursor: default;
+}
+
+.header-item-title h2 {
+    font-size: 1.25rem;
+}
+
+.header-item-close {
+    display: flex;
+    flex-basis: $sides;
+    justify-content: flex-start;
+}
+
+.body {
+    background: $ui-white;
+    padding: 1.5rem 2.25rem;
+    text-align: center;
+}
+
+.input-row {
+    margin: 1.5rem 0;
+    font-weight: bolder;
+    text-align: right;
+    display: flex;
+    justify-content: center;
+    border: 1px solid;
+    border-radius: 0.25rem;
+    overflow: hidden;
+}
+
+.ok-input-container {
+    border-color: $motion-primary;
+    box-shadow: 0 0 0 0.2rem $motion-transparent;
+}
+
+.bad-input-container {
+    border-color: $data-primary;
+    box-shadow: 0 0 0 0.2rem hsla(30, 100%, 55%, 0.15);
+}
+
+.input-row input {
+    width: 100%;
+    padding: 0 1rem;
+    height: 3rem;
+    color: $text-primary;
+    font-size: .875rem;
+    outline: none;
+    border: none;
+}
+
+.input-row input::placeholder {
+    font-style: italic;
+    color: $text-primary-transparent;
+}
+
+.input-row button {
+    padding: 0.5rem 2rem;
+    font-weight: bold;
+    font-size: .875rem;
+    cursor: pointer;
+    border: 0px solid $pen-primary;
+    outline: none;
+}
+
+.input-row button.ok-button {
+    background: $pen-primary;
+    color: white;
+}
+
+.error-row {
+    margin: 1.5rem 0;
+    text-align: center;
+    display: flex;
+    justify-content: center;
+    background: hsla(30, 100%, 55%, 0.25);
+    color: $data-primary;
+    border: 1px solid $data-primary;
+    border-radius: 0.25rem;
+}
+
+.error-row p {
+    font-size: 0.875rem;
+    font-weight: bold;
+}
+
+/* Confirmation buttons at the bottom of the modal */
+.button-row {
+    margin: 1.5rem 0;
+    font-weight: bolder;
+    text-align: right;
+    display: flex;
+    justify-content: center;
+}
+
+.button-row button {
+    border: 1px solid $motion-primary;
+    border-radius: 0.25rem;
+    padding: 0.5rem 1.5rem;
+    background: white;
+    font-weight: bold;
+    font-size: .875rem;
+    cursor: pointer;
+    color: $motion-primary;
+}
+
+.faq-link-text {
+    margin: 2rem 0 .5rem 0;
+    font-size: .875rem;
+    color: $text-primary;
+}
+
+.faq-link {
+    color: $motion-primary;
+    text-decoration: none;
+}
diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx
new file mode 100644
index 000000000..ad42929e7
--- /dev/null
+++ b/src/components/import-modal/import-modal.jsx
@@ -0,0 +1,155 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ReactModal from 'react-modal';
+import Box from '../box/box.jsx';
+import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
+import classNames from 'classnames';
+
+import CloseButton from '../close-button/close-button.jsx';
+
+import styles from './import-modal.css';
+
+const messages = defineMessages({
+    title: {
+        id: 'gui.importInfo.title',
+        defaultMessage: 'View a Scratch 2.0 Project',
+        description: 'Scratch 2.0 import modal label - for accessibility'
+    },
+    formDescription: {
+        defaultMessage:
+            'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Preview will not be saved.',
+        description: 'Import project message',
+        id: 'gui.importInfo.message'
+    },
+    invalidFormatError: {
+        id: 'gui.importInfo.invalidFormatError',
+        defaultMessage: 'Uh oh, that project link or id doesn\'t look quite right.',
+        description: 'Invalid project link or id message'
+    }
+});
+
+const ImportModal = ({intl, ...props}) => (
+    <ReactModal
+        isOpen
+        className={styles.modalContent}
+        contentLabel={intl.formatMessage({...messages.title})}
+        overlayClassName={styles.modalOverlay}
+        onRequestClose={props.onCancel}
+    >
+        <Box>
+            <div className={styles.header}>
+                <div
+                    className={classNames(
+                        styles.headerItem,
+                        styles.headerItemClose
+                    )}
+                >
+                    <CloseButton
+                        buttonType="back"
+                        size={CloseButton.SIZE_LARGE}
+                        onClick={props.onGoBack}
+                    />
+                </div>
+                <div
+                    className={classNames(
+                        styles.headerItem,
+                        styles.headerItemTitle
+                    )}
+                >
+                    <h2>
+                        {intl.formatMessage({...messages.title})}
+                    </h2>
+                </div>
+                <div className={classNames(styles.headerItem, styles.headerItemFilter)}>
+                    {null}
+                </div>
+            </div>
+        </Box>
+
+        <Box className={styles.body}>
+            <p>
+                {intl.formatMessage({...messages.formDescription})}
+            </p>
+            <Box
+                className={classNames(styles.inputRow,
+                    (props.hasValidationError ? styles.badInputContainer : styles.okInputContainer))
+                }
+            >
+                <input
+                    autoFocus
+                    placeholder={props.placeholder}
+                    value={props.inputValue}
+                    onChange={props.onChange}
+                    onKeyPress={props.onKeyPress}
+                />
+                <button
+                    className={styles.okButton}
+                    title="viewproject"
+                    onClick={props.onViewProject}
+                >
+                    <FormattedMessage
+                        defaultMessage="View"
+                        description="Label for button to load a scratch 2.0 project"
+                        id="gui.importModal.viewproject"
+                    />
+                </button>
+            </Box>
+            {props.hasValidationError ?
+                <Box className={styles.errorRow}>
+                    <p>
+                        <FormattedMessage
+                            {...messages[`${props.errorMessage}`]}
+                        />
+                    </p>
+                </Box> : null
+            }
+            <Box className={styles.buttonRow}>
+                <button
+                    onClick={props.onGoBack}
+                >
+                    <FormattedMessage
+                        defaultMessage="Go Back"
+                        description="Label for button to back out of importing a project"
+                        id="gui.importInfo.goback"
+                    />
+                </button>
+            </Box>
+            <Box className={styles.faqLinkText}>
+                <FormattedMessage
+                    defaultMessage="To learn more, go to the {previewFaqLink}."
+                    description="Invitation to try 3.0 preview"
+                    id="gui.importInfo.previewfaq"
+                    values={{
+                        previewFaqLink: (
+                            <a
+                                className={styles.faqLink}
+                                href="//scratch.mit.edu/preview-faq"
+                            >
+                                <FormattedMessage
+                                    defaultMessage="Preview FAQ"
+                                    description="link to Scratch 3.0 preview FAQ page"
+                                    id="gui.importInfo.previewfaqlink"
+                                />
+                            </a>
+                        )
+                    }}
+                />
+            </Box>
+        </Box>
+    </ReactModal>
+);
+
+ImportModal.propTypes = {
+    errorMessage: PropTypes.string.isRequired,
+    hasValidationError: PropTypes.bool.isRequired,
+    inputValue: PropTypes.string.isRequired,
+    intl: intlShape.isRequired,
+    onCancel: PropTypes.func.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onGoBack: PropTypes.func.isRequired,
+    onKeyPress: PropTypes.func.isRequired,
+    onViewProject: PropTypes.func.isRequired,
+    placeholder: PropTypes.string
+};
+
+export default injectIntl(ImportModal);
diff --git a/src/components/preview-modal/preview-modal.css b/src/components/preview-modal/preview-modal.css
index a746c9178..837ed92cf 100644
--- a/src/components/preview-modal/preview-modal.css
+++ b/src/components/preview-modal/preview-modal.css
@@ -39,12 +39,6 @@
     text-align: center;
 }
 
-.disclaimer {
-    margin-top: 2.0rem;
-    border-top: 1px solid $text-primary;
-    padding-top: 1.0rem;
-}
-
 /* Confirmation buttons at the bottom of the modal */
 .button-row {
     margin: 1.5rem 0;
@@ -73,6 +67,12 @@
     color: $motion-primary;
 }
 
+.button-row button.view-project-button {
+    background: $pen-primary;
+    border-color: $pen-primary;
+    color: white;
+}
+
 .button-row button + button {
     margin-left: 0.5rem;
 }
diff --git a/src/components/preview-modal/preview-modal.jsx b/src/components/preview-modal/preview-modal.jsx
index 9e86e5434..5772b5185 100644
--- a/src/components/preview-modal/preview-modal.jsx
+++ b/src/components/preview-modal/preview-modal.jsx
@@ -35,45 +35,12 @@ const PreviewModal = ({intl, ...props}) => (
             </h2>
             <p>
                 <FormattedMessage
-                    defaultMessage="We're excited for you to try the next generation of Scratch!
-                        To learn more, go to the {previewFaqLink}."
+                    defaultMessage="We're working on the next generation of Scratch. We're excited for you to try it!"
                     description="Invitation to try 3.0 preview"
-                    id="gui.previewInfo.previewfaq"
-                    values={{
-                        previewFaqLink: (
-                            <a
-                                className={styles.faqLink}
-                                href="//scratch.mit.edu/preview-faq"
-                            >
-                                <FormattedMessage
-                                    defaultMessage="Preview FAQ"
-                                    description="link to Scratch 3.0 preview FAQ page"
-                                    id="gui.previewInfo.previewfaqlink"
-                                />
-                            </a>
-                        )
-                    }}
+                    id="gui.previewInfo.invitation"
                 />
             </p>
 
-            <Box className={styles.disclaimer}>
-                <p>
-                    <strong>
-                        <FormattedMessage
-                            defaultMessage="Changes to projects will not be saved."
-                            description="Disclaimer for 3.0 preview"
-                            id="gui.previewInfo.disclaimer"
-                        />
-                        <br />
-                        <FormattedMessage
-                            defaultMessage="This feature is coming soon!"
-                            description="Notice that a feature is in progress"
-                            id="gui.previewInfo.comingsoon"
-                        />
-                    </strong>
-                </p>
-            </Box>
-
             <Box className={styles.buttonRow}>
                 <button
                     className={styles.noButton}
@@ -104,6 +71,38 @@ const PreviewModal = ({intl, ...props}) => (
                         }}
                     />
                 </button>
+                <button
+                    className={styles.viewProjectButton}
+                    title="viewproject"
+                    onClick={props.onViewProject}
+                >
+                    <FormattedMessage
+                        defaultMessage="View 2.0 Project"
+                        description="Label for button to import a 2.0 project"
+                        id="gui.previewModal.viewproject"
+                    />
+                </button>
+            </Box>
+            <Box className={styles.faqLinkText}>
+                <FormattedMessage
+                    defaultMessage="To learn more, go to the {previewFaqLink}."
+                    description="Invitation to try 3.0 preview"
+                    id="gui.previewInfo.previewfaq"
+                    values={{
+                        previewFaqLink: (
+                            <a
+                                className={styles.faqLink}
+                                href="//scratch.mit.edu/preview-faq"
+                            >
+                                <FormattedMessage
+                                    defaultMessage="Preview FAQ"
+                                    description="link to Scratch 3.0 preview FAQ page"
+                                    id="gui.previewInfo.previewfaqlink"
+                                />
+                            </a>
+                        )
+                    }}
+                />
             </Box>
         </Box>
     </ReactModal>
@@ -112,7 +111,8 @@ const PreviewModal = ({intl, ...props}) => (
 PreviewModal.propTypes = {
     intl: intlShape.isRequired,
     onCancel: PropTypes.func.isRequired,
-    onTryIt: PropTypes.func.isRequired
+    onTryIt: PropTypes.func.isRequired,
+    onViewProject: PropTypes.func.isRequired
 };
 
 export default injectIntl(PreviewModal);
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index a47467c0c..521837bff 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -86,6 +86,7 @@ class GUI extends React.Component {
 GUI.propTypes = {
     ...GUIComponent.propTypes,
     fetchingProject: PropTypes.bool,
+    importInfoVisible: PropTypes.bool,
     loadingStateVisible: PropTypes.bool,
     previewInfoVisible: PropTypes.bool,
     projectData: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
@@ -99,6 +100,7 @@ const mapStateToProps = state => ({
     blocksTabVisible: state.editorTab.activeTabIndex === BLOCKS_TAB_INDEX,
     cardsVisible: state.cards.visible,
     costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
+    importInfoVisible: state.modals.importInfo,
     loadingStateVisible: state.modals.loadingProject,
     previewInfoVisible: state.modals.previewInfo,
     soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
diff --git a/src/containers/import-modal.jsx b/src/containers/import-modal.jsx
new file mode 100644
index 000000000..0731098f1
--- /dev/null
+++ b/src/containers/import-modal.jsx
@@ -0,0 +1,111 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+
+import ImportModalComponent from '../components/import-modal/import-modal.jsx';
+
+import {
+    closeImportInfo,
+    openPreviewInfo
+} from '../reducers/modals';
+
+class ImportModal extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'handleViewProject',
+            'handleCancel',
+            'handleChange',
+            'handleGoBack',
+            'handleKeyPress'
+        ]);
+
+        this.state = {
+            inputValue: '',
+            hasValidationError: false,
+            errorMessage: ''
+        };
+    }
+    handleKeyPress (event) {
+        if (event.key === 'Enter') this.handleViewProject();
+    }
+    handleViewProject () {
+        const inputValue = this.state.inputValue;
+        const projectId = this.validate(inputValue);
+
+        if (projectId) {
+            const projectLink = document.createElement('a');
+            document.body.appendChild(projectLink);
+            projectLink.href = `#${projectId}`;
+            projectLink.click();
+            document.body.removeChild(projectLink);
+            this.props.onViewProject();
+        } else {
+            this.setState({
+                hasValidationError: true,
+                errorMessage: `invalidFormatError`});
+        }
+    }
+    handleChange (e) {
+        this.setState({inputValue: e.target.value, hasValidationError: false});
+    }
+    validate (input) {
+        const urlMatches = input.match(/^(?:https?:\/\/)?scratch\.mit\.edu\/projects\/(\d+)/);
+        if (urlMatches && urlMatches.length > 0) {
+            return urlMatches[1];
+        }
+        const projectIdMatches = input.match(/^#?(\d+)$/);
+        if (projectIdMatches && projectIdMatches.length > 0) {
+            return projectIdMatches[1];
+        }
+        return null;
+    }
+    handleCancel () {
+        this.props.onCancel();
+    }
+    handleGoBack () {
+        this.props.onBack();
+    }
+    render () {
+        return (
+            <ImportModalComponent
+                errorMessage={this.state.errorMessage}
+                hasValidationError={this.state.hasValidationError}
+                inputValue={this.state.inputValue}
+                placeholder="scratch.mit.edu/projects/123456789"
+                onCancel={this.handleCancel}
+                onChange={this.handleChange}
+                onGoBack={this.handleGoBack}
+                onKeyPress={this.handleKeyPress}
+                onViewProject={this.handleViewProject}
+            />
+        );
+    }
+}
+
+ImportModal.propTypes = {
+    onBack: PropTypes.func.isRequired,
+    onCancel: PropTypes.func.isRequired,
+    onViewProject: PropTypes.func
+};
+
+const mapStateToProps = () => ({});
+
+const mapDispatchToProps = dispatch => ({
+    onBack: () => {
+        dispatch(closeImportInfo());
+        dispatch(openPreviewInfo());
+    },
+    onCancel: () => {
+        dispatch(closeImportInfo());
+    },
+    onViewProject: () => {
+        dispatch(closeImportInfo());
+    }
+});
+
+export default connect(
+    mapStateToProps,
+    mapDispatchToProps
+)(ImportModal);
diff --git a/src/containers/preview-modal.jsx b/src/containers/preview-modal.jsx
index 70fb3290b..3fa6c8f03 100644
--- a/src/containers/preview-modal.jsx
+++ b/src/containers/preview-modal.jsx
@@ -8,7 +8,8 @@ import PreviewModalComponent from '../components/preview-modal/preview-modal.jsx
 import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx';
 
 import {
-    closePreviewInfo
+    closePreviewInfo,
+    openImportInfo
 } from '../reducers/modals';
 
 class PreviewModal extends React.Component {
@@ -16,7 +17,8 @@ class PreviewModal extends React.Component {
         super(props);
         bindAll(this, [
             'handleTryIt',
-            'handleCancel'
+            'handleCancel',
+            'handleViewProject'
         ]);
 
         this.state = {
@@ -30,6 +32,9 @@ class PreviewModal extends React.Component {
     handleCancel () {
         window.location.replace('https://scratch.mit.edu');
     }
+    handleViewProject () {
+        this.props.onViewProject();
+    }
     supportedBrowser () {
         return !['IE', 'Opera', 'Opera Mini', 'Silk', 'Vivaldi'].includes(platform.name);
     }
@@ -39,6 +44,7 @@ class PreviewModal extends React.Component {
                 previewing={this.state.previewing}
                 onCancel={this.handleCancel}
                 onTryIt={this.handleTryIt}
+                onViewProject={this.handleViewProject}
             /> :
             <BrowserModalComponent
                 onBack={this.handleCancel}
@@ -48,7 +54,8 @@ class PreviewModal extends React.Component {
 }
 
 PreviewModal.propTypes = {
-    onTryIt: PropTypes.func
+    onTryIt: PropTypes.func,
+    onViewProject: PropTypes.func
 };
 
 const mapStateToProps = () => ({});
@@ -56,6 +63,10 @@ const mapStateToProps = () => ({});
 const mapDispatchToProps = dispatch => ({
     onTryIt: () => {
         dispatch(closePreviewInfo());
+    },
+    onViewProject: () => {
+        dispatch(closePreviewInfo());
+        dispatch(openImportInfo());
     }
 });
 
diff --git a/src/reducers/modals.js b/src/reducers/modals.js
index 6bb28c81b..481c44f29 100644
--- a/src/reducers/modals.js
+++ b/src/reducers/modals.js
@@ -6,6 +6,7 @@ const CLOSE_MODAL = 'scratch-gui/modals/CLOSE_MODAL';
 const MODAL_BACKDROP_LIBRARY = 'backdropLibrary';
 const MODAL_COSTUME_LIBRARY = 'costumeLibrary';
 const MODAL_EXTENSION_LIBRARY = 'extensionLibrary';
+const MODAL_IMPORT_INFO = 'importInfo';
 const MODAL_LOADING_PROJECT = 'loadingProject';
 const MODAL_PREVIEW_INFO = 'previewInfo';
 const MODAL_SOUND_LIBRARY = 'soundLibrary';
@@ -19,6 +20,7 @@ const initialState = {
     [MODAL_BACKDROP_LIBRARY]: false,
     [MODAL_COSTUME_LIBRARY]: false,
     [MODAL_EXTENSION_LIBRARY]: false,
+    [MODAL_IMPORT_INFO]: false,
     [MODAL_LOADING_PROJECT]: false,
     [MODAL_PREVIEW_INFO]: true,
     [MODAL_SOUND_LIBRARY]: false,
@@ -66,6 +68,10 @@ const openExtensionLibrary = function () {
     analytics.pageview('/libraries/extensions');
     return openModal(MODAL_EXTENSION_LIBRARY);
 };
+const openImportInfo = function () {
+    analytics.pageview('modals/import');
+    return openModal(MODAL_IMPORT_INFO);
+};
 const openLoadingProject = function () {
     analytics.pageview('modals/loading');
     return openModal(MODAL_LOADING_PROJECT);
@@ -99,6 +105,9 @@ const closeCostumeLibrary = function () {
 const closeExtensionLibrary = function () {
     return closeModal(MODAL_EXTENSION_LIBRARY);
 };
+const closeImportInfo = function () {
+    return closeModal(MODAL_IMPORT_INFO);
+};
 const closeLoadingProject = function () {
     return closeModal(MODAL_LOADING_PROJECT);
 };
@@ -122,6 +131,7 @@ export {
     openBackdropLibrary,
     openCostumeLibrary,
     openExtensionLibrary,
+    openImportInfo,
     openLoadingProject,
     openPreviewInfo,
     openSoundLibrary,
@@ -131,6 +141,7 @@ export {
     closeBackdropLibrary,
     closeCostumeLibrary,
     closeExtensionLibrary,
+    closeImportInfo,
     closeLoadingProject,
     closePreviewInfo,
     closeSpriteLibrary,
diff --git a/test/integration/project-loading.test.js b/test/integration/project-loading.test.js
index cb36f20e5..cb0477ec9 100644
--- a/test/integration/project-loading.test.js
+++ b/test/integration/project-loading.test.js
@@ -4,6 +4,7 @@ import SeleniumHelper from '../helpers/selenium-helper';
 const {
     clickText,
     clickXpath,
+    findByXpath,
     getDriver,
     getLogs,
     loadUri
@@ -30,6 +31,40 @@ describe('Loading scratch gui', () => {
     });
 
     describe('Loading projects by ID', () => {
+
+        test('Load 2.0 project using import modal', async () => {
+            await loadUri(uri);
+            await clickText('View 2.0 Project');
+            const el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
+            const projectId = '96708228';
+            await el.sendKeys(`scratch.mit.edu/projects/${projectId}`);
+            await clickXpath('//button[@title="viewproject"]');
+            await new Promise(resolve => setTimeout(resolve, 2000));
+            await clickXpath('//img[@title="Go"]');
+            await new Promise(resolve => setTimeout(resolve, 2000));
+            await clickXpath('//img[@title="Stop"]');
+            const logs = await getLogs();
+            await expect(logs).toEqual([]);
+        });
+
+        test('Invalid url when loading project through modal lets you try again', async () => {
+            await loadUri(uri);
+            await clickText('View 2.0 Project');
+            let el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
+            await el.sendKeys('thisisnotaurl');
+            await clickXpath('//button[@title="viewproject"]');
+            el = await findByXpath("//input[@placeholder='scratch.mit.edu/projects/123456789']");
+            await el.clear();
+            await el.sendKeys('scratch.mit.edu/projects/96708228');
+            await clickXpath('//button[@title="viewproject"]');
+            await new Promise(resolve => setTimeout(resolve, 2000));
+            await clickXpath('//img[@title="Go"]');
+            await new Promise(resolve => setTimeout(resolve, 2000));
+            await clickXpath('//img[@title="Stop"]');
+            const logs = await getLogs();
+            await expect(logs).toEqual([]);
+        });
+
         test('Load a project by ID directly through url', async () => {
             await driver.quit(); // Reset driver to test hitting # url directly
             driver = getDriver();
-- 
GitLab