diff --git a/package.json b/package.json index 7840f355b7fdeb23cf13e4b928c19c03911921b8..e44c89a024c70922b937446d231ca6829ae5e337 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.8.0", "eslint-plugin-react": "^7.5.1", - "file-loader": "1.1.6", + "file-loader": "1.1.9", "get-float-time-domain-data": "0.1.0", "get-user-media-promise": "1.1.1", "gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder", @@ -77,8 +77,8 @@ "react-ga": "2.4.1", "react-intl": "2.4.0", "react-intl-redux": "0.7.0", - "react-modal": "3.1.13", - "react-redux": "5.0.6", + "react-modal": "3.3.1", + "react-redux": "5.0.7", "react-responsive": "4.0.3", "react-style-proptype": "3.2.0", "react-tabs": "2.2.1", @@ -89,12 +89,12 @@ "redux-throttle": "0.1.1", "rimraf": "^2.6.1", "scratch-audio": "0.1.0-prerelease.1516198804", - "scratch-blocks": "0.1.0-prerelease.1518210548", + "scratch-blocks": "0.1.0-prerelease.1519256473", "scratch-l10n": "2.0.20180108132626", - "scratch-paint": "0.1.0-prerelease.20180209163938", + "scratch-paint": "0.2.0-prerelease.20180222192821", "scratch-render": "0.1.0-prerelease.1516837442", "scratch-storage": "0.4.0", - "scratch-vm": "0.1.0-prerelease.1518098842-prerelease.1518098861", + "scratch-vm": "0.1.0-prerelease.1519653784-prerelease.1519653801", "selenium-webdriver": "3.6.0", "startaudiocontext": "1.2.1", "style-loader": "^0.20.0", diff --git a/src/components/asset-panel/selector.jsx b/src/components/asset-panel/selector.jsx index 012206517b48133c965b9514c21a0926821575e7..b6970778155ca4ccd205092e593a310554e16821 100644 --- a/src/components/asset-panel/selector.jsx +++ b/src/components/asset-panel/selector.jsx @@ -13,6 +13,7 @@ const Selector = props => { items, selectedItemIndex, onDeleteClick, + onDuplicateClick, onItemClick } = props; @@ -30,6 +31,7 @@ const Selector = props => { selected={index === selectedItemIndex} onClick={onItemClick} onDeleteButtonClick={onDeleteClick} + onDuplicateButtonClick={onDuplicateClick} /> ))} </Box> @@ -58,6 +60,7 @@ Selector.propTypes = { name: PropTypes.string.isRequired })), onDeleteClick: PropTypes.func, + onDuplicateClick: PropTypes.func, onItemClick: PropTypes.func.isRequired, selectedItemIndex: PropTypes.number.isRequired }; diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index fe58243ba3bd4649fa7cb6ed4f146f194ef85ee8..488e705e88db5ce7cb76a4cc320446b0dab1b35b 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -35,16 +35,18 @@ const messages = defineMessages({ const GUIComponent = props => { const { + activeTabIndex, basePath, + blocksTabVisible, children, - enableExtensions, - intl, + costumesTabVisible, feedbackFormVisible, - vm, - previewInfoVisible, + intl, onExtensionButtonClick, - onTabSelect, - tabIndex, + onActivateTab, + previewInfoVisible, + soundsTabVisible, + vm, ...componentProps } = props; if (children) { @@ -87,9 +89,10 @@ const GUIComponent = props => { <Tabs className={tabClassNames.tabs} forceRenderTabPanel={true} // eslint-disable-line react/jsx-boolean-value + selectedIndex={activeTabIndex} selectedTabClassName={tabClassNames.tabSelected} selectedTabPanelClassName={tabClassNames.tabPanelSelected} - onSelect={onTabSelect} + onSelect={onActivateTab} > <TabList className={tabClassNames.tabList}> <Tab className={tabClassNames.tab}>Blocks</Tab> @@ -100,7 +103,7 @@ const GUIComponent = props => { <Box className={styles.blocksWrapper}> <Blocks grow={1} - isVisible={tabIndex === 0} // Blocks tab + isVisible={blocksTabVisible} options={{ media: `${basePath}static/blocks-media/` }} @@ -109,9 +112,7 @@ const GUIComponent = props => { </Box> <Box className={styles.extensionButtonContainer}> <button - className={classNames(styles.extensionButton, { - [styles.hidden]: !enableExtensions - })} + className={styles.extensionButton} title={intl.formatMessage(messages.addExtension)} onClick={onExtensionButtonClick} > @@ -124,10 +125,10 @@ const GUIComponent = props => { </Box> </TabPanel> <TabPanel className={tabClassNames.tabPanel}> - {tabIndex === 1 ? <CostumeTab vm={vm} /> : null} + {costumesTabVisible ? <CostumeTab vm={vm} /> : null} </TabPanel> <TabPanel className={tabClassNames.tabPanel}> - {tabIndex === 2 ? <SoundTab vm={vm} /> : null} + {soundsTabVisible ? <SoundTab vm={vm} /> : null} </TabPanel> </Tabs> </Box> @@ -162,15 +163,17 @@ const GUIComponent = props => { ); }; GUIComponent.propTypes = { + activeTabIndex: PropTypes.number, basePath: PropTypes.string, + blocksTabVisible: PropTypes.bool, children: PropTypes.node, - enableExtensions: PropTypes.bool, + costumesTabVisible: PropTypes.bool, feedbackFormVisible: PropTypes.bool, intl: intlShape.isRequired, + onActivateTab: PropTypes.func, onExtensionButtonClick: PropTypes.func, - onTabSelect: PropTypes.func, previewInfoVisible: PropTypes.bool, - tabIndex: PropTypes.number, + soundsTabVisible: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired }; GUIComponent.defaultProps = { diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index 17b562120996b073e35f53e85e056385e1ec2bb6..5fb521e9cf7af1a85d2951d9efddc1c1511220f0 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -9,7 +9,8 @@ const categories = { sensing: '#5CB1D6', sound: '#CF63CF', looks: '#9966FF', - motion: '#4C97FF' + motion: '#4C97FF', + list: '#FC662C' }; const MonitorComponent = props => ( diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index a53b487600ce9341a1b958ad04a4806ab81ea3e7..bedbbdbade90c98fe17572495521ca28478cdef9 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -252,7 +252,10 @@ class Blocks extends React.Component { } handleCustomProceduresClose (data) { this.props.onRequestCloseCustomProcedures(data); - this.workspace.refreshToolboxSelection_(); + const ws = this.workspace; + ws.refreshToolboxSelection_(); + // @todo ensure this does not break on localization + ws.toolbox_.scrollToCategoryByName('My Blocks'); } render () { /* eslint-disable no-unused-vars */ diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index be4c554fee26de0e9d0637266c76c9e40eed03e9..e22baebf203f86de87d305a6a0b79a58f74051e8 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -51,10 +51,21 @@ class CostumeTab extends React.Component { bindAll(this, [ 'handleSelectCostume', 'handleDeleteCostume', + 'handleDuplicateCostume', 'handleNewCostume', 'handleNewBlankCostume' ]); - this.state = {selectedCostumeIndex: 0}; + const { + editingTarget, + sprites, + stage + } = props; + const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage; + if (target && target.currentCostume) { + this.state = {selectedCostumeIndex: target.currentCostume}; + } else { + this.state = {selectedCostumeIndex: 0}; + } } componentWillReceiveProps (nextProps) { const { @@ -75,9 +86,14 @@ class CostumeTab extends React.Component { handleDeleteCostume (costumeIndex) { this.props.vm.deleteCostume(costumeIndex); } + handleDuplicateCostume (costumeIndex) { + this.props.vm.duplicateCostume(costumeIndex).then(() => { + this.setState({selectedCostumeIndex: costumeIndex + 1}); + }); + } handleNewCostume () { if (!this.props.vm.editingTarget) return; - const costumes = this.props.vm.editingTarget.sprite.costumes || []; + const costumes = this.props.vm.editingTarget.getCostumes() || []; this.setState({selectedCostumeIndex: Math.max(costumes.length - 1, 0)}); } handleNewBlankCostume () { @@ -145,6 +161,7 @@ class CostumeTab extends React.Component { items={target.costumes || []} selectedItemIndex={this.state.selectedCostumeIndex} onDeleteClick={target.costumes.length > 1 ? this.handleDeleteCostume : null} + onDuplicateClick={this.handleDuplicateCostume} onItemClick={this.handleSelectCostume} > {target.costumes ? @@ -186,7 +203,8 @@ CostumeTab.propTypes = { id: PropTypes.shape({ costumes: PropTypes.arrayOf(PropTypes.shape({ url: PropTypes.string, - name: PropTypes.string.isRequired + name: PropTypes.string.isRequired, + skinId: PropTypes.number })) }) }), diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 539705a5808dccd5991bd8ef0bbc3dac512288b8..bae6f9684ec5e3553a2d1eeb4e32eb10da4793d0 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -2,23 +2,21 @@ import AudioEngine from 'scratch-audio'; import PropTypes from 'prop-types'; import React from 'react'; import VM from 'scratch-vm'; -import bindAll from 'lodash.bindall'; import {connect} from 'react-redux'; -import {openExtensionLibrary} from '../reducers/modals.js'; +import {openExtensionLibrary} from '../reducers/modals'; +import { + activateTab, + BLOCKS_TAB_INDEX, + COSTUMES_TAB_INDEX, + SOUNDS_TAB_INDEX +} from '../reducers/editor-tab'; import vmListenerHOC from '../lib/vm-listener-hoc.jsx'; import GUIComponent from '../components/gui/gui.jsx'; class GUI extends React.Component { - constructor (props) { - super(props); - bindAll(this, [ - 'handleTabSelect' - ]); - this.state = {tabIndex: 0}; - } componentDidMount () { this.audioEngine = new AudioEngine(); this.props.vm.attachAudioEngine(this.audioEngine); @@ -34,9 +32,6 @@ class GUI extends React.Component { componentWillUnmount () { this.props.vm.stopAll(); } - handleTabSelect (tabIndex) { - this.setState({tabIndex}); - } render () { const { children, @@ -46,10 +41,7 @@ class GUI extends React.Component { } = this.props; return ( <GUIComponent - enableExtensions={window.location.search.includes('extensions')} - tabIndex={this.state.tabIndex} vm={vm} - onTabSelect={this.handleTabSelect} {...componentProps} > {children} @@ -69,12 +61,17 @@ GUI.propTypes = { GUI.defaultProps = GUIComponent.defaultProps; const mapStateToProps = state => ({ + activeTabIndex: state.editorTab.activeTabIndex, + blocksTabVisible: state.editorTab.activeTabIndex === BLOCKS_TAB_INDEX, + costumesTabVisible: state.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, feedbackFormVisible: state.modals.feedbackForm, - previewInfoVisible: state.modals.previewInfo + previewInfoVisible: state.modals.previewInfo, + soundsTabVisible: state.editorTab.activeTabIndex === SOUNDS_TAB_INDEX }); const mapDispatchToProps = dispatch => ({ - onExtensionButtonClick: () => dispatch(openExtensionLibrary()) + onExtensionButtonClick: () => dispatch(openExtensionLibrary()), + onActivateTab: tab => dispatch(activateTab(tab)) }); const ConnectedGUI = connect( diff --git a/src/containers/sound-editor.jsx b/src/containers/sound-editor.jsx index 781b2cc5fe51f9c068c46a7ae7467d0c93e9a10f..bbe032c0b67bf897fb2ecdb6edf26cdc04b5c41f 100644 --- a/src/containers/sound-editor.jsx +++ b/src/containers/sound-editor.jsx @@ -1,6 +1,7 @@ import bindAll from 'lodash.bindall'; import PropTypes from 'prop-types'; import React from 'react'; +import WavEncoder from 'wav-encoder'; import {connect} from 'react-redux'; @@ -9,6 +10,7 @@ import {computeChunkedRMS} from '../lib/audio/audio-util.js'; import AudioEffects from '../lib/audio/audio-effects.js'; import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx'; import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js'; +import log from '../lib/log.js'; const UNDO_STACK_SIZE = 99; @@ -72,11 +74,25 @@ class SoundEditor extends React.Component { } this.undoStack.push(this.copyCurrentBuffer()); } + // Encode the new sound into a wav so that it can be stored + let wavBuffer = null; + try { + wavBuffer = WavEncoder.encode.sync({ + sampleRate: sampleRate, + channelData: [samples] + }); + } catch (e) { + // This error state is mostly for the mock sounds used during testing. + // Any incorrect sound buffer trying to get interpretd as a Wav file + // should yield this error. + log.error(`Encountered error while trying to encode sound update: ${e}`); + } + this.resetState(samples, sampleRate); this.props.onUpdateSoundBuffer( this.props.soundIndex, - this.audioBufferPlayer.buffer - ); + this.audioBufferPlayer.buffer, + wavBuffer ? new Uint8Array(wavBuffer) : new Uint8Array()); } handlePlay () { this.audioBufferPlayer.play( diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index 9e30ba2edf81b5ec045de55a89fc644b44bfd340..8686ae6f90528c6ad010e40cc72965730dec6e7c 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -26,6 +26,7 @@ class SoundTab extends React.Component { bindAll(this, [ 'handleSelectSound', 'handleDeleteSound', + 'handleDuplicateSound', 'handleNewSound' ]); this.state = {selectedSoundIndex: 0}; @@ -51,6 +52,15 @@ class SoundTab extends React.Component { handleDeleteSound (soundIndex) { this.props.vm.deleteSound(soundIndex); + if (soundIndex >= this.state.selectedSoundIndex) { + this.setState({selectedSoundIndex: Math.max(0, soundIndex - 1)}); + } + } + + handleDuplicateSound (soundIndex) { + this.props.vm.duplicateSound(soundIndex).then(() => { + this.setState({selectedSoundIndex: soundIndex + 1}); + }); } handleNewSound () { @@ -113,6 +123,7 @@ class SoundTab extends React.Component { }))} selectedItemIndex={this.state.selectedSoundIndex} onDeleteClick={this.handleDeleteSound} + onDuplicateClick={this.handleDuplicateSound} onItemClick={this.handleSelectSound} > {sprite.sounds && sprite.sounds[this.state.selectedSoundIndex] ? ( diff --git a/src/containers/sprite-selector-item.jsx b/src/containers/sprite-selector-item.jsx index d1a3e4b3fac0a29cc74fafbdd8d19dd799b2a534..44253e6cfa66644d82d4987db97c38f3edeafc77 100644 --- a/src/containers/sprite-selector-item.jsx +++ b/src/containers/sprite-selector-item.jsx @@ -22,7 +22,8 @@ class SpriteSelectorItem extends React.Component { e.preventDefault(); this.props.onClick(this.props.id); } - handleDelete () { + handleDelete (e) { + e.stopPropagation(); // To prevent from bubbling back to handleClick // @todo add i18n here // eslint-disable-next-line no-alert if (window.confirm('Are you sure you want to delete this?')) { diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx index 5095f86dd074cc221c7561f12886a6ecbc7b0ed9..c386112b3858cbfbc8bc4040c84bff2ec88ac88b 100644 --- a/src/containers/stage.jsx +++ b/src/containers/stage.jsx @@ -31,6 +31,7 @@ class Stage extends React.Component { 'onMouseDown', 'onStartDrag', 'onStopDrag', + 'onWheel', 'updateRect', 'questionListener', 'setCanvas' @@ -98,6 +99,7 @@ class Stage extends React.Component { document.addEventListener('touchend', this.onMouseUp); canvas.addEventListener('mousedown', this.onMouseDown); canvas.addEventListener('touchstart', this.onMouseDown); + canvas.addEventListener('wheel', this.onWheel); } detachMouseEvents (canvas) { document.removeEventListener('mousemove', this.onMouseMove); @@ -106,6 +108,7 @@ class Stage extends React.Component { document.removeEventListener('touchend', this.onMouseUp); canvas.removeEventListener('mousedown', this.onMouseDown); canvas.removeEventListener('touchstart', this.onMouseDown); + canvas.removeEventListener('wheel', this.onWheel); } attachRectEvents () { window.addEventListener('resize', this.updateRect); @@ -232,6 +235,13 @@ class Stage extends React.Component { this.setState({colorInfo: null}); } } + onWheel (e) { + const data = { + deltaX: e.deltaX, + deltaY: e.deltaY + }; + this.props.vm.postIOData('mouseWheel', data); + } cancelMouseDownTimeout () { if (this.state.mouseDownTimeoutId !== null) { clearTimeout(this.state.mouseDownTimeoutId); diff --git a/src/lib/blocks.js b/src/lib/blocks.js index af15abc8760295c9b61674c1ecf68ffaca19c13a..06c9a459c02bcc3005712d4e503a4667f60b2dfb 100644 --- a/src/lib/blocks.js +++ b/src/lib/blocks.js @@ -36,15 +36,15 @@ export default function (vm) { }; const costumesMenu = function () { - if (vm.editingTarget && vm.editingTarget.sprite.costumes.length > 0) { - return vm.editingTarget.sprite.costumes.map(costume => [costume.name, costume.name]); + if (vm.editingTarget && vm.editingTarget.getCostumes().length > 0) { + return vm.editingTarget.getCostumes().map(costume => [costume.name, costume.name]); } return [['', '']]; }; const backdropsMenu = function () { - if (vm.runtime.targets[0] && vm.runtime.targets[0].sprite.costumes.length > 0) { - return vm.runtime.targets[0].sprite.costumes.map(costume => [costume.name, costume.name]) + if (vm.runtime.targets[0] && vm.runtime.targets[0].getCostumes().length > 0) { + return vm.runtime.targets[0].getCostumes().map(costume => [costume.name, costume.name]) .concat([['next backdrop', 'next backdrop'], ['previous backdrop', 'previous backdrop']]); } return [['', '']]; diff --git a/src/lib/opcode-labels.js b/src/lib/opcode-labels.js index c4fa07bcfd998f81962c1a50388675797dc49029..e877a2a51290d29f33534b0c5e8cccf589bae312 100644 --- a/src/lib/opcode-labels.js +++ b/src/lib/opcode-labels.js @@ -37,10 +37,10 @@ const opcodeMap = { labelFn: params => params.VARIABLE }, data_listcontents: { - category: 'data', + category: 'list', labelFn: params => params.LIST }, - + // Sound sound_volume: { category: 'sound', diff --git a/src/reducers/editor-tab.js b/src/reducers/editor-tab.js new file mode 100644 index 0000000000000000000000000000000000000000..21b3534e29fb2700a4a2469886445d4e2da0b557 --- /dev/null +++ b/src/reducers/editor-tab.js @@ -0,0 +1,37 @@ +const ACTIVATE_TAB = 'scratch-gui/navigation/ACTIVATE_TAB'; + +// Constants use numbers to make it easier to work with react-tabs +const BLOCKS_TAB_INDEX = 0; +const COSTUMES_TAB_INDEX = 1; +const SOUNDS_TAB_INDEX = 2; + +const initialState = { + activeTabIndex: BLOCKS_TAB_INDEX +}; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + switch (action.type) { + case ACTIVATE_TAB: + return Object.assign({}, state, { + activeTabIndex: action.activeTabIndex + }); + default: + return state; + } +}; + +const activateTab = function (tab) { + return { + type: ACTIVATE_TAB, + activeTabIndex: tab + }; +}; + +export { + reducer as default, + activateTab, + BLOCKS_TAB_INDEX, + COSTUMES_TAB_INDEX, + SOUNDS_TAB_INDEX +}; diff --git a/src/reducers/gui.js b/src/reducers/gui.js index 49b9a0120394a6e1992074c0d4c459c1ec7693ae..d89a195c9dc2d485082337e5a0961f18e2e63ddd 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -2,6 +2,7 @@ import {combineReducers} from 'redux'; import colorPickerReducer from './color-picker'; import customProceduresReducer from './custom-procedures'; import blockDragReducer from './block-drag'; +import editorTabReducer from './editor-tab'; import hoveredTargetReducer from './hovered-target'; import intlReducer from './intl'; import modalReducer from './modals'; @@ -17,6 +18,7 @@ export default combineReducers({ blockDrag: blockDragReducer, colorPicker: colorPickerReducer, customProcedures: customProceduresReducer, + editorTab: editorTabReducer, hoveredTarget: hoveredTargetReducer, intl: intlReducer, stageSize: stageSizeReducer, diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js index c8ead90725e7197bf1ab966e94d05a7145ae88e5..b63ea2bf66ac321ae9dc68f79906af68c21d9d16 100644 --- a/test/helpers/selenium-helper.js +++ b/test/helpers/selenium-helper.js @@ -25,6 +25,7 @@ class SeleniumHelper { // List of useful xpath scopes for finding elements return { blocksTab: "*[@id='react-tabs-1']", + costumesTab: "*[@id='react-tabs-3']", modal: '*[@class="ReactModalPortal"]', reportedValue: '*[@class="blocklyDropDownContent"]', soundsTab: "*[@id='react-tabs-5']", diff --git a/test/integration/costumes.test.js b/test/integration/costumes.test.js index 98f31629a591b97ff5024b3c94db2dc605100f11..f07c3d0f309cd27ecdddc7c7b2eaa769f2c8448a 100644 --- a/test/integration/costumes.test.js +++ b/test/integration/costumes.test.js @@ -7,7 +7,9 @@ const { findByXpath, getDriver, getLogs, - loadUri + loadUri, + rightClickText, + scope } = new SeleniumHelper(); const uri = path.resolve(__dirname, '../../build/index.html'); @@ -36,6 +38,22 @@ describe('Working with costumes', () => { await expect(logs).toEqual([]); }); + test('Duplicating a costume', async () => { + await loadUri(uri); + await clickXpath('//button[@title="tryit"]'); + await clickText('Costumes'); + + await rightClickText('costume1', scope.costumesTab); + await clickText('duplicate', scope.costumesTab); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for duplication to finish + + // Make sure the duplicated costume is named correctly. + await clickText('costume3', scope.costumesTab); + + const logs = await getLogs(); + await expect(logs).toEqual([]); + }); + test('Adding a backdrop', async () => { await loadUri(uri); await clickXpath('//button[@title="tryit"]'); diff --git a/test/integration/sounds.test.js b/test/integration/sounds.test.js index 244ebb9b454d7b5d88b17b243db47fc9e4ad44ef..e3154868417ae9194c3e0c4eb736ca3a4f70cdc9 100644 --- a/test/integration/sounds.test.js +++ b/test/integration/sounds.test.js @@ -63,6 +63,22 @@ describe('Working with sounds', () => { await expect(logs).toEqual([]); }); + test('Duplicating a sound', async () => { + await loadUri(uri); + await clickXpath('//button[@title="tryit"]'); + await clickText('Sounds'); + + await rightClickText('Meow', scope.soundsTab); + await clickText('duplicate', scope.soundsTab); + await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for error + + // Make sure the duplicated sound is named correctly. + await clickText('Meow2', scope.soundsTab); + + const logs = await getLogs(); + await expect(logs).toEqual([]); + }); + // Regression test for gui issue #1320 test('Switching sprites with different numbers of sounds', async () => { await loadUri(uri); diff --git a/webpack.config.js b/webpack.config.js index c92f72ea718775322dbd7b29aca36dedbbcb5734..389cf8abd6db12e1deb277d680469a688d518cb6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -111,10 +111,6 @@ module.exports = { from: 'node_modules/scratch-blocks/media', to: 'static/blocks-media' }]), - new CopyWebpackPlugin([{ - from: 'node_modules/scratch-vm/dist/node/assets', - to: 'static/extension-assets' - }]), new CopyWebpackPlugin([{ from: 'extensions/**', to: 'static',