├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci-cd.yml │ ├── signature-assistant.yml │ └── update-i18n.yml ├── .gitignore ├── .husky ├── .gitattributes └── commit-msg ├── .jsdoc.json ├── .npmignore ├── .nvmrc ├── .tx └── config ├── CHANGELOG.md ├── LICENSE ├── README.md ├── TRADEMARK ├── commitlint.config.js ├── docs └── extensions.md ├── package-lock.json ├── package.json ├── release.config.js ├── renovate.json5 ├── src ├── .eslintrc.js ├── blocks │ ├── scratch3_control.js │ ├── scratch3_core_example.js │ ├── scratch3_data.js │ ├── scratch3_event.js │ ├── scratch3_looks.js │ ├── scratch3_motion.js │ ├── scratch3_operators.js │ ├── scratch3_procedures.js │ ├── scratch3_sensing.js │ └── scratch3_sound.js ├── dispatch │ ├── central-dispatch.js │ ├── shared-dispatch.js │ └── worker-dispatch.js ├── engine │ ├── adapter.js │ ├── block-utility.js │ ├── blocks-execute-cache.js │ ├── blocks-runtime-cache.js │ ├── blocks.js │ ├── comment.js │ ├── execute.js │ ├── monitor-record.js │ ├── mutation-adapter.js │ ├── profiler.js │ ├── runtime.js │ ├── scratch-blocks-constants.js │ ├── sequencer.js │ ├── stage-layering.js │ ├── target.js │ ├── thread.js │ └── variable.js ├── extension-support │ ├── argument-type.js │ ├── block-type.js │ ├── define-messages.js │ ├── extension-manager.js │ ├── extension-metadata.js │ ├── extension-worker.js │ ├── reporter-scope.js │ └── target-type.js ├── extensions │ ├── scratch3_boost │ │ └── index.js │ ├── scratch3_ev3 │ │ └── index.js │ ├── scratch3_gdx_for │ │ ├── index.js │ │ └── scratch-link-device-adapter.js │ ├── scratch3_makeymakey │ │ └── index.js │ ├── scratch3_microbit │ │ └── index.js │ ├── scratch3_music │ │ ├── assets │ │ │ ├── drums │ │ │ │ ├── 1-snare.mp3 │ │ │ │ ├── 10-wood-block.mp3 │ │ │ │ ├── 11-cowbell.mp3 │ │ │ │ ├── 12-triangle.mp3 │ │ │ │ ├── 13-bongo.mp3 │ │ │ │ ├── 14-conga.mp3 │ │ │ │ ├── 15-cabasa.mp3 │ │ │ │ ├── 16-guiro.mp3 │ │ │ │ ├── 17-vibraslap.mp3 │ │ │ │ ├── 18-cuica.mp3 │ │ │ │ ├── 2-bass-drum.mp3 │ │ │ │ ├── 3-side-stick.mp3 │ │ │ │ ├── 4-crash-cymbal.mp3 │ │ │ │ ├── 5-open-hi-hat.mp3 │ │ │ │ ├── 6-closed-hi-hat.mp3 │ │ │ │ ├── 7-tambourine.mp3 │ │ │ │ ├── 8-hand-clap.mp3 │ │ │ │ └── 9-claves.mp3 │ │ │ └── instruments │ │ │ │ ├── 1-piano │ │ │ │ ├── 108.mp3 │ │ │ │ ├── 24.mp3 │ │ │ │ ├── 36.mp3 │ │ │ │ ├── 48.mp3 │ │ │ │ ├── 60.mp3 │ │ │ │ ├── 72.mp3 │ │ │ │ ├── 84.mp3 │ │ │ │ └── 96.mp3 │ │ │ │ ├── 10-clarinet │ │ │ │ ├── 48.mp3 │ │ │ │ └── 60.mp3 │ │ │ │ ├── 11-saxophone │ │ │ │ ├── 36.mp3 │ │ │ │ ├── 60.mp3 │ │ │ │ └── 84.mp3 │ │ │ │ ├── 12-flute │ │ │ │ ├── 60.mp3 │ │ │ │ └── 72.mp3 │ │ │ │ ├── 13-wooden-flute │ │ │ │ ├── 60.mp3 │ │ │ │ └── 72.mp3 │ │ │ │ ├── 14-bassoon │ │ │ │ ├── 36.mp3 │ │ │ │ ├── 48.mp3 │ │ │ │ └── 60.mp3 │ │ │ │ ├── 15-choir │ │ │ │ ├── 48.mp3 │ │ │ │ ├── 60.mp3 │ │ │ │ └── 72.mp3 │ │ │ │ ├── 16-vibraphone │ │ │ │ ├── 60.mp3 │ │ │ │ └── 72.mp3 │ │ │ │ ├── 17-music-box │ │ │ │ └── 60.mp3 │ │ │ │ ├── 18-steel-drum │ │ │ │ └── 60.mp3 │ │ │ │ ├── 19-marimba │ │ │ │ └── 60.mp3 │ │ │ │ ├── 2-electric-piano │ │ │ │ └── 60.mp3 │ │ │ │ ├── 20-synth-lead │ │ │ │ └── 60.mp3 │ │ │ │ ├── 21-synth-pad │ │ │ │ └── 60.mp3 │ │ │ │ ├── 3-organ │ │ │ │ └── 60.mp3 │ │ │ │ ├── 4-guitar │ │ │ │ └── 60.mp3 │ │ │ │ ├── 5-electric-guitar │ │ │ │ └── 60.mp3 │ │ │ │ ├── 6-bass │ │ │ │ ├── 36.mp3 │ │ │ │ └── 48.mp3 │ │ │ │ ├── 7-pizzicato │ │ │ │ └── 60.mp3 │ │ │ │ ├── 8-cello │ │ │ │ ├── 36.mp3 │ │ │ │ ├── 48.mp3 │ │ │ │ └── 60.mp3 │ │ │ │ └── 9-trombone │ │ │ │ ├── 36.mp3 │ │ │ │ ├── 48.mp3 │ │ │ │ └── 60.mp3 │ │ ├── index.js │ │ └── manifest.js │ ├── scratch3_pen │ │ └── index.js │ ├── scratch3_speech2text │ │ └── index.js │ ├── scratch3_text2speech │ │ └── index.js │ ├── scratch3_translate │ │ └── index.js │ ├── scratch3_video_sensing │ │ ├── debug.js │ │ ├── index.js │ │ ├── library.js │ │ ├── math.js │ │ └── view.js │ └── scratch3_wedo2 │ │ └── index.js ├── import │ ├── load-costume.js │ └── load-sound.js ├── index.js ├── io │ ├── ble.js │ ├── bt.js │ ├── clock.js │ ├── cloud.js │ ├── keyboard.js │ ├── mouse.js │ ├── mouseWheel.js │ ├── userData.js │ └── video.js ├── playground │ ├── benchmark.css │ ├── benchmark.js │ ├── index.html │ ├── suite.css │ ├── suite.html │ ├── suite.js │ ├── video-sensing.html │ └── video-sensing.js ├── serialization │ ├── deserialize-assets.js │ ├── sb2.js │ ├── sb2_specmap.js │ ├── sb3.js │ └── serialize-assets.js ├── sprites │ ├── rendered-target.js │ └── sprite.js ├── util │ ├── base64-util.js │ ├── cast.js │ ├── clone.js │ ├── color.js │ ├── fetch-with-timeout.js │ ├── get-monitor-id.js │ ├── jsonrpc.js │ ├── log.js │ ├── math-util.js │ ├── maybe-format-message.js │ ├── new-block-ids.js │ ├── rateLimiter.js │ ├── scratch-link-websocket.js │ ├── string-util.js │ ├── task-queue.js │ ├── timer.js │ ├── uid.js │ ├── variable-util.js │ └── xml-escape.js └── virtual-machine.js ├── test ├── .eslintrc.js ├── fixtures │ ├── block-to-workspace-comments-without-scripts.sb2 │ ├── block-to-workspace-comments.sb2 │ ├── broadcast_special_chars.sb2 │ ├── broadcast_special_chars.sb3 │ ├── cat.sprite2 │ ├── cat.sprite3 │ ├── clone-cleanup.sb2 │ ├── cloud_variables_exceeded_limit.sb2 │ ├── cloud_variables_exceeded_limit.sb3 │ ├── cloud_variables_limit.sb2 │ ├── cloud_variables_limit.sb3 │ ├── cloud_variables_local.sb2 │ ├── cloud_variables_local.sb3 │ ├── cloud_variables_simple.sb2 │ ├── cloud_variables_simple.sb3 │ ├── comments.sb2 │ ├── comments.sb3 │ ├── comments_no_duplicate_id_serialization.sb3 │ ├── complex.sb2 │ ├── control.sb2 │ ├── corrupt_png.sb2 │ ├── corrupt_png.sb3 │ ├── corrupt_png.sprite2 │ ├── corrupt_png.sprite3 │ ├── corrupt_sound.sb3 │ ├── corrupt_svg.sb2 │ ├── corrupt_svg.sb3 │ ├── corrupt_svg.sprite2 │ ├── corrupt_svg.sprite3 │ ├── data.sb2 │ ├── default.sb2 │ ├── default.sb3 │ ├── default_nested.sb2 │ ├── demo.json │ ├── dispatch-test-service.js │ ├── dispatch-test-worker-shim.js │ ├── dispatch-test-worker.js │ ├── draggable.sb3 │ ├── edge-triggered-hat.sb3 │ ├── event.sb2 │ ├── events.json │ ├── example_sprite.sprite2 │ ├── execute │ │ ├── README.md │ │ ├── broadcast-wait-arg-change.sb2 │ │ ├── control-if-false-then-else.sb2 │ │ ├── control-if-false-then.sb2 │ │ ├── control-if-true-then-else.sb2 │ │ ├── control-if-true-then.sb2 │ │ ├── control-stop-all-leaks.sb2 │ │ ├── data-operators-global.sb2 │ │ ├── data-operators-local.sb2 │ │ ├── data-reporter-contents-global.sb2 │ │ ├── data-reporter-contents-local.sb2 │ │ ├── event-broadcast-and-wait-can-continue-same-tick.sb2 │ │ ├── event-when-green-flag.sb2 │ │ ├── events-broadcast-and-wait-yields-a-tick.sb2 │ │ ├── hat-thread-execution.sb2 │ │ ├── monitors-stage-name.sb2 │ │ ├── operators-not-blank.sb2 │ │ ├── order-changes-back-2-broadcast-wait.sb2 │ │ ├── order-changes-backwards-2-broadcast-and-wait-repeat-message.sb2 │ │ ├── order-changes-backwards-2-broadcast-and-wait.sb2 │ │ ├── order-changes-backwards-2-broadcast-no-wait.sb2 │ │ ├── order-changes-backwards-2-broadcast-wait.sb2 │ │ ├── order-changes-backwards-2-continuous.sb2 │ │ ├── order-changes-backwards-2-threads-broadcast-wait.sb2 │ │ ├── order-changes-forewards-2-broadcast-wait.sb2 │ │ ├── order-changes-front-2-broadcast-wait.sb2 │ │ ├── order-clones-backwards-2-broadcast-wait.sb2 │ │ ├── order-clones-backwards-broadcast-wait.sb2 │ │ ├── order-clones-static-2.sb2 │ │ ├── order-immobile-stage.sb2 │ │ ├── order-library-reverse.sb2 │ │ ├── order-library-reverse.sb3 │ │ ├── order-library.sb2 │ │ ├── order-library.sb3 │ │ ├── procedures-boolean-reporter-bug.sb2 │ │ ├── procedures-nested-missing-boolean-param.sb2 │ │ ├── procedures-nested-missing-no-param.sb2 │ │ ├── procedures-nested-missing-number-param.sb2 │ │ ├── procedures-nested-missing-string-param.sb2 │ │ ├── procedures-number-number-boolean.sb2 │ │ ├── procedures-param-outside-boolean.sb2 │ │ ├── procedures-param-outside-number.sb2 │ │ ├── procedures-param-outside-string.sb2 │ │ ├── procedures-recursive-default-boolean.sb2 │ │ ├── procedures-recursive-default-number.sb2 │ │ ├── procedures-recursive-default-string.sb2 │ │ ├── sensing-get-attribute-of-stage-alt-name.sb2 │ │ └── sprite-number-name.sb2 │ ├── fake-bitmap-adapter.js │ ├── fake-renderer.js │ ├── hat-execution-order.sb2 │ ├── invisible-tempo-monitor-no-other-music-blocks.sb2 │ ├── invisible-video-monitor.sb2 │ ├── list-monitor-rename.sb3 │ ├── load-extensions │ │ ├── README.md │ │ ├── confirm-load │ │ │ ├── ev3-simple-project.sb3 │ │ │ ├── microbit-simple-project.sb3 │ │ │ ├── music-simple-project.sb2 │ │ │ ├── music-simple-project.sb3 │ │ │ ├── pen-dolphin-3d.sb2 │ │ │ ├── pen-dolphin-3d.sb3 │ │ │ ├── pen-simple-project.sb2 │ │ │ ├── pen-simple-project.sb3 │ │ │ ├── text2speech-simple-project.sb3 │ │ │ ├── videoSensing-simple-project.sb2 │ │ │ ├── videoSensing-simple-project.sb3 │ │ │ ├── wedo2-simple-project.sb2 │ │ │ └── wedo2-simple-project.sb3 │ │ ├── music-visible-monitor-no-blocks.sb2 │ │ └── video-state │ │ │ ├── videoState-off.sb2 │ │ │ └── videoState-on-transparency-0.sb2 │ ├── looks.sb2 │ ├── make-test-storage.js │ ├── missing_png.sb2 │ ├── missing_png.sb3 │ ├── missing_png.sprite2 │ ├── missing_png.sprite3 │ ├── missing_sound.sb3 │ ├── missing_svg.sb2 │ ├── missing_svg.sb3 │ ├── missing_svg.sprite2 │ ├── missing_svg.sprite3 │ ├── mock-timer.js │ ├── monitored_variables.sb3 │ ├── monitors.sb2 │ ├── monitors.sb3 │ ├── motion.sb2 │ ├── offline-custom-assets.sb2 │ ├── ordering.sb2 │ ├── origin-absent.sb3 │ ├── origin.sb3 │ ├── pen.sb2 │ ├── procedure.sb2 │ ├── readProjectFile.js │ ├── saythink-and-wait.sb2 │ ├── sb2-from-sb1-missing-backdrop-image.sb2 │ ├── sensing.sb2 │ ├── simple-stack.js │ ├── single_sound.sb │ ├── sound.sb2 │ ├── sprite.json │ ├── stack-click.sb2 │ ├── test-compare.js │ ├── timer-greater-than-hat.sb2 │ ├── timer-monitor.sb3 │ ├── top-level-reporters.sb3 │ ├── top-level-variable-reporter.sb2 │ ├── unknown-opcode-as-reporter-block.sb2 │ ├── unknown-opcode-in-c-block.sb2 │ ├── unknown-opcode.sb2 │ ├── variable_characters.sb2 │ ├── variable_characters.sb3 │ ├── visible-tempo-monitor-no-other-music-blocks.sb2 │ ├── visible-video-monitor-and-video-blocks.sb2 │ ├── visible-video-monitor-no-other-video-blocks.sb2 │ └── when-clicked.sb2 ├── integration │ ├── addSprite.js │ ├── block_to_workspace_comment_import.js │ ├── block_to_workspace_comment_import_no_scripts.js │ ├── broadcast_special_chars_sb2.js │ ├── broadcast_special_chars_sb3.js │ ├── clone-cleanup.js │ ├── cloud_variables_sb2.js │ ├── cloud_variables_sb3.js │ ├── comments.js │ ├── comments_sb3.js │ ├── complex.js │ ├── control.js │ ├── data.js │ ├── delete-and-restore-sprite.js │ ├── event.js │ ├── execute.js │ ├── hat-execution-order.js │ ├── hat-threads-run-every-frame.js │ ├── import-sb.js │ ├── import-sb2-from-object.js │ ├── import_nested_sb2.js │ ├── import_sb2.js │ ├── internal-extension.js │ ├── list-monitor-rename.js │ ├── load-extensions.js │ ├── load-sb2-originally-sb1-without-backdrop-image.js │ ├── looks.js │ ├── monitor-threads-run-every-frame.js │ ├── monitors_sb2.js │ ├── monitors_sb2_to_sb3.js │ ├── monitors_sb3.js │ ├── motion.js │ ├── offline-custom-assets.js │ ├── pen.js │ ├── procedure.js │ ├── runId.js │ ├── running_project_changed_state.js │ ├── saythink-and-wait.js │ ├── sb2-import-extension-monitors.js │ ├── sb2_corrupted_png.js │ ├── sb2_corrupted_svg.js │ ├── sb2_missing_png.js │ ├── sb2_missing_svg.js │ ├── sb3-roundtrip.js │ ├── sb3_corrupted_png.js │ ├── sb3_corrupted_sound.js │ ├── sb3_corrupted_svg.js │ ├── sb3_missing_png.js │ ├── sb3_missing_sound.js │ ├── sb3_missing_svg.js │ ├── sensing.js │ ├── sound.js │ ├── sprite2_corrupted_png.js │ ├── sprite2_corrupted_svg.js │ ├── sprite2_missing_png.js │ ├── sprite2_missing_svg.js │ ├── sprite3_corrupted_png.js │ ├── sprite3_corrupted_svg.js │ ├── sprite3_missing_png.js │ ├── sprite3_missing_svg.js │ ├── stack-click.js │ ├── unknown-opcode-as-reporter-block.js │ ├── unknown-opcode-in-c-block.js │ ├── unknown-opcode.js │ ├── variable_monitor_reset.js │ ├── variable_special_chars_sb2.js │ └── variable_special_chars_sb3.js └── unit │ ├── blocks_control.js │ ├── blocks_data.js │ ├── blocks_data_infinity.js │ ├── blocks_event.js │ ├── blocks_looks.js │ ├── blocks_motion.js │ ├── blocks_operators.js │ ├── blocks_operators_infinity.js │ ├── blocks_procedures.js │ ├── blocks_sensing.js │ ├── blocks_sounds.js │ ├── dispatch.js │ ├── engine_adapter.js │ ├── engine_blocks.js │ ├── engine_mutation-adapter.js │ ├── engine_runtime.js │ ├── engine_sequencer.js │ ├── engine_target.js │ ├── engine_thread.js │ ├── engine_variable.js │ ├── extension_conversion.js │ ├── extension_microbit.js │ ├── extension_music.js │ ├── extension_text_to_speech.js │ ├── extension_video_sensing.js │ ├── extension_video_sensing_center.png │ ├── extension_video_sensing_down-10.png │ ├── extension_video_sensing_left-10.png │ ├── extension_video_sensing_left-5.png │ ├── io_clock.js │ ├── io_cloud.js │ ├── io_keyboard.js │ ├── io_mouse.js │ ├── io_mousewheel.js │ ├── io_scratchBLE.js │ ├── io_scratchBT.js │ ├── io_userData.js │ ├── maybe_format_message.js │ ├── mock-timer.js │ ├── project_changed_state.js │ ├── project_changed_state_blocks.js │ ├── project_load_changed_state.js │ ├── serialization_sb2.js │ ├── serialization_sb3.js │ ├── spec.js │ ├── sprites_rendered-target.js │ ├── util_base64.js │ ├── util_cast.js │ ├── util_color.js │ ├── util_jsonrpc-web-socket.js │ ├── util_jsonrpc.js │ ├── util_math.js │ ├── util_new-block-ids.js │ ├── util_rateLimiter.js │ ├── util_string.js │ ├── util_task-queue.js │ ├── util_timer.js │ ├── util_variable.js │ ├── util_xml.js │ ├── virtual-machine.js │ └── vm_collectAssets.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | [*.{js,html}] 11 | indent_style = space 12 | 13 | [.circleci/config.yml] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/* 2 | dist/* 3 | node_modules/* 4 | playground/* 5 | benchmark/* 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['scratch', 'scratch/node', 'scratch/es6'] 3 | }; 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly specify line endings for as many files as possible. 5 | # People who (for example) rsync between Windows and Linux need this. 6 | 7 | # File types which we know are binary 8 | *.sb2 binary 9 | 10 | # Prefer LF for most file types 11 | *.css text eol=lf 12 | *.frag text eol=lf 13 | *.htm text eol=lf 14 | *.html text eol=lf 15 | *.iml text eol=lf 16 | *.js text eol=lf 17 | *.js.map text eol=lf 18 | *.json text eol=lf 19 | *.json5 text eol=lf 20 | *.md text eol=lf 21 | *.vert text eol=lf 22 | *.xml text eol=lf 23 | *.yml text eol=lf 24 | 25 | # Prefer LF for these files 26 | .editorconfig text eol=lf 27 | .eslintignore text eol=lf 28 | .eslintrc text eol=lf 29 | .gitattributes text eol=lf 30 | .gitignore text eol=lf 31 | .gitmodules text eol=lf 32 | .npmignore text eol=lf 33 | LICENSE text eol=lf 34 | Makefile text eol=lf 35 | README text eol=lf 36 | TRADEMARK text eol=lf 37 | 38 | # Use CRLF for Windows-specific file types 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected Behavior 2 | 3 | _Please describe what should happen_ 4 | 5 | ### Actual Behavior 6 | 7 | _Describe what actually happens_ 8 | 9 | ### Steps to Reproduce 10 | 11 | _Explain what someone needs to do in order to see what's described in *Actual behavior* above_ 12 | 13 | ### Operating System and Browser 14 | 15 | _e.g. Mac OS 10.11.6 Safari 10.0_ 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Resolves 2 | 3 | _What Github issue does this resolve (please include link)?_ 4 | 5 | ### Proposed Changes 6 | 7 | _Describe what this Pull Request does_ 8 | 9 | ### Reason for Changes 10 | 11 | _Explain why these changes should be made_ 12 | 13 | ### Test Coverage 14 | 15 | _Please show how you have added tests to cover your changes_ 16 | -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: 4 | pull_request: # Runs whenever a pull request is created or updated 5 | push: # Runs whenever a commit is pushed to the repository... 6 | branches: [main, develop, hotfix/*] # ...on any of these branches 7 | workflow_dispatch: # Allows you to run this workflow manually from the Actions tab 8 | 9 | concurrency: 10 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 11 | cancel-in-progress: true 12 | 13 | permissions: 14 | contents: write # publish a GitHub release 15 | pages: write # deploy to GitHub Pages 16 | issues: write # comment on released issues 17 | pull-requests: write # comment on released pull requests 18 | 19 | jobs: 20 | ci-cd: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 24 | - uses: wagoid/commitlint-github-action@5ce82f5d814d4010519d15f0552aec4f17a1e1fe # v5 25 | if: github.event_name == 'pull_request' 26 | - uses: actions/setup-node@26961cf329f22f6837d5f54c3efd76b480300ace # v4 27 | with: 28 | cache: 'npm' 29 | node-version-file: '.nvmrc' 30 | - name: Info 31 | run: | 32 | cat <.json 6 | source_file = translations/core/en.json 7 | source_lang = en 8 | type = CHROME 9 | -------------------------------------------------------------------------------- /TRADEMARK: -------------------------------------------------------------------------------- 1 | The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission. 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | ignores: [message => message.startsWith('chore(release):')] 4 | }; 5 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'scratch-semantic-release-config', 3 | branches: [ 4 | { 5 | name: 'develop' 6 | // default channel 7 | }, 8 | { 9 | name: 'hotfix/*', 10 | channel: 'hotfix' 11 | } 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | 4 | "extends": [ 5 | "github>scratchfoundation/scratch-renovate-config:js-lib-bundled" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['scratch', 'scratch/es6'], 4 | env: { 5 | browser: true 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/engine/blocks-execute-cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview 3 | * Access point for private method shared between blocks.js and execute.js for 4 | * caching execute information. 5 | */ 6 | 7 | /** 8 | * A private method shared with execute to build an object containing the block 9 | * information execute needs and that is reset when other cached Blocks info is 10 | * reset. 11 | * @param {Blocks} blocks Blocks containing the expected blockId 12 | * @param {string} blockId blockId for the desired execute cache 13 | */ 14 | exports.getCached = function () { 15 | throw new Error('blocks.js has not initialized BlocksExecuteCache'); 16 | }; 17 | 18 | // Call after the default throwing getCached is assigned for Blocks to replace. 19 | require('./blocks'); 20 | -------------------------------------------------------------------------------- /src/engine/comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview 3 | * Object representing a Scratch Comment (block or workspace). 4 | */ 5 | 6 | const uid = require('../util/uid'); 7 | const xmlEscape = require('../util/xml-escape'); 8 | 9 | class Comment { 10 | /** 11 | * @param {string} id Id of the comment. 12 | * @param {string} text Text content of the comment. 13 | * @param {number} x X position of the comment on the workspace. 14 | * @param {number} y Y position of the comment on the workspace. 15 | * @param {number} width The width of the comment when it is full size. 16 | * @param {number} height The height of the comment when it is full size. 17 | * @param {boolean} minimized Whether the comment is minimized. 18 | * @constructor 19 | */ 20 | constructor (id, text, x, y, width, height, minimized) { 21 | this.id = id || uid(); 22 | this.text = text; 23 | this.x = x; 24 | this.y = y; 25 | this.width = Math.max(Number(width), Comment.MIN_WIDTH); 26 | this.height = Math.max(Number(height), Comment.MIN_HEIGHT); 27 | this.minimized = minimized || false; 28 | this.blockId = null; 29 | } 30 | 31 | toXML () { 32 | return `${xmlEscape(this.text)}`; 35 | } 36 | 37 | // TODO choose min and defaults for width and height 38 | static get MIN_WIDTH () { 39 | return 20; 40 | } 41 | 42 | static get MIN_HEIGHT () { 43 | return 20; 44 | } 45 | 46 | static get DEFAULT_WIDTH () { 47 | return 100; 48 | } 49 | 50 | static get DEFAULT_HEIGHT () { 51 | return 100; 52 | } 53 | 54 | } 55 | 56 | module.exports = Comment; 57 | -------------------------------------------------------------------------------- /src/engine/monitor-record.js: -------------------------------------------------------------------------------- 1 | const {Record} = require('immutable'); 2 | 3 | const MonitorRecord = Record({ 4 | id: null, // Block Id 5 | /** Present only if the monitor is sprite-specific, such as x position */ 6 | spriteName: null, 7 | /** Present only if the monitor is sprite-specific, such as x position */ 8 | targetId: null, 9 | opcode: null, 10 | value: null, 11 | params: null, 12 | mode: 'default', 13 | sliderMin: 0, 14 | sliderMax: 100, 15 | isDiscrete: true, 16 | x: null, // (x: null, y: null) Indicates that the monitor should be auto-positioned 17 | y: null, 18 | width: 0, 19 | height: 0, 20 | visible: true 21 | }); 22 | 23 | module.exports = MonitorRecord; 24 | -------------------------------------------------------------------------------- /src/engine/mutation-adapter.js: -------------------------------------------------------------------------------- 1 | const html = require('htmlparser2'); 2 | const decodeHtml = require('decode-html'); 3 | 4 | /** 5 | * Convert a part of a mutation DOM to a mutation VM object, recursively. 6 | * @param {object} dom DOM object for mutation tag. 7 | * @return {object} Object representing useful parts of this mutation. 8 | */ 9 | const mutatorTagToObject = function (dom) { 10 | const obj = Object.create(null); 11 | obj.tagName = dom.name; 12 | obj.children = []; 13 | for (const prop in dom.attribs) { 14 | if (prop === 'xmlns') continue; 15 | obj[prop] = decodeHtml(dom.attribs[prop]); 16 | // Note: the capitalization of block info in the following lines is important. 17 | // The lowercase is read in from xml which normalizes case. The VM uses camel case everywhere else. 18 | if (prop === 'blockinfo') { 19 | obj.blockInfo = JSON.parse(obj.blockinfo); 20 | delete obj.blockinfo; 21 | } 22 | } 23 | for (let i = 0; i < dom.children.length; i++) { 24 | obj.children.push( 25 | mutatorTagToObject(dom.children[i]) 26 | ); 27 | } 28 | return obj; 29 | }; 30 | 31 | /** 32 | * Adapter between mutator XML or DOM and block representation which can be 33 | * used by the Scratch runtime. 34 | * @param {(object|string)} mutation Mutation XML string or DOM. 35 | * @return {object} Object representing the mutation. 36 | */ 37 | const mutationAdpater = function (mutation) { 38 | let mutationParsed; 39 | // Check if the mutation is already parsed; if not, parse it. 40 | if (typeof mutation === 'object') { 41 | mutationParsed = mutation; 42 | } else { 43 | mutationParsed = html.parseDOM(mutation)[0]; 44 | } 45 | return mutatorTagToObject(mutationParsed); 46 | }; 47 | 48 | module.exports = mutationAdpater; 49 | -------------------------------------------------------------------------------- /src/engine/scratch-blocks-constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * These constants are copied from scratch-blocks/core/constants.js 3 | * @TODO find a way to require() these straight from scratch-blocks... maybe make a scratch-blocks/dist/constants.js? 4 | * @readonly 5 | * @enum {int} 6 | */ 7 | const ScratchBlocksConstants = { 8 | /** 9 | * ENUM for output shape: hexagonal (booleans/predicates). 10 | * @const 11 | */ 12 | OUTPUT_SHAPE_HEXAGONAL: 1, 13 | 14 | /** 15 | * ENUM for output shape: rounded (numbers). 16 | * @const 17 | */ 18 | OUTPUT_SHAPE_ROUND: 2, 19 | 20 | /** 21 | * ENUM for output shape: squared (any/all values; strings). 22 | * @const 23 | */ 24 | OUTPUT_SHAPE_SQUARE: 3 25 | }; 26 | 27 | module.exports = ScratchBlocksConstants; 28 | -------------------------------------------------------------------------------- /src/engine/stage-layering.js: -------------------------------------------------------------------------------- 1 | class StageLayering { 2 | static get BACKGROUND_LAYER () { 3 | return 'background'; 4 | } 5 | 6 | static get VIDEO_LAYER () { 7 | return 'video'; 8 | } 9 | 10 | static get PEN_LAYER () { 11 | return 'pen'; 12 | } 13 | 14 | static get SPRITE_LAYER () { 15 | return 'sprite'; 16 | } 17 | 18 | // Order of layer groups relative to each other, 19 | static get LAYER_GROUPS () { 20 | return [ 21 | StageLayering.BACKGROUND_LAYER, 22 | StageLayering.VIDEO_LAYER, 23 | StageLayering.PEN_LAYER, 24 | StageLayering.SPRITE_LAYER 25 | ]; 26 | } 27 | } 28 | 29 | module.exports = StageLayering; 30 | -------------------------------------------------------------------------------- /src/engine/variable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview 3 | * Object representing a Scratch variable. 4 | */ 5 | 6 | const uid = require('../util/uid'); 7 | const xmlEscape = require('../util/xml-escape'); 8 | 9 | class Variable { 10 | /** 11 | * @param {string} id Id of the variable. 12 | * @param {string} name Name of the variable. 13 | * @param {string} type Type of the variable, one of '' or 'list' 14 | * @param {boolean} isCloud Whether the variable is stored in the cloud. 15 | * @constructor 16 | */ 17 | constructor (id, name, type, isCloud) { 18 | this.id = id || uid(); 19 | this.name = name; 20 | this.type = type; 21 | this.isCloud = isCloud; 22 | switch (this.type) { 23 | case Variable.SCALAR_TYPE: 24 | this.value = 0; 25 | break; 26 | case Variable.LIST_TYPE: 27 | this.value = []; 28 | break; 29 | case Variable.BROADCAST_MESSAGE_TYPE: 30 | this.value = this.name; 31 | break; 32 | default: 33 | throw new Error(`Invalid variable type: ${this.type}`); 34 | } 35 | } 36 | 37 | toXML (isLocal) { 38 | isLocal = (isLocal === true); 39 | return `${xmlEscape(this.name)}`; 41 | } 42 | 43 | /** 44 | * Type representation for scalar variables. 45 | * This is currently represented as '' 46 | * for compatibility with blockly. 47 | * @const {string} 48 | */ 49 | static get SCALAR_TYPE () { 50 | return ''; 51 | } 52 | 53 | /** 54 | * Type representation for list variables. 55 | * @const {string} 56 | */ 57 | static get LIST_TYPE () { 58 | return 'list'; 59 | } 60 | 61 | /** 62 | * Type representation for list variables. 63 | * @const {string} 64 | */ 65 | static get BROADCAST_MESSAGE_TYPE () { 66 | return 'broadcast_msg'; 67 | } 68 | } 69 | 70 | module.exports = Variable; 71 | -------------------------------------------------------------------------------- /src/extension-support/argument-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Block argument types 3 | * @enum {string} 4 | */ 5 | const ArgumentType = { 6 | /** 7 | * Numeric value with angle picker 8 | */ 9 | ANGLE: 'angle', 10 | 11 | /** 12 | * Boolean value with hexagonal placeholder 13 | */ 14 | BOOLEAN: 'Boolean', 15 | 16 | /** 17 | * Numeric value with color picker 18 | */ 19 | COLOR: 'color', 20 | 21 | /** 22 | * Numeric value with text field 23 | */ 24 | NUMBER: 'number', 25 | 26 | /** 27 | * String value with text field 28 | */ 29 | STRING: 'string', 30 | 31 | /** 32 | * String value with matrix field 33 | */ 34 | MATRIX: 'matrix', 35 | 36 | /** 37 | * MIDI note number with note picker (piano) field 38 | */ 39 | NOTE: 'note', 40 | 41 | /** 42 | * Inline image on block (as part of the label) 43 | */ 44 | IMAGE: 'image' 45 | }; 46 | 47 | module.exports = ArgumentType; 48 | -------------------------------------------------------------------------------- /src/extension-support/block-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Types of block 3 | * @enum {string} 4 | */ 5 | const BlockType = { 6 | /** 7 | * Boolean reporter with hexagonal shape 8 | */ 9 | BOOLEAN: 'Boolean', 10 | 11 | /** 12 | * A button (not an actual block) for some special action, like making a variable 13 | */ 14 | BUTTON: 'button', 15 | 16 | /** 17 | * Command block 18 | */ 19 | COMMAND: 'command', 20 | 21 | /** 22 | * Specialized command block which may or may not run a child branch 23 | * The thread continues with the next block whether or not a child branch ran. 24 | */ 25 | CONDITIONAL: 'conditional', 26 | 27 | /** 28 | * Specialized hat block with no implementation function 29 | * This stack only runs if the corresponding event is emitted by other code. 30 | */ 31 | EVENT: 'event', 32 | 33 | /** 34 | * Hat block which conditionally starts a block stack 35 | */ 36 | HAT: 'hat', 37 | 38 | /** 39 | * Specialized command block which may or may not run a child branch 40 | * If a child branch runs, the thread evaluates the loop block again. 41 | */ 42 | LOOP: 'loop', 43 | 44 | /** 45 | * General reporter with numeric or string value 46 | */ 47 | REPORTER: 'reporter' 48 | }; 49 | 50 | module.exports = BlockType; 51 | -------------------------------------------------------------------------------- /src/extension-support/define-messages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {object} MessageDescriptor 3 | * @property {string} id - the translator-friendly unique ID of this message. 4 | * @property {string} default - the message text in the default language (English). 5 | * @property {string} [description] - a description of this message to help translators understand the context. 6 | */ 7 | 8 | /** 9 | * This is a hook for extracting messages from extension source files. 10 | * This function simply returns the message descriptor map object that's passed in. 11 | * @param {object.} messages - the messages to be defined 12 | * @return {object.} - the input, unprocessed 13 | */ 14 | const defineMessages = function (messages) { 15 | return messages; 16 | }; 17 | 18 | module.exports = defineMessages; 19 | -------------------------------------------------------------------------------- /src/extension-support/extension-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-env worker */ 2 | 3 | const ArgumentType = require('../extension-support/argument-type'); 4 | const BlockType = require('../extension-support/block-type'); 5 | const dispatch = require('../dispatch/worker-dispatch'); 6 | const TargetType = require('../extension-support/target-type'); 7 | 8 | class ExtensionWorker { 9 | constructor () { 10 | this.nextExtensionId = 0; 11 | 12 | this.initialRegistrations = []; 13 | 14 | dispatch.waitForConnection.then(() => { 15 | dispatch.call('extensions', 'allocateWorker').then(x => { 16 | const [id, extension] = x; 17 | this.workerId = id; 18 | 19 | try { 20 | importScripts(extension); 21 | 22 | const initialRegistrations = this.initialRegistrations; 23 | this.initialRegistrations = null; 24 | 25 | Promise.all(initialRegistrations).then(() => dispatch.call('extensions', 'onWorkerInit', id)); 26 | } catch (e) { 27 | dispatch.call('extensions', 'onWorkerInit', id, e); 28 | } 29 | }); 30 | }); 31 | 32 | this.extensions = []; 33 | } 34 | 35 | register (extensionObject) { 36 | const extensionId = this.nextExtensionId++; 37 | this.extensions.push(extensionObject); 38 | const serviceName = `extension.${this.workerId}.${extensionId}`; 39 | const promise = dispatch.setService(serviceName, extensionObject) 40 | .then(() => dispatch.call('extensions', 'registerExtensionService', serviceName)); 41 | if (this.initialRegistrations) { 42 | this.initialRegistrations.push(promise); 43 | } 44 | return promise; 45 | } 46 | } 47 | 48 | global.Scratch = global.Scratch || {}; 49 | global.Scratch.ArgumentType = ArgumentType; 50 | global.Scratch.BlockType = BlockType; 51 | global.Scratch.TargetType = TargetType; 52 | 53 | /** 54 | * Expose only specific parts of the worker to extensions. 55 | */ 56 | const extensionWorker = new ExtensionWorker(); 57 | global.Scratch.extensions = { 58 | register: extensionWorker.register.bind(extensionWorker) 59 | }; 60 | -------------------------------------------------------------------------------- /src/extension-support/reporter-scope.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Indicate the scope for a reporter's value. 3 | * @enum {string} 4 | */ 5 | const ReporterScope = { 6 | /** 7 | * This reporter's value is global and does not depend on context. 8 | */ 9 | GLOBAL: 'global', 10 | 11 | /** 12 | * This reporter's value is specific to a particular target/sprite. 13 | * Another target may have a different value or may not even have a value. 14 | */ 15 | TARGET: 'target' 16 | }; 17 | 18 | module.exports = ReporterScope; 19 | -------------------------------------------------------------------------------- /src/extension-support/target-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default types of Target supported by the VM 3 | * @enum {string} 4 | */ 5 | const TargetType = { 6 | /** 7 | * Rendered target which can move, change costumes, etc. 8 | */ 9 | SPRITE: 'sprite', 10 | 11 | /** 12 | * Rendered target which cannot move but can change backdrops 13 | */ 14 | STAGE: 'stage' 15 | }; 16 | 17 | module.exports = TargetType; 18 | -------------------------------------------------------------------------------- /src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js: -------------------------------------------------------------------------------- 1 | const Base64Util = require('../../util/base64-util'); 2 | 3 | /** 4 | * Adapter class 5 | */ 6 | class ScratchLinkDeviceAdapter { 7 | constructor (socket, {service, commandChar, responseChar}) { 8 | this.socket = socket; 9 | 10 | this._service = service; 11 | this._commandChar = commandChar; 12 | this._responseChar = responseChar; 13 | this._onResponse = this._onResponse.bind(this); 14 | this._deviceOnResponse = null; 15 | } 16 | 17 | get godirectAdapter () { 18 | return true; 19 | } 20 | 21 | writeCommand (commandBuffer) { 22 | const data = Base64Util.uint8ArrayToBase64(commandBuffer); 23 | 24 | return this.socket 25 | .write(this._service, this._commandChar, data, 'base64'); 26 | } 27 | 28 | setup ({onResponse}) { 29 | this._deviceOnResponse = onResponse; 30 | return this.socket 31 | .startNotifications(this._service, this._responseChar, this._onResponse); 32 | 33 | // TODO: 34 | // How do we find out from scratch link if communication closes? 35 | } 36 | 37 | _onResponse (base64) { 38 | const array = Base64Util.base64ToUint8Array(base64); 39 | const response = new DataView(array.buffer); 40 | return this._deviceOnResponse(response); 41 | } 42 | } 43 | 44 | module.exports = ScratchLinkDeviceAdapter; 45 | -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/1-snare.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/1-snare.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/10-wood-block.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/11-cowbell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/12-triangle.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/13-bongo.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/14-conga.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/14-conga.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/15-cabasa.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/16-guiro.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/18-cuica.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/3-side-stick.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/7-tambourine.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/drums/9-claves.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/drums/9-claves.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 -------------------------------------------------------------------------------- /src/extensions/scratch3_video_sensing/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A debug "index" module exporting VideoMotion and VideoMotionView to debug 3 | * VideoMotion directly. 4 | * @file debug.js 5 | */ 6 | 7 | const VideoMotion = require('./library'); 8 | const VideoMotionView = require('./view'); 9 | 10 | module.exports = { 11 | VideoMotion, 12 | VideoMotionView 13 | }; 14 | -------------------------------------------------------------------------------- /src/extensions/scratch3_video_sensing/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A constant value helping to transform a value in radians to degrees. 3 | * @type {number} 4 | */ 5 | const TO_DEGREE = 180 / Math.PI; 6 | 7 | /** 8 | * A object reused to save on memory allocation returning u and v vector from 9 | * motionVector. 10 | * @type {UV} 11 | */ 12 | const _motionVectorOut = {u: 0, v: 0}; 13 | 14 | /** 15 | * Determine a motion vector combinations of the color component difference on 16 | * the x axis, y axis, and temporal axis. 17 | * @param {number} A2 - a sum of x axis squared 18 | * @param {number} A1B2 - a sum of x axis times y axis 19 | * @param {number} B1 - a sum of y axis squared 20 | * @param {number} C2 - a sum of x axis times temporal axis 21 | * @param {number} C1 - a sum of y axis times temporal axis 22 | * @param {UV} out - optional object to store return UV info in 23 | * @returns {UV} a uv vector representing the motion for the given input 24 | */ 25 | const motionVector = function (A2, A1B2, B1, C2, C1, out = _motionVectorOut) { 26 | // Compare sums of X * Y and sums of X squared and Y squared. 27 | const delta = ((A1B2 * A1B2) - (A2 * B1)); 28 | if (delta) { 29 | // System is not singular - solving by Kramer method. 30 | const deltaX = -((C1 * A1B2) - (C2 * B1)); 31 | const deltaY = -((A1B2 * C2) - (A2 * C1)); 32 | const Idelta = 8 / delta; 33 | out.u = deltaX * Idelta; 34 | out.v = deltaY * Idelta; 35 | } else { 36 | // Singular system - find optical flow in gradient direction. 37 | const Norm = ((A1B2 + A2) * (A1B2 + A2)) + ((B1 + A1B2) * (B1 + A1B2)); 38 | if (Norm) { 39 | const IGradNorm = 8 / Norm; 40 | const temp = -(C1 + C2) * IGradNorm; 41 | out.u = (A1B2 + A2) * temp; 42 | out.v = (B1 + A1B2) * temp; 43 | } else { 44 | out.u = 0; 45 | out.v = 0; 46 | } 47 | } 48 | return out; 49 | }; 50 | 51 | /** 52 | * Translate an angle in degrees with the range -180 to 180 rotated to 53 | * Scratch's reference angle. 54 | * @param {number} degrees - angle in range -180 to 180 55 | * @returns {number} angle from Scratch's reference angle 56 | */ 57 | const scratchDegrees = function (degrees) { 58 | return ((degrees + 270) % 360) - 180; 59 | }; 60 | 61 | /** 62 | * Get the angle of the y and x component of a 2d vector in degrees in 63 | * Scratch's coordinate plane. 64 | * @param {number} y - the y component of a 2d vector 65 | * @param {number} x - the x component of a 2d vector 66 | * @returns {number} angle in degrees in Scratch's coordinate plane 67 | */ 68 | const scratchAtan2 = function (y, x) { 69 | return scratchDegrees(Math.atan2(y, x) * TO_DEGREE); 70 | }; 71 | 72 | module.exports = { 73 | motionVector, 74 | scratchDegrees, 75 | scratchAtan2 76 | }; 77 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const VirtualMachine = require('./virtual-machine'); 2 | 3 | const ArgumentType = require('./extension-support/argument-type'); 4 | const BlockType = require('./extension-support/block-type'); 5 | 6 | module.exports = VirtualMachine; 7 | 8 | // TODO: ESM named exports will save us all 9 | module.exports.ArgumentType = ArgumentType; 10 | module.exports.BlockType = BlockType; 11 | -------------------------------------------------------------------------------- /src/io/clock.js: -------------------------------------------------------------------------------- 1 | const Timer = require('../util/timer'); 2 | 3 | class Clock { 4 | constructor (runtime) { 5 | this._projectTimer = new Timer({now: () => runtime.currentMSecs}); 6 | this._projectTimer.start(); 7 | this._pausedTime = null; 8 | this._paused = false; 9 | /** 10 | * Reference to the owning Runtime. 11 | * @type{!Runtime} 12 | */ 13 | this.runtime = runtime; 14 | } 15 | 16 | projectTimer () { 17 | if (this._paused) { 18 | return this._pausedTime / 1000; 19 | } 20 | return this._projectTimer.timeElapsed() / 1000; 21 | } 22 | 23 | pause () { 24 | this._paused = true; 25 | this._pausedTime = this._projectTimer.timeElapsed(); 26 | } 27 | 28 | resume () { 29 | this._paused = false; 30 | const dt = this._projectTimer.timeElapsed() - this._pausedTime; 31 | this._projectTimer.startTime += dt; 32 | } 33 | 34 | resetProjectTimer () { 35 | this._projectTimer.start(); 36 | } 37 | } 38 | 39 | module.exports = Clock; 40 | -------------------------------------------------------------------------------- /src/io/mouseWheel.js: -------------------------------------------------------------------------------- 1 | class MouseWheel { 2 | constructor (runtime) { 3 | /** 4 | * Reference to the owning Runtime. 5 | * @type{!Runtime} 6 | */ 7 | this.runtime = runtime; 8 | } 9 | 10 | /** 11 | * Mouse wheel DOM event handler. 12 | * @param {object} data Data from DOM event. 13 | */ 14 | postData (data) { 15 | const matchFields = {}; 16 | if (data.deltaY < 0) { 17 | matchFields.KEY_OPTION = 'up arrow'; 18 | } else if (data.deltaY > 0) { 19 | matchFields.KEY_OPTION = 'down arrow'; 20 | } else { 21 | return; 22 | } 23 | 24 | this.runtime.startHats('event_whenkeypressed', matchFields); 25 | } 26 | } 27 | 28 | module.exports = MouseWheel; 29 | -------------------------------------------------------------------------------- /src/io/userData.js: -------------------------------------------------------------------------------- 1 | class UserData { 2 | constructor () { 3 | this._username = ''; 4 | } 5 | 6 | /** 7 | * Handler for updating the username 8 | * @param {object} data Data posted to this ioDevice. 9 | * @property {!string} username The new username. 10 | */ 11 | postData (data) { 12 | this._username = data.username; 13 | } 14 | 15 | /** 16 | * Getter for username. Initially empty string, until set via postData. 17 | * @returns {!string} The current username 18 | */ 19 | getUsername () { 20 | return this._username; 21 | } 22 | } 23 | 24 | module.exports = UserData; 25 | -------------------------------------------------------------------------------- /src/playground/benchmark.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: monospace; 3 | } 4 | p { 5 | max-width: 400px; 6 | } 7 | @media (min-width: 960px) { 8 | .profile-tables { 9 | top: 0px; 10 | position: absolute; 11 | left: 450px; 12 | } 13 | } 14 | .share { 15 | display: none; 16 | } 17 | .share label { 18 | cursor: pointer; 19 | } 20 | .share[href] { 21 | display: inline; 22 | } 23 | 24 | .render .profile-tables { 25 | position: static; 26 | left: 0; 27 | } 28 | .render .description { 29 | display: none; 30 | } 31 | .render .run-push { 32 | display: none; 33 | } 34 | #scratch-stage { 35 | border: 5px solid black; 36 | display: block; 37 | width: 400px; 38 | height: 300px; 39 | } 40 | .render #scratch-stage { 41 | display: none; 42 | } 43 | .loading label, .profile-count label{ 44 | width: 15em; 45 | display: inline-block; 46 | } 47 | .render .loading { 48 | display: none; 49 | } 50 | .profile-tables table { 51 | margin: 30px 0 30px 0px; 52 | } 53 | .profile-tables th { 54 | border-bottom: 1px solid #333; 55 | text-align: center; 56 | } 57 | .profile-tables th:first-child { 58 | width: 215px; 59 | } 60 | .profile-tables th, .profile-tables td { 61 | min-width: 85px; 62 | border-bottom: 1px solid #ccc; 63 | border-spacing: 0; 64 | border-collapse: collapse; 65 | padding: 5px; 66 | } 67 | .profile-tables td:not(:first-child) { 68 | text-align: center; 69 | } 70 | .profile-tables img{ 71 | margin: 0 auto; 72 | display: block; 73 | clear: both; 74 | width: 20%; 75 | } 76 | 77 | .slow { 78 | background-color: #ffa1a1; 79 | } 80 | .profiler-count-running { 81 | height: 4em; 82 | background-color: #dddddd; 83 | } 84 | -------------------------------------------------------------------------------- /src/playground/suite.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | margin: 0; 5 | overflow: hidden; 6 | } 7 | 8 | iframe { 9 | border: none; 10 | } 11 | 12 | .runner-controls { 13 | position: absolute; 14 | width: 30em; 15 | height: 100%; 16 | left: 0; 17 | right: 30em; 18 | top: 0; 19 | bottom: 0; 20 | overflow: scroll; 21 | padding: 1em; 22 | padding-top: 0; 23 | box-sizing: border-box; 24 | } 25 | 26 | .bench-frame-owner { 27 | position: absolute; 28 | width: calc(100% - 30em); 29 | height: 100%; 30 | left: 30em; 31 | right: 100%; 32 | top: 0; 33 | bottom: 0; 34 | } 35 | 36 | .controls { 37 | margin-bottom: 1em; 38 | } 39 | 40 | .legend { 41 | margin: 1em 0; 42 | } 43 | 44 | .result-view { 45 | border-bottom: 1px solid #ccc; 46 | border-spacing: 0; 47 | border-collapse: collapse; 48 | padding: 5px; 49 | } 50 | 51 | .fixture-project { 52 | display: inline-block; 53 | clear: both; 54 | } 55 | 56 | .fixture-warm-up { 57 | display: inline-block; 58 | } 59 | 60 | .fixture-recording { 61 | display: inline-block; 62 | } 63 | 64 | .result-view.resume { 65 | cursor: pointer; 66 | } 67 | 68 | .result-status { 69 | float: right; 70 | text-align: right; 71 | margin-left: 0.3em; 72 | } 73 | 74 | .compare-file { 75 | cursor: pointer; 76 | visibility: hidden; 77 | width: 0; 78 | } 79 | -------------------------------------------------------------------------------- /src/playground/suite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Scratch VM Benchmark Suite 7 | 8 | 9 | 10 |
11 |

Scratch VM Benchmark Suite

12 |
13 | 14 |
15 |
16 | 17 |
18 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/playground/video-sensing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Video Motion Test Playground 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/util/base64-util.js: -------------------------------------------------------------------------------- 1 | const atob = require('atob'); 2 | const btoa = require('btoa'); 3 | 4 | class Base64Util { 5 | 6 | /** 7 | * Convert a base64 encoded string to a Uint8Array. 8 | * @param {string} base64 - a base64 encoded string. 9 | * @return {Uint8Array} - a decoded Uint8Array. 10 | */ 11 | static base64ToUint8Array (base64) { 12 | const binaryString = atob(base64); 13 | const len = binaryString.length; 14 | const array = new Uint8Array(len); 15 | for (let i = 0; i < len; i++) { 16 | array[i] = binaryString.charCodeAt(i); 17 | } 18 | return array; 19 | } 20 | 21 | /** 22 | * Convert a Uint8Array to a base64 encoded string. 23 | * @param {Uint8Array} array - the array to convert. 24 | * @return {string} - the base64 encoded string. 25 | */ 26 | static uint8ArrayToBase64 (array) { 27 | const base64 = btoa(String.fromCharCode.apply(null, array)); 28 | return base64; 29 | } 30 | 31 | /** 32 | * Convert an array buffer to a base64 encoded string. 33 | * @param {array} buffer - an array buffer to convert. 34 | * @return {string} - the base64 encoded string. 35 | */ 36 | static arrayBufferToBase64 (buffer) { 37 | let binary = ''; 38 | const bytes = new Uint8Array(buffer); 39 | const len = bytes.byteLength; 40 | for (let i = 0; i < len; i++) { 41 | binary += String.fromCharCode(bytes[ i ]); 42 | } 43 | return btoa(binary); 44 | } 45 | 46 | } 47 | 48 | module.exports = Base64Util; 49 | -------------------------------------------------------------------------------- /src/util/clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Methods for cloning JavaScript objects. 3 | * @type {object} 4 | */ 5 | class Clone { 6 | /** 7 | * Deep-clone a "simple" object: one which can be fully expressed with JSON. 8 | * Non-JSON values, such as functions, will be stripped from the clone. 9 | * @param {object} original - the object to be cloned. 10 | * @returns {object} a deep clone of the original object. 11 | */ 12 | static simple (original) { 13 | return JSON.parse(JSON.stringify(original)); 14 | } 15 | } 16 | 17 | module.exports = Clone; 18 | -------------------------------------------------------------------------------- /src/util/fetch-with-timeout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @callback FetchFunction 3 | * @param {RequestInfo|URL} input 4 | * @param {RequestInit|undefined} [init] 5 | * @returns {Promise} 6 | */ 7 | 8 | /** 9 | * @type {FetchFunction} 10 | */ 11 | let myFetch = global.fetch; 12 | 13 | /** 14 | * Tell `fetchWithTimeout` to use a specific `fetch` function. 15 | * By default, `fetchWithTimeout` will use the global `fetch` function. 16 | * If there is no global `fetch`, then `fetchWithTimeout` will fail unless provided with an alternative. 17 | * @param {FetchFunction} newFetch The new `fetch` function to use within fetchWithTimeout. 18 | */ 19 | const setFetch = newFetch => { 20 | myFetch = newFetch; 21 | }; 22 | 23 | /** 24 | * Fetch a remote resource like `fetch` does, but with a time limit. 25 | * @param {Request|string} resource Remote resource to fetch. 26 | * @param {?object} init An options object containing any custom settings that you want to apply to the request. 27 | * @param {number} timeout The amount of time before the request is canceled, in milliseconds 28 | * @returns {Promise} The response from the server. 29 | */ 30 | const fetchWithTimeout = (resource, init, timeout) => { 31 | let timeoutID = null; 32 | // Not supported in Safari <11 33 | const controller = window.AbortController ? new window.AbortController() : null; 34 | const signal = controller ? controller.signal : null; 35 | // The fetch call races a timer. 36 | return Promise.race([ 37 | myFetch(resource, Object.assign({signal}, init)).then(response => { 38 | clearTimeout(timeoutID); 39 | return response; 40 | }), 41 | new Promise((resolve, reject) => { 42 | timeoutID = setTimeout(() => { 43 | if (controller) controller.abort(); 44 | reject(new Error(`Fetch timed out after ${timeout} ms`)); 45 | }, timeout); 46 | }) 47 | ]); 48 | }; 49 | 50 | module.exports = { 51 | fetchWithTimeout, 52 | setFetch 53 | }; 54 | -------------------------------------------------------------------------------- /src/util/get-monitor-id.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a string representing a unique id for a monitored block 3 | * where a single reporter block can have more than one monitor 4 | * (and therefore more than one monitor block) associated 5 | * with it (e.g. when reporter blocks have inputs). 6 | * @param {string} baseId The base id to use for the different monitor blocks 7 | * @param {object} fields The monitor block's fields object. 8 | */ 9 | // TODO this function should eventually be the single place where all monitor 10 | // IDs are obtained given an opcode for the reporter block and the list of 11 | // selected parameters. 12 | const getMonitorIdForBlockWithArgs = function (id, fields) { 13 | let fieldString = ''; 14 | for (const fieldKey in fields) { 15 | let fieldValue = fields[fieldKey].value; 16 | if (fieldKey === 'CURRENTMENU') { 17 | // The 'sensing_current' block has field values in all caps. 18 | // However, when importing from scratch 2.0, these 19 | // could have gotten imported as lower case field values. 20 | // Normalize the field value here so that we don't ever 21 | // end up with a different monitor ID representing the same 22 | // block configuration 23 | // Note: we are not doing this for every block field that comes into 24 | // this function so as not to make the faulty assumption that block 25 | // field values coming in would be unique after being made lower case 26 | fieldValue = fieldValue.toLowerCase(); 27 | } 28 | fieldString += `_${fieldValue}`; 29 | } 30 | return `${id}${fieldString}`; 31 | }; 32 | 33 | module.exports = getMonitorIdForBlockWithArgs; 34 | -------------------------------------------------------------------------------- /src/util/log.js: -------------------------------------------------------------------------------- 1 | const minilog = require('minilog'); 2 | minilog.enable(); 3 | 4 | module.exports = minilog('vm'); 5 | -------------------------------------------------------------------------------- /src/util/maybe-format-message.js: -------------------------------------------------------------------------------- 1 | const formatMessage = require('format-message'); 2 | 3 | /** 4 | * Check if `maybeMessage` looks like a message object, and if so pass it to `formatMessage`. 5 | * Otherwise, return `maybeMessage` as-is. 6 | * @param {*} maybeMessage - something that might be a message descriptor object. 7 | * @param {object} [args] - the arguments to pass to `formatMessage` if it gets called. 8 | * @param {string} [locale] - the locale to pass to `formatMessage` if it gets called. 9 | * @return {string|*} - the formatted message OR the original `maybeMessage` input. 10 | */ 11 | const maybeFormatMessage = function (maybeMessage, args, locale) { 12 | if (maybeMessage && maybeMessage.id && maybeMessage.default) { 13 | return formatMessage(maybeMessage, args, locale); 14 | } 15 | return maybeMessage; 16 | }; 17 | 18 | module.exports = maybeFormatMessage; 19 | -------------------------------------------------------------------------------- /src/util/new-block-ids.js: -------------------------------------------------------------------------------- 1 | const uid = require('./uid'); 2 | 3 | /** 4 | * Mutate the given blocks to have new IDs and update all internal ID references. 5 | * Does not return anything to make it clear that the blocks are updated in-place. 6 | * @param {array} blocks - blocks to be mutated. 7 | */ 8 | module.exports = blocks => { 9 | const oldToNew = {}; 10 | 11 | // First update all top-level IDs and create old-to-new mapping 12 | for (let i = 0; i < blocks.length; i++) { 13 | const newId = uid(); 14 | const oldId = blocks[i].id; 15 | blocks[i].id = oldToNew[oldId] = newId; 16 | } 17 | 18 | // Then go back through and update inputs (block/shadow) 19 | // and next/parent properties 20 | for (let i = 0; i < blocks.length; i++) { 21 | for (const key in blocks[i].inputs) { 22 | const input = blocks[i].inputs[key]; 23 | input.block = oldToNew[input.block]; 24 | input.shadow = oldToNew[input.shadow]; 25 | } 26 | if (blocks[i].parent) { 27 | blocks[i].parent = oldToNew[blocks[i].parent]; 28 | } 29 | if (blocks[i].next) { 30 | blocks[i].next = oldToNew[blocks[i].next]; 31 | } 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/util/rateLimiter.js: -------------------------------------------------------------------------------- 1 | const Timer = require('../util/timer'); 2 | 3 | class RateLimiter { 4 | /** 5 | * A utility for limiting the rate of repetitive send operations, such as 6 | * bluetooth messages being sent to hardware devices. It uses the token bucket 7 | * strategy: a counter accumulates tokens at a steady rate, and each send costs 8 | * a token. If no tokens remain, it's not okay to send. 9 | * @param {number} maxRate the maximum number of sends allowed per second 10 | * @constructor 11 | */ 12 | constructor (maxRate) { 13 | /** 14 | * The maximum number of tokens. 15 | * @type {number} 16 | */ 17 | this._maxTokens = maxRate; 18 | 19 | /** 20 | * The interval in milliseconds for refilling one token. It is calculated 21 | * so that the tokens will be filled to maximum in one second. 22 | * @type {number} 23 | */ 24 | this._refillInterval = 1000 / maxRate; 25 | 26 | /** 27 | * The current number of tokens in the bucket. 28 | * @type {number} 29 | */ 30 | this._count = this._maxTokens; 31 | 32 | this._timer = new Timer(); 33 | this._timer.start(); 34 | 35 | /** 36 | * The last time in milliseconds when the token count was updated. 37 | * @type {number} 38 | */ 39 | this._lastUpdateTime = this._timer.timeElapsed(); 40 | } 41 | 42 | /** 43 | * Check if it is okay to send a message, by updating the token count, 44 | * taking a token and then checking if we are still under the rate limit. 45 | * @return {boolean} true if we are under the rate limit 46 | */ 47 | okayToSend () { 48 | // Calculate the number of tokens to refill the bucket with, based on the 49 | // amount of time since the last refill. 50 | const now = this._timer.timeElapsed(); 51 | const timeSinceRefill = now - this._lastUpdateTime; 52 | const refillCount = Math.floor(timeSinceRefill / this._refillInterval); 53 | 54 | // If we're adding at least one token, reset _lastUpdateTime to now. 55 | // Otherwise, don't reset it so that we can continue measuring time until 56 | // the next refill. 57 | if (refillCount > 0) { 58 | this._lastUpdateTime = now; 59 | } 60 | 61 | // Refill the tokens up to the maximum 62 | this._count = Math.min(this._maxTokens, this._count + refillCount); 63 | 64 | // If we have at least one token, use one, and it's okay to send. 65 | if (this._count > 0) { 66 | this._count--; 67 | return true; 68 | } 69 | return false; 70 | } 71 | } 72 | 73 | module.exports = RateLimiter; 74 | -------------------------------------------------------------------------------- /src/util/uid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview UID generator, from Blockly. 3 | */ 4 | 5 | /** 6 | * Legal characters for the unique ID. 7 | * Should be all on a US keyboard. No XML special characters or control codes. 8 | * Removed $ due to issue 251. 9 | * @private 10 | */ 11 | const soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' + 12 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 13 | 14 | /** 15 | * Generate a unique ID, from Blockly. This should be globally unique. 16 | * 87 characters ^ 20 length > 128 bits (better than a UUID). 17 | * @return {string} A globally unique ID string. 18 | */ 19 | const uid = function () { 20 | const length = 20; 21 | const soupLength = soup_.length; 22 | const id = []; 23 | for (let i = 0; i < length; i++) { 24 | id[i] = soup_.charAt(Math.random() * soupLength); 25 | } 26 | return id.join(''); 27 | }; 28 | 29 | module.exports = uid; 30 | -------------------------------------------------------------------------------- /src/util/variable-util.js: -------------------------------------------------------------------------------- 1 | class VariableUtil { 2 | static _mergeVarRefObjects (accum, obj2) { 3 | for (const id in obj2) { 4 | if (accum[id]) { 5 | accum[id] = accum[id].concat(obj2[id]); 6 | } else { 7 | accum[id] = obj2[id]; 8 | } 9 | } 10 | return accum; 11 | } 12 | 13 | /** 14 | * Get all variable/list references in the given list of targets 15 | * in the project. 16 | * @param {Array.} targets The list of targets to get the variable 17 | * and list references from. 18 | * @param {boolean} shouldIncludeBroadcast Whether to include broadcast message fields. 19 | * @return {object} An object with variable ids as the keys and a list of block fields referencing 20 | * the variable. 21 | */ 22 | static getAllVarRefsForTargets (targets, shouldIncludeBroadcast) { 23 | return targets 24 | .map(t => t.blocks.getAllVariableAndListReferences(null, shouldIncludeBroadcast)) 25 | .reduce(VariableUtil._mergeVarRefObjects, {}); 26 | } 27 | 28 | /** 29 | * Give all variable references provided a new id and possibly new name. 30 | * @param {Array} referencesToUpdate Context of the change, the object containing variable 31 | * references to update. 32 | * @param {string} newId ID of the variable that the old references should be replaced with 33 | * @param {?string} optNewName New variable name to merge with. The old 34 | * variable name in the references being updated should be replaced with this new name. 35 | * If this parameter is not provided or is '', no name change occurs. 36 | */ 37 | static updateVariableIdentifiers (referencesToUpdate, newId, optNewName) { 38 | referencesToUpdate.map(ref => { 39 | ref.referencingField.id = newId; 40 | if (optNewName) { 41 | ref.referencingField.value = optNewName; 42 | } 43 | return ref; 44 | }); 45 | } 46 | } 47 | 48 | module.exports = VariableUtil; 49 | -------------------------------------------------------------------------------- /src/util/xml-escape.js: -------------------------------------------------------------------------------- 1 | const log = require('./log'); 2 | 3 | /** 4 | * Escape a string to be safe to use in XML content. 5 | * CC-BY-SA: hgoebl 6 | * https://stackoverflow.com/questions/7918868/ 7 | * how-to-escape-xml-entities-in-javascript 8 | * @param {!string | !Array.} unsafe Unsafe string. 9 | * @return {string} XML-escaped string, for use within an XML tag. 10 | */ 11 | const xmlEscape = function (unsafe) { 12 | if (typeof unsafe !== 'string') { 13 | if (Array.isArray(unsafe)) { 14 | // This happens when we have hacked blocks from 2.0 15 | // See #1030 16 | unsafe = String(unsafe); 17 | } else { 18 | log.error('Unexpected input recieved in replaceUnsafeChars'); 19 | return unsafe; 20 | } 21 | } 22 | return unsafe.replace(/[<>&'"]/g, c => { 23 | switch (c) { 24 | case '<': return '<'; 25 | case '>': return '>'; 26 | case '&': return '&'; 27 | case '\'': return '''; 28 | case '"': return '"'; 29 | } 30 | }); 31 | }; 32 | 33 | module.exports = xmlEscape; 34 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'no-undefined': [0] 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/block-to-workspace-comments-without-scripts.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/block-to-workspace-comments-without-scripts.sb2 -------------------------------------------------------------------------------- /test/fixtures/block-to-workspace-comments.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/block-to-workspace-comments.sb2 -------------------------------------------------------------------------------- /test/fixtures/broadcast_special_chars.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/broadcast_special_chars.sb2 -------------------------------------------------------------------------------- /test/fixtures/broadcast_special_chars.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/broadcast_special_chars.sb3 -------------------------------------------------------------------------------- /test/fixtures/cat.sprite2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cat.sprite2 -------------------------------------------------------------------------------- /test/fixtures/cat.sprite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cat.sprite3 -------------------------------------------------------------------------------- /test/fixtures/clone-cleanup.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/clone-cleanup.sb2 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_exceeded_limit.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_exceeded_limit.sb2 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_exceeded_limit.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_exceeded_limit.sb3 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_limit.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_limit.sb2 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_limit.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_limit.sb3 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_local.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_local.sb2 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_local.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_local.sb3 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_simple.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_simple.sb2 -------------------------------------------------------------------------------- /test/fixtures/cloud_variables_simple.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/cloud_variables_simple.sb3 -------------------------------------------------------------------------------- /test/fixtures/comments.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/comments.sb2 -------------------------------------------------------------------------------- /test/fixtures/comments.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/comments.sb3 -------------------------------------------------------------------------------- /test/fixtures/comments_no_duplicate_id_serialization.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/comments_no_duplicate_id_serialization.sb3 -------------------------------------------------------------------------------- /test/fixtures/complex.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/complex.sb2 -------------------------------------------------------------------------------- /test/fixtures/control.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/control.sb2 -------------------------------------------------------------------------------- /test/fixtures/corrupt_png.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_png.sb2 -------------------------------------------------------------------------------- /test/fixtures/corrupt_png.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_png.sb3 -------------------------------------------------------------------------------- /test/fixtures/corrupt_png.sprite2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_png.sprite2 -------------------------------------------------------------------------------- /test/fixtures/corrupt_png.sprite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_png.sprite3 -------------------------------------------------------------------------------- /test/fixtures/corrupt_sound.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_sound.sb3 -------------------------------------------------------------------------------- /test/fixtures/corrupt_svg.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_svg.sb2 -------------------------------------------------------------------------------- /test/fixtures/corrupt_svg.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_svg.sb3 -------------------------------------------------------------------------------- /test/fixtures/corrupt_svg.sprite2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_svg.sprite2 -------------------------------------------------------------------------------- /test/fixtures/corrupt_svg.sprite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/corrupt_svg.sprite3 -------------------------------------------------------------------------------- /test/fixtures/data.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/data.sb2 -------------------------------------------------------------------------------- /test/fixtures/default.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/default.sb2 -------------------------------------------------------------------------------- /test/fixtures/default.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/default.sb3 -------------------------------------------------------------------------------- /test/fixtures/default_nested.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/default_nested.sb2 -------------------------------------------------------------------------------- /test/fixtures/dispatch-test-service.js: -------------------------------------------------------------------------------- 1 | class DispatchTestService { 2 | returnFortyTwo () { 3 | return 42; 4 | } 5 | 6 | doubleArgument (x) { 7 | return 2 * x; 8 | } 9 | 10 | throwException () { 11 | throw new Error('This is a test exception thrown by DispatchTest'); 12 | } 13 | } 14 | 15 | module.exports = DispatchTestService; 16 | -------------------------------------------------------------------------------- /test/fixtures/dispatch-test-worker-shim.js: -------------------------------------------------------------------------------- 1 | const Module = require('module'); 2 | 3 | const callsite = require('callsite'); 4 | const path = require('path'); 5 | 6 | const oldRequire = Module.prototype.require; 7 | Module.prototype.require = function (target) { 8 | if (target.indexOf('/') === -1) { 9 | // we really do just want to forward the arguments here 10 | // eslint-disable-next-line prefer-rest-params 11 | return oldRequire.apply(this, arguments); 12 | } 13 | 14 | const stack = callsite(); 15 | const callerFile = stack[2].getFileName(); 16 | const callerDir = path.dirname(callerFile); 17 | target = path.resolve(callerDir, target); 18 | return oldRequire.call(this, target); 19 | }; 20 | 21 | oldRequire(path.resolve(__dirname, 'dispatch-test-worker')); 22 | -------------------------------------------------------------------------------- /test/fixtures/dispatch-test-worker.js: -------------------------------------------------------------------------------- 1 | const dispatch = require('../../src/dispatch/worker-dispatch'); 2 | const DispatchTestService = require('./dispatch-test-service'); 3 | const log = require('../../src/util/log'); 4 | 5 | dispatch.setService('RemoteDispatchTest', new DispatchTestService()); 6 | 7 | dispatch.waitForConnection.then(() => { 8 | dispatch.call('test', 'onWorkerReady').catch(e => { 9 | log(`Test worker failed to call onWorkerReady: ${JSON.stringify(e)}`); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/draggable.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/draggable.sb3 -------------------------------------------------------------------------------- /test/fixtures/edge-triggered-hat.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/edge-triggered-hat.sb3 -------------------------------------------------------------------------------- /test/fixtures/event.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/event.sb2 -------------------------------------------------------------------------------- /test/fixtures/example_sprite.sprite2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/example_sprite.sprite2 -------------------------------------------------------------------------------- /test/fixtures/execute/README.md: -------------------------------------------------------------------------------- 1 | Tests in this folder are run in scratch by integration/execute.js. The tests can SAY test messages that map to tap methods. Read integration/execute.js for more. 2 | -------------------------------------------------------------------------------- /test/fixtures/execute/broadcast-wait-arg-change.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/broadcast-wait-arg-change.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/control-if-false-then-else.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/control-if-false-then-else.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/control-if-false-then.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/control-if-false-then.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/control-if-true-then-else.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/control-if-true-then-else.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/control-if-true-then.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/control-if-true-then.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/control-stop-all-leaks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/control-stop-all-leaks.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/data-operators-global.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/data-operators-global.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/data-operators-local.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/data-operators-local.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/data-reporter-contents-global.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/data-reporter-contents-global.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/data-reporter-contents-local.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/data-reporter-contents-local.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/event-broadcast-and-wait-can-continue-same-tick.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/event-broadcast-and-wait-can-continue-same-tick.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/event-when-green-flag.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/event-when-green-flag.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/events-broadcast-and-wait-yields-a-tick.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/events-broadcast-and-wait-yields-a-tick.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/hat-thread-execution.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/hat-thread-execution.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/monitors-stage-name.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/monitors-stage-name.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/operators-not-blank.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/operators-not-blank.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-back-2-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-back-2-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-backwards-2-broadcast-and-wait-repeat-message.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-backwards-2-broadcast-and-wait-repeat-message.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-backwards-2-broadcast-and-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-backwards-2-broadcast-and-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-backwards-2-broadcast-no-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-backwards-2-broadcast-no-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-backwards-2-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-backwards-2-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-backwards-2-continuous.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-backwards-2-continuous.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-backwards-2-threads-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-backwards-2-threads-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-forewards-2-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-forewards-2-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-changes-front-2-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-changes-front-2-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-clones-backwards-2-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-clones-backwards-2-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-clones-backwards-broadcast-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-clones-backwards-broadcast-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-clones-static-2.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-clones-static-2.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-immobile-stage.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-immobile-stage.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-library-reverse.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-library-reverse.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-library-reverse.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-library-reverse.sb3 -------------------------------------------------------------------------------- /test/fixtures/execute/order-library.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-library.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/order-library.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/order-library.sb3 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-boolean-reporter-bug.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-boolean-reporter-bug.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-nested-missing-boolean-param.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-nested-missing-boolean-param.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-nested-missing-no-param.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-nested-missing-no-param.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-nested-missing-number-param.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-nested-missing-number-param.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-nested-missing-string-param.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-nested-missing-string-param.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-number-number-boolean.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-number-number-boolean.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-param-outside-boolean.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-param-outside-boolean.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-param-outside-number.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-param-outside-number.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-param-outside-string.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-param-outside-string.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-recursive-default-boolean.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-recursive-default-boolean.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-recursive-default-number.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-recursive-default-number.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/procedures-recursive-default-string.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/procedures-recursive-default-string.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/sensing-get-attribute-of-stage-alt-name.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/sensing-get-attribute-of-stage-alt-name.sb2 -------------------------------------------------------------------------------- /test/fixtures/execute/sprite-number-name.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/execute/sprite-number-name.sb2 -------------------------------------------------------------------------------- /test/fixtures/fake-bitmap-adapter.js: -------------------------------------------------------------------------------- 1 | const FakeBitmapAdapter = require('scratch-svg-renderer').BitmapAdapter; 2 | 3 | FakeBitmapAdapter.prototype.resize = function (canvas) { 4 | return canvas; 5 | }; 6 | 7 | module.exports = FakeBitmapAdapter; 8 | -------------------------------------------------------------------------------- /test/fixtures/hat-execution-order.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/hat-execution-order.sb2 -------------------------------------------------------------------------------- /test/fixtures/invisible-tempo-monitor-no-other-music-blocks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/invisible-tempo-monitor-no-other-music-blocks.sb2 -------------------------------------------------------------------------------- /test/fixtures/invisible-video-monitor.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/invisible-video-monitor.sb2 -------------------------------------------------------------------------------- /test/fixtures/list-monitor-rename.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/list-monitor-rename.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/README.md: -------------------------------------------------------------------------------- 1 | Tests in this folder are run in scratch by integration/load-extensions.js to determine whether an extension can load properly. The test projects in this folder are examples of non-core extensions usage. Read `integration/load-extensions.js` for more. 2 | 3 | ### Adding new extensions 4 | 5 | When extending Scratch with non-core extensions, save an example project to this the appropiate subdirectory based on which test in `load-extensions.js` will be using that test file. The file should use the following naming convention: 6 | 7 | `[extensionID]-rest-of-file-name.[file type sb3 or sb2]` 8 | 9 | The load-extensions.js test will automatically test this new project file since it gets a list of all files in its repsective subdirectories for testing and extracts the extension id from the first section of the file same separated by a dash. 10 | 11 | Each of the `[extensionID]-simple-project` test files have been made as the simplest possible cases for loading the extension. This means that only one block has been added to the project and that block is from the relevant extension. 12 | 13 | ### Adding more example projects 14 | 15 | Sometimes we need to test more complex projects to catch cases and contexts where an extension should load and doesn't. We can save those project files using the convention [extensionID]-project-name. For example, the Dolphins 3D project (#115870836) had a pen extension that wouldn't load, whereas `pen-simple-project.sb2` and `pen-simple-project.sb3` did pass these tests. For this reason, `pen-dolphin-3d.sb2` and `pen-dolphin-3d.sb3` are now part of the test examples. 16 | 17 | ### // TO DO 18 | The translation extension doesn't have test projects added for them yet since they need a little more infrastructure stubbed out in the test. 19 | -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/ev3-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/ev3-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/microbit-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/microbit-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/music-simple-project.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/music-simple-project.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/music-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/music-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/pen-dolphin-3d.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/pen-simple-project.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/pen-simple-project.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/pen-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/pen-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/text2speech-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/text2speech-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/videoSensing-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/confirm-load/wedo2-simple-project.sb3 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/music-visible-monitor-no-blocks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/music-visible-monitor-no-blocks.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/video-state/videoState-off.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/video-state/videoState-off.sb2 -------------------------------------------------------------------------------- /test/fixtures/load-extensions/video-state/videoState-on-transparency-0.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/load-extensions/video-state/videoState-on-transparency-0.sb2 -------------------------------------------------------------------------------- /test/fixtures/looks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/looks.sb2 -------------------------------------------------------------------------------- /test/fixtures/make-test-storage.js: -------------------------------------------------------------------------------- 1 | const {ScratchStorage} = require('scratch-storage'); 2 | 3 | const ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu/'; 4 | const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu/'; 5 | 6 | /** 7 | * @param {Asset} asset - calculate a URL for this asset. 8 | * @returns {string} a URL to download a project file. 9 | */ 10 | const getProjectUrl = function (asset) { 11 | const assetIdParts = asset.assetId.split('.'); 12 | const assetUrlParts = [PROJECT_SERVER, 'internalapi/project/', assetIdParts[0], '/get/']; 13 | if (assetIdParts[1]) { 14 | assetUrlParts.push(assetIdParts[1]); 15 | } 16 | return assetUrlParts.join(''); 17 | }; 18 | 19 | /** 20 | * @param {Asset} asset - calculate a URL for this asset. 21 | * @returns {string} a URL to download a project asset (PNG, WAV, etc.) 22 | */ 23 | const getAssetUrl = function (asset) { 24 | const assetUrlParts = [ 25 | ASSET_SERVER, 26 | 'internalapi/asset/', 27 | asset.assetId, 28 | '.', 29 | asset.dataFormat, 30 | '/get/' 31 | ]; 32 | return assetUrlParts.join(''); 33 | }; 34 | 35 | /** 36 | * Construct a new instance of ScratchStorage and provide it with default web sources. 37 | * @returns {ScratchStorage} - an instance of ScratchStorage, ready to be used for tests. 38 | */ 39 | const makeTestStorage = function () { 40 | const storage = new ScratchStorage(); 41 | const AssetType = storage.AssetType; 42 | storage.addWebStore([AssetType.Project], getProjectUrl); 43 | storage.addWebStore([AssetType.ImageVector, AssetType.ImageBitmap, AssetType.Sound], getAssetUrl); 44 | return storage; 45 | }; 46 | 47 | module.exports = makeTestStorage; 48 | -------------------------------------------------------------------------------- /test/fixtures/missing_png.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_png.sb2 -------------------------------------------------------------------------------- /test/fixtures/missing_png.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_png.sb3 -------------------------------------------------------------------------------- /test/fixtures/missing_png.sprite2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_png.sprite2 -------------------------------------------------------------------------------- /test/fixtures/missing_png.sprite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_png.sprite3 -------------------------------------------------------------------------------- /test/fixtures/missing_sound.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_sound.sb3 -------------------------------------------------------------------------------- /test/fixtures/missing_svg.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_svg.sb2 -------------------------------------------------------------------------------- /test/fixtures/missing_svg.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_svg.sb3 -------------------------------------------------------------------------------- /test/fixtures/missing_svg.sprite2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_svg.sprite2 -------------------------------------------------------------------------------- /test/fixtures/missing_svg.sprite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/missing_svg.sprite3 -------------------------------------------------------------------------------- /test/fixtures/monitored_variables.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/monitored_variables.sb3 -------------------------------------------------------------------------------- /test/fixtures/monitors.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/monitors.sb2 -------------------------------------------------------------------------------- /test/fixtures/monitors.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/monitors.sb3 -------------------------------------------------------------------------------- /test/fixtures/motion.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/motion.sb2 -------------------------------------------------------------------------------- /test/fixtures/offline-custom-assets.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/offline-custom-assets.sb2 -------------------------------------------------------------------------------- /test/fixtures/ordering.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/ordering.sb2 -------------------------------------------------------------------------------- /test/fixtures/origin-absent.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/origin-absent.sb3 -------------------------------------------------------------------------------- /test/fixtures/origin.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/origin.sb3 -------------------------------------------------------------------------------- /test/fixtures/pen.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/pen.sb2 -------------------------------------------------------------------------------- /test/fixtures/procedure.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/procedure.sb2 -------------------------------------------------------------------------------- /test/fixtures/readProjectFile.js: -------------------------------------------------------------------------------- 1 | const AdmZip = require('adm-zip'); 2 | const fs = require('fs'); 3 | 4 | module.exports = { 5 | readFileToBuffer: function (path) { 6 | return Buffer.from(fs.readFileSync(path)); 7 | }, 8 | extractProjectJson: function (path) { 9 | const zip = new AdmZip(path); 10 | const projectEntry = zip.getEntries().find(item => item.entryName.match(/project\.json/)); 11 | if (projectEntry) { 12 | return JSON.parse(zip.readAsText(projectEntry.entryName, 'utf8')); 13 | } 14 | return null; 15 | }, 16 | extractAsset: function (path, assetFileName) { 17 | const zip = new AdmZip(path); 18 | const assetEntry = zip.getEntries().find(item => item.entryName.match(assetFileName)); 19 | return assetEntry.getData(); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /test/fixtures/saythink-and-wait.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/saythink-and-wait.sb2 -------------------------------------------------------------------------------- /test/fixtures/sb2-from-sb1-missing-backdrop-image.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/sb2-from-sb1-missing-backdrop-image.sb2 -------------------------------------------------------------------------------- /test/fixtures/sensing.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/sensing.sb2 -------------------------------------------------------------------------------- /test/fixtures/simple-stack.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | id: '1ZGd(W8DvU?vI1RN)e0E', 4 | opcode: 'motion_goto', 5 | inputs: { 6 | TO: { 7 | name: 'TO', 8 | block: 'Ht(rOKMe0@sB3n4(b3;=', 9 | shadow: '-C=c-_TI_7d(l3ii2[wh' 10 | } 11 | }, 12 | fields: { 13 | 14 | }, 15 | next: 'l.JBk`WcXE+A@i9y1tCU', 16 | topLevel: true, 17 | parent: null, 18 | shadow: false 19 | }, 20 | { 21 | id: 'Ht(rOKMe0@sB3n4(b3;=', 22 | opcode: 'looks_size', 23 | inputs: { 24 | 25 | }, 26 | fields: { 27 | 28 | }, 29 | next: null, 30 | topLevel: false, 31 | parent: '1ZGd(W8DvU?vI1RN)e0E', 32 | shadow: false 33 | }, 34 | { 35 | id: '-C=c-_TI_7d(l3ii2[wh', 36 | opcode: 'motion_goto_menu', 37 | inputs: { 38 | 39 | }, 40 | fields: { 41 | TO: { 42 | name: 'TO', 43 | value: '_random_' 44 | } 45 | }, 46 | next: null, 47 | topLevel: false, 48 | parent: '1ZGd(W8DvU?vI1RN)e0E', 49 | shadow: true 50 | }, 51 | { 52 | id: 'l.JBk`WcXE+A@i9y1tCU', 53 | opcode: 'sound_stopallsounds', 54 | inputs: { 55 | 56 | }, 57 | fields: { 58 | 59 | }, 60 | next: null, 61 | topLevel: false, 62 | parent: '1ZGd(W8DvU?vI1RN)e0E', 63 | shadow: false 64 | } 65 | ]; 66 | -------------------------------------------------------------------------------- /test/fixtures/single_sound.sb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/single_sound.sb -------------------------------------------------------------------------------- /test/fixtures/sound.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/sound.sb2 -------------------------------------------------------------------------------- /test/fixtures/sprite.json: -------------------------------------------------------------------------------- 1 | { 2 | "objName": "Sprite1", 3 | "sounds": [{ 4 | "soundName": "meow", 5 | "soundID": 0, 6 | "md5": "83c36d806dc92327b9e7049a565c6bff.wav", 7 | "sampleCount": 18688, 8 | "rate": 22050, 9 | "format": "" 10 | }], 11 | "costumes": [{ 12 | "costumeName": "costume1", 13 | "baseLayerID": 0, 14 | "baseLayerMD5": "f9a1c175dbe2e5dee472858dd30d16bb.svg", 15 | "bitmapResolution": 1, 16 | "rotationCenterX": 47, 17 | "rotationCenterY": 55 18 | }, 19 | { 20 | "costumeName": "costume2", 21 | "baseLayerID": 1, 22 | "baseLayerMD5": "6e8bd9ae68fdb02b7e1e3df656a75635.svg", 23 | "bitmapResolution": 1, 24 | "rotationCenterX": 47, 25 | "rotationCenterY": 55 26 | }], 27 | "currentCostumeIndex": 0, 28 | "scratchX": 0, 29 | "scratchY": 0, 30 | "scale": 1, 31 | "direction": 90, 32 | "rotationStyle": "normal", 33 | "isDraggable": false, 34 | "indexInLibrary": 100000, 35 | "visible": true, 36 | "spriteInfo": { 37 | } 38 | } -------------------------------------------------------------------------------- /test/fixtures/stack-click.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/stack-click.sb2 -------------------------------------------------------------------------------- /test/fixtures/test-compare.js: -------------------------------------------------------------------------------- 1 | const testCompare = (t, lhs, op, rhs, message) => { 2 | const details = `Expected: ${lhs} ${op} ${rhs}`; 3 | const extra = {details}; 4 | switch (op) { 5 | case '<': return t.ok(lhs < rhs, message, extra); 6 | case '<=': return t.ok(lhs <= rhs, message, extra); 7 | case '===': return t.ok(lhs === rhs, message, extra); 8 | case '!==': return t.ok(lhs !== rhs, message, extra); 9 | case '>=': return t.ok(lhs >= rhs, message, extra); 10 | case '>': return t.ok(lhs > rhs, message, extra); 11 | default: return t.fail(`Unrecognized op: ${op}`); 12 | } 13 | }; 14 | 15 | module.exports = testCompare; 16 | -------------------------------------------------------------------------------- /test/fixtures/timer-greater-than-hat.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/timer-greater-than-hat.sb2 -------------------------------------------------------------------------------- /test/fixtures/timer-monitor.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/timer-monitor.sb3 -------------------------------------------------------------------------------- /test/fixtures/top-level-reporters.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/top-level-reporters.sb3 -------------------------------------------------------------------------------- /test/fixtures/top-level-variable-reporter.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/top-level-variable-reporter.sb2 -------------------------------------------------------------------------------- /test/fixtures/unknown-opcode-as-reporter-block.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/unknown-opcode-as-reporter-block.sb2 -------------------------------------------------------------------------------- /test/fixtures/unknown-opcode-in-c-block.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/unknown-opcode-in-c-block.sb2 -------------------------------------------------------------------------------- /test/fixtures/unknown-opcode.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/unknown-opcode.sb2 -------------------------------------------------------------------------------- /test/fixtures/variable_characters.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/variable_characters.sb2 -------------------------------------------------------------------------------- /test/fixtures/variable_characters.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/variable_characters.sb3 -------------------------------------------------------------------------------- /test/fixtures/visible-tempo-monitor-no-other-music-blocks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/visible-tempo-monitor-no-other-music-blocks.sb2 -------------------------------------------------------------------------------- /test/fixtures/visible-video-monitor-and-video-blocks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/visible-video-monitor-and-video-blocks.sb2 -------------------------------------------------------------------------------- /test/fixtures/visible-video-monitor-no-other-video-blocks.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/visible-video-monitor-no-other-video-blocks.sb2 -------------------------------------------------------------------------------- /test/fixtures/when-clicked.sb2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/fixtures/when-clicked.sb2 -------------------------------------------------------------------------------- /test/integration/block_to_workspace_comment_import.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const projectUri = path.resolve(__dirname, '../fixtures/block-to-workspace-comments.sb2'); 8 | const project = readFileToBuffer(projectUri); 9 | 10 | test('importing sb2 project where block comment is converted to workspace comment and block is deleted', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.equal(threads.length, 0); 18 | 19 | const target = vm.runtime.targets[1]; 20 | 21 | // Sprite 1 has 3 Comments, 1 block comment and 2 workspace comments (which were 22 | // originally created via a block comment to workspace comment conversion in Scratch 2.0). 23 | const targetComments = Object.values(target.comments); 24 | t.equal(targetComments.length, 3); 25 | const spriteWorkspaceComments = targetComments.filter(comment => comment.blockId === null); 26 | t.equal(spriteWorkspaceComments.length, 2); 27 | 28 | // Test the sprite block comments 29 | const blockComments = targetComments.filter(comment => !!comment.blockId); 30 | t.equal(blockComments.length, 1); 31 | 32 | // There should not be any comments where blockId is a number 33 | const invalidComments = targetComments.filter(comment => typeof comment.blockId === 'number'); 34 | t.equal(invalidComments.length, 0); 35 | 36 | vm.quit(); 37 | t.end(); 38 | }); 39 | 40 | // Start VM, load project, and run 41 | t.doesNotThrow(() => { 42 | vm.start(); 43 | vm.clear(); 44 | vm.setCompatibilityMode(false); 45 | vm.setTurboMode(false); 46 | vm.loadProject(project).then(() => { 47 | vm.greenFlag(); 48 | setTimeout(() => { 49 | vm.getPlaygroundData(); 50 | vm.stopAll(); 51 | }, 100); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/integration/block_to_workspace_comment_import_no_scripts.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const projectUri = path.resolve(__dirname, '../fixtures/block-to-workspace-comments-without-scripts.sb2'); 8 | const project = readFileToBuffer(projectUri); 9 | 10 | /* eslint-disable-next-line max-len */ 11 | test('importing sb2 project where block comment is converted to workspace comment and block is deleted, and there are no scripts on the workspace', t => { 12 | const vm = new VirtualMachine(); 13 | vm.attachStorage(makeTestStorage()); 14 | 15 | // Evaluate playground data and exit 16 | vm.on('playgroundData', e => { 17 | const threads = JSON.parse(e.threads); 18 | t.equal(threads.length, 0); 19 | 20 | const target = vm.runtime.targets[1]; 21 | 22 | // Sprite 1 has 1 comments, a workspace comment which was 23 | // originally created via a block comment to workspace comment conversion in Scratch 2.0. 24 | // What differentiates this test from above is that there are no scripts in this project. 25 | const targetComments = Object.values(target.comments); 26 | t.equal(targetComments.length, 1); 27 | const spriteWorkspaceComments = targetComments.filter(comment => comment.blockId === null); 28 | t.equal(spriteWorkspaceComments.length, 1); 29 | 30 | // Test the sprite block comments 31 | const blockComments = targetComments.filter(comment => !!comment.blockId); 32 | t.equal(blockComments.length, 0); 33 | 34 | // There should not be any comments where blockId is a number 35 | const invalidComments = targetComments.filter(comment => typeof comment.blockId === 'number'); 36 | t.equal(invalidComments.length, 0); 37 | 38 | const targetBlocks = Object.values(target.blocks._blocks); 39 | t.equal(targetBlocks.length, 0); 40 | 41 | vm.quit(); 42 | t.end(); 43 | }); 44 | 45 | // Start VM, load project, and run 46 | t.doesNotThrow(() => { 47 | vm.start(); 48 | vm.clear(); 49 | vm.setCompatibilityMode(false); 50 | vm.setTurboMode(false); 51 | vm.loadProject(project).then(() => { 52 | vm.greenFlag(); 53 | setTimeout(() => { 54 | vm.getPlaygroundData(); 55 | vm.stopAll(); 56 | }, 100); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/integration/control.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/control.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('control', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length > 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | }); 31 | }); 32 | 33 | // After two seconds, get playground data and stop 34 | setTimeout(() => { 35 | vm.getPlaygroundData(); 36 | vm.stopAll(); 37 | }, 2000); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/data.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/data.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('data', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', () => { 16 | // @todo Additional tests 17 | vm.quit(); 18 | t.end(); 19 | }); 20 | 21 | // Start VM, load project, and run 22 | t.doesNotThrow(() => { 23 | vm.start(); 24 | vm.clear(); 25 | vm.setCompatibilityMode(false); 26 | vm.setTurboMode(false); 27 | vm.loadProject(project).then(() => { 28 | vm.greenFlag(); 29 | 30 | // After two seconds, get playground data and stop 31 | setTimeout(() => { 32 | vm.getPlaygroundData(); 33 | vm.stopAll(); 34 | }, 2000); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/integration/delete-and-restore-sprite.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | 6 | const VirtualMachine = require('../../src/virtual-machine'); 7 | // const RenderedTarget = require('../../src/sprites/rendered-target'); 8 | 9 | const projectUri = path.resolve(__dirname, '../fixtures/default.sb2'); 10 | const project = readFileToBuffer(projectUri); 11 | 12 | const vm = new VirtualMachine(); 13 | 14 | test('spec', t => { 15 | t.type(vm.deleteSprite, 'function'); 16 | t.end(); 17 | }); 18 | 19 | test('default cat', t => { 20 | // Get default cat from .sprite2 21 | // const uri = path.resolve(__dirname, '../fixtures/example_sprite.sprite2'); 22 | // const sprite = readFileToBuffer(uri); 23 | 24 | vm.attachStorage(makeTestStorage()); 25 | 26 | // Evaluate playground data and exit 27 | vm.on('playgroundData', e => { 28 | const threads = JSON.parse(e.threads); 29 | t.ok(threads.length === 0); 30 | vm.quit(); 31 | t.end(); 32 | }); 33 | 34 | vm.start(); 35 | vm.clear(); 36 | vm.setCompatibilityMode(false); 37 | vm.setTurboMode(false); 38 | t.doesNotThrow(() => { 39 | vm.loadProject(project).then(() => { 40 | 41 | t.equal(vm.runtime.targets.length, 2); // stage and default sprite 42 | 43 | const defaultSprite = vm.runtime.targets[1]; 44 | 45 | // Delete the sprite 46 | const addSpriteBack = vm.deleteSprite(vm.runtime.targets[1].id); 47 | 48 | t.equal(vm.runtime.targets.length, 1); 49 | 50 | t.type(addSpriteBack, 'function'); 51 | 52 | addSpriteBack().then(() => { 53 | t.equal(vm.runtime.targets.length, 2); 54 | t.equal(vm.runtime.targets[1].getName(), defaultSprite.getName()); 55 | 56 | vm.greenFlag(); 57 | 58 | setTimeout(() => { 59 | vm.getPlaygroundData(); 60 | vm.stopAll(); 61 | }, 1000); 62 | }); 63 | }); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /test/integration/event.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/event.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('event', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length > 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | // After two seconds, get playground data and stop 32 | setTimeout(() => { 33 | vm.getPlaygroundData(); 34 | vm.stopAll(); 35 | }, 2000); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/hat-execution-order.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const projectUri = path.resolve(__dirname, '../fixtures/hat-execution-order.sb2'); 8 | const project = readFileToBuffer(projectUri); 9 | 10 | test('complex', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length === 0); 18 | 19 | const resultKey = Object.keys(vm.runtime.targets[0].variables)[0]; 20 | const results = vm.runtime.targets[0].variables[resultKey].value; 21 | t.deepEqual(results, ['3', '2', '1', 'stage']); 22 | 23 | vm.quit(); 24 | t.end(); 25 | }); 26 | 27 | // Start VM, load project, and run 28 | t.doesNotThrow(() => { 29 | vm.start(); 30 | vm.clear(); 31 | vm.setCompatibilityMode(false); 32 | vm.setTurboMode(false); 33 | vm.loadProject(project).then(() => { 34 | vm.greenFlag(); 35 | 36 | // After two seconds, get playground data and stop 37 | setTimeout(() => { 38 | vm.getPlaygroundData(); 39 | vm.stopAll(); 40 | }, 2000); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/integration/import-sb.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/single_sound.sb'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('default', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length === 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | const stageSounds = vm.runtime.targets[0].sprite.sounds; 32 | const firstSound = stageSounds[0]; 33 | 34 | // Check that the sound has the correct md5 35 | // This md5 was obtained from the asset server 36 | t.equal(firstSound.md5, 'edb9713dedbe9a2e05c09e0540182ef1.wav'); 37 | 38 | // After two seconds, get playground data and stop 39 | setTimeout(() => { 40 | vm.getPlaygroundData(); 41 | vm.stopAll(); 42 | }, 2000); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/integration/import-sb2-from-object.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const extractProjectJson = require('../fixtures/readProjectFile').extractProjectJson; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/default.sb2'); 8 | const project = extractProjectJson(uri); 9 | 10 | test('default', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length === 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | // After two seconds, get playground data and stop 32 | setTimeout(() => { 33 | vm.getPlaygroundData(); 34 | vm.stopAll(); 35 | }, 2000); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/import_nested_sb2.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const extractProjectJson = require('../fixtures/readProjectFile').extractProjectJson; 5 | 6 | const renderedTarget = require('../../src/sprites/rendered-target'); 7 | const runtime = require('../../src/engine/runtime'); 8 | const sb2 = require('../../src/serialization/sb2'); 9 | 10 | test('spec', t => { 11 | t.type(sb2.deserialize, 'function'); 12 | t.end(); 13 | }); 14 | 15 | test('nested default/*', t => { 16 | // Get SB2 JSON (string) 17 | const uri = path.resolve(__dirname, '../fixtures/default_nested.sb2'); 18 | const json = extractProjectJson(uri, 'default'); 19 | 20 | // Create runtime instance & load SB2 into it 21 | const rt = new runtime(); 22 | rt.attachStorage(makeTestStorage()); 23 | sb2.deserialize(json, rt).then(({targets}) => { 24 | // Test 25 | t.type(json, 'object'); 26 | t.type(rt, 'object'); 27 | t.type(targets, 'object'); 28 | 29 | t.ok(targets[0] instanceof renderedTarget); 30 | t.type(targets[0].id, 'string'); 31 | t.type(targets[0].blocks, 'object'); 32 | t.type(targets[0].variables, 'object'); 33 | t.type(targets[0].comments, 'object'); 34 | 35 | t.equal(targets[0].isOriginal, true); 36 | t.equal(targets[0].currentCostume, 0); 37 | t.equal(targets[0].isOriginal, true); 38 | t.equal(targets[0].isStage, true); 39 | 40 | t.ok(targets[1] instanceof renderedTarget); 41 | t.type(targets[1].id, 'string'); 42 | t.type(targets[1].blocks, 'object'); 43 | t.type(targets[1].variables, 'object'); 44 | t.type(targets[1].comments, 'object'); 45 | 46 | t.equal(targets[1].isOriginal, true); 47 | t.equal(targets[1].currentCostume, 0); 48 | t.equal(targets[1].isOriginal, true); 49 | t.equal(targets[1].isStage, false); 50 | t.end(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/integration/import_sb2.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const extractProjectJson = require('../fixtures/readProjectFile').extractProjectJson; 5 | 6 | const renderedTarget = require('../../src/sprites/rendered-target'); 7 | const runtime = require('../../src/engine/runtime'); 8 | const sb2 = require('../../src/serialization/sb2'); 9 | 10 | test('spec', t => { 11 | t.type(sb2.deserialize, 'function'); 12 | t.end(); 13 | }); 14 | 15 | test('default', t => { 16 | // Get SB2 JSON (string) 17 | const uri = path.resolve(__dirname, '../fixtures/default.sb2'); 18 | const json = extractProjectJson(uri); 19 | 20 | // Create runtime instance & load SB2 into it 21 | const rt = new runtime(); 22 | rt.attachStorage(makeTestStorage()); 23 | sb2.deserialize(json, rt).then(({targets}) => { 24 | // Test 25 | t.type(json, 'object'); 26 | t.type(rt, 'object'); 27 | t.type(targets, 'object'); 28 | 29 | t.ok(targets[0] instanceof renderedTarget); 30 | t.type(targets[0].id, 'string'); 31 | t.type(targets[0].blocks, 'object'); 32 | t.type(targets[0].variables, 'object'); 33 | t.type(targets[0].comments, 'object'); 34 | 35 | t.equal(targets[0].isOriginal, true); 36 | t.equal(targets[0].currentCostume, 0); 37 | t.equal(targets[0].isOriginal, true); 38 | t.equal(targets[0].isStage, true); 39 | 40 | t.ok(targets[1] instanceof renderedTarget); 41 | t.type(targets[1].id, 'string'); 42 | t.type(targets[1].blocks, 'object'); 43 | t.type(targets[1].variables, 'object'); 44 | t.type(targets[1].comments, 'object'); 45 | 46 | t.equal(targets[1].isOriginal, true); 47 | t.equal(targets[1].currentCostume, 0); 48 | t.equal(targets[1].isOriginal, true); 49 | t.equal(targets[1].isStage, false); 50 | t.end(); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/integration/list-monitor-rename.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const projectUri = path.resolve(__dirname, '../fixtures/list-monitor-rename.sb3'); 8 | const project = readFileToBuffer(projectUri); 9 | 10 | test('importing sb3 project with incorrect list monitor name', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', () => { 16 | const stage = vm.runtime.targets[0]; 17 | const cat = vm.runtime.targets[1]; 18 | 19 | for (const {target, renamedListName} of [ 20 | {target: stage, renamedListName: 'renamed global'}, 21 | {target: cat, renamedListName: 'renamed local'} 22 | ]) { 23 | const listId = Object.keys(target.variables).find(k => target.variables[k].name === renamedListName); 24 | 25 | const monitorRecord = vm.runtime._monitorState.get(listId); 26 | const monitorBlock = vm.runtime.monitorBlocks.getBlock(listId); 27 | t.equal(monitorRecord.opcode, 'data_listcontents'); 28 | 29 | // The list name should be properly renamed 30 | t.equal(monitorRecord.params.LIST, renamedListName); 31 | t.equal(monitorBlock.fields.LIST.value, renamedListName); 32 | } 33 | 34 | vm.quit(); 35 | t.end(); 36 | }); 37 | 38 | // Start VM, load project, and run 39 | t.doesNotThrow(() => { 40 | vm.start(); 41 | vm.clear(); 42 | vm.setCompatibilityMode(false); 43 | vm.setTurboMode(false); 44 | vm.loadProject(project).then(() => { 45 | vm.greenFlag(); 46 | setTimeout(() => { 47 | vm.getPlaygroundData(); 48 | vm.stopAll(); 49 | }, 100); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/integration/load-sb2-originally-sb1-without-backdrop-image.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | 6 | const VirtualMachine = require('../../src/virtual-machine'); 7 | 8 | const projectUri = path.resolve(__dirname, '../fixtures/sb2-from-sb1-missing-backdrop-image.sb2'); 9 | const project = readFileToBuffer(projectUri); 10 | 11 | const vm = new VirtualMachine(); 12 | 13 | test('sb2 project (originally from Scratch 1.4) with missing backdrop image should load', t => { 14 | vm.attachStorage(makeTestStorage()); 15 | 16 | // Evaluate playground data and exit 17 | vm.on('playgroundData', e => { 18 | const threads = JSON.parse(e.threads); 19 | t.ok(threads.length === 0); 20 | vm.quit(); 21 | t.end(); 22 | }); 23 | 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | t.doesNotThrow(() => { 29 | vm.loadProject(project).then(() => { 30 | 31 | t.equal(vm.runtime.targets.length, 2); // stage and default sprite 32 | 33 | vm.greenFlag(); 34 | 35 | setTimeout(() => { 36 | vm.getPlaygroundData(); 37 | vm.stopAll(); 38 | }, 1000); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/integration/looks.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/looks.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('looks', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length === 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | // After two seconds, get playground data and stop 32 | setTimeout(() => { 33 | vm.getPlaygroundData(); 34 | vm.stopAll(); 35 | }, 2000); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/motion.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/motion.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('motion', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length > 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | // After two seconds, get playground data and stop 32 | setTimeout(() => { 33 | vm.getPlaygroundData(); 34 | vm.stopAll(); 35 | }, 2000); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/pen.js: -------------------------------------------------------------------------------- 1 | const Worker = require('web-worker'); 2 | const path = require('path'); 3 | const test = require('tap').test; 4 | 5 | const Scratch3PenBlocks = require('../../src/extensions/scratch3_pen/index.js'); 6 | const VirtualMachine = require('../../src/index'); 7 | const dispatch = require('../../src/dispatch/central-dispatch'); 8 | 9 | const makeTestStorage = require('../fixtures/make-test-storage'); 10 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 11 | 12 | const uri = path.resolve(__dirname, '../fixtures/pen.sb2'); 13 | const project = readFileToBuffer(uri); 14 | 15 | // By default Central Dispatch works with the Worker class built into the browser. Tell it to use TinyWorker instead. 16 | dispatch.workerClass = Worker; 17 | 18 | test('pen', t => { 19 | const vm = new VirtualMachine(); 20 | vm.attachStorage(makeTestStorage()); 21 | 22 | // Evaluate playground data and exit 23 | vm.on('playgroundData', () => { 24 | // @todo Additional tests 25 | 26 | const catSprite = vm.runtime.targets[1].sprite; 27 | const [originalCat, cloneCat] = catSprite.clones; 28 | t.notStrictEqual(originalCat, cloneCat); 29 | 30 | /** @type {PenState} */ 31 | const originalPenState = originalCat.getCustomState(Scratch3PenBlocks.STATE_KEY); 32 | 33 | /** @type {PenState} */ 34 | const clonePenState = cloneCat.getCustomState(Scratch3PenBlocks.STATE_KEY); 35 | 36 | t.notStrictEqual(originalPenState, clonePenState); 37 | t.equal(originalPenState.penAttributes.diameter, 51); 38 | t.equal(clonePenState.penAttributes.diameter, 42); 39 | 40 | vm.quit(); 41 | t.end(); 42 | }); 43 | 44 | // Start VM, load project, and run 45 | t.doesNotThrow(() => { 46 | vm.start(); 47 | vm.clear(); 48 | vm.setCompatibilityMode(false); 49 | vm.setTurboMode(false); 50 | vm.loadProject(project) 51 | .then(() => { 52 | vm.greenFlag(); 53 | 54 | // After two seconds, get playground data and stop 55 | setTimeout(() => { 56 | vm.getPlaygroundData(); 57 | vm.stopAll(); 58 | }, 2000); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/integration/procedure.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/procedure.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('procedure', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length === 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | // After two seconds, get playground data and stop 32 | setTimeout(() => { 33 | vm.getPlaygroundData(); 34 | vm.stopAll(); 35 | }, 2000); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/running_project_changed_state.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/looks.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('Running project should not emit project changed event', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | let projectChanged = false; 15 | vm.on('PROJECT_CHANGED', () => { 16 | projectChanged = true; 17 | }); 18 | 19 | // Evaluate playground data and exit 20 | vm.on('playgroundData', () => { 21 | t.equal(projectChanged, false); 22 | vm.quit(); 23 | t.end(); 24 | }); 25 | 26 | // Start VM, load project, and run 27 | t.doesNotThrow(() => { 28 | vm.start(); 29 | vm.clear(); 30 | vm.setCompatibilityMode(false); 31 | vm.setTurboMode(false); 32 | vm.loadProject(project).then(() => { 33 | // The test in unit/project_load_changed_state.js tests 34 | // that loading a project does not emit a project changed 35 | // event. This setup tries to be agnostic of whether that 36 | // test is passing or failing. 37 | projectChanged = false; 38 | 39 | vm.greenFlag(); 40 | 41 | // After two seconds, get playground data and stop 42 | setTimeout(() => { 43 | vm.getPlaygroundData(); 44 | vm.stopAll(); 45 | }, 2000); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/integration/saythink-and-wait.js: -------------------------------------------------------------------------------- 1 | const Worker = require('web-worker'); 2 | const path = require('path'); 3 | const test = require('tap').test; 4 | const makeTestStorage = require('../fixtures/make-test-storage'); 5 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 6 | const VirtualMachine = require('../../src/index'); 7 | const dispatch = require('../../src/dispatch/central-dispatch'); 8 | 9 | const uri = path.resolve(__dirname, '../fixtures/saythink-and-wait.sb2'); 10 | const project = readFileToBuffer(uri); 11 | 12 | // By default Central Dispatch works with the Worker class built into the browser. Tell it to use TinyWorker instead. 13 | dispatch.workerClass = Worker; 14 | 15 | test('say/think and wait', t => { 16 | const vm = new VirtualMachine(); 17 | vm.attachStorage(makeTestStorage()); 18 | 19 | // Start VM, load project, and run 20 | t.doesNotThrow(() => { 21 | vm.start(); 22 | vm.clear(); 23 | vm.setCompatibilityMode(false); 24 | vm.setTurboMode(false); 25 | vm.loadProject(project).then(() => { 26 | vm.greenFlag(); 27 | 28 | // After two seconds, stop the project. 29 | // The test will fail if the project throws. 30 | setTimeout(() => { 31 | vm.stopAll(); 32 | vm.quit(); 33 | t.end(); 34 | }, 2000); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/integration/sensing.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/sensing.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('sensing', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | // Evaluate playground data and exit 15 | vm.on('playgroundData', e => { 16 | const threads = JSON.parse(e.threads); 17 | t.ok(threads.length > 0); 18 | vm.quit(); 19 | t.end(); 20 | }); 21 | 22 | // Start VM, load project, and run 23 | t.doesNotThrow(() => { 24 | vm.start(); 25 | vm.clear(); 26 | vm.setCompatibilityMode(false); 27 | vm.setTurboMode(false); 28 | vm.loadProject(project).then(() => { 29 | vm.greenFlag(); 30 | 31 | // After two seconds, get playground data and stop 32 | setTimeout(() => { 33 | vm.getPlaygroundData(); 34 | vm.stopAll(); 35 | }, 2000); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/sound.js: -------------------------------------------------------------------------------- 1 | const Worker = require('web-worker'); 2 | const path = require('path'); 3 | const test = require('tap').test; 4 | const makeTestStorage = require('../fixtures/make-test-storage'); 5 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 6 | const VirtualMachine = require('../../src/index'); 7 | const dispatch = require('../../src/dispatch/central-dispatch'); 8 | 9 | const uri = path.resolve(__dirname, '../fixtures/sound.sb2'); 10 | const project = readFileToBuffer(uri); 11 | 12 | // By default Central Dispatch works with the Worker class built into the browser. Tell it to use TinyWorker instead. 13 | dispatch.workerClass = Worker; 14 | 15 | test('sound', t => { 16 | const vm = new VirtualMachine(); 17 | vm.attachStorage(makeTestStorage()); 18 | 19 | // Evaluate playground data and exit 20 | vm.on('playgroundData', e => { 21 | const threads = JSON.parse(e.threads); 22 | t.ok(threads.length > 0); 23 | vm.quit(); 24 | t.end(); 25 | }); 26 | 27 | // Start VM, load project, and run 28 | t.doesNotThrow(() => { 29 | vm.start(); 30 | vm.clear(); 31 | vm.setCompatibilityMode(false); 32 | vm.setTurboMode(false); 33 | vm.loadProject(project).then(() => { 34 | vm.greenFlag(); 35 | 36 | // After two seconds, get playground data and stop 37 | setTimeout(() => { 38 | vm.getPlaygroundData(); 39 | vm.stopAll(); 40 | }, 2000); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/integration/stack-click.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const projectUri = path.resolve(__dirname, '../fixtures/stack-click.sb2'); 8 | const project = readFileToBuffer(projectUri); 9 | 10 | /** 11 | * stack-click.sb2 contains a sprite at (0, 0) with a single stack 12 | * when timer > 100000000 13 | * move 100 steps 14 | * The intention is to make sure that the stack can be activated by a stack click 15 | * even when the hat predicate is false. 16 | */ 17 | test('stack click activates the stack', t => { 18 | const vm = new VirtualMachine(); 19 | vm.attachStorage(makeTestStorage()); 20 | 21 | // Evaluate playground data and exit 22 | vm.on('playgroundData', () => { 23 | // The sprite should have moved 100 to the right 24 | t.equal(vm.editingTarget.x, 100); 25 | vm.quit(); 26 | t.end(); 27 | }); 28 | 29 | // Start VM, load project, and run 30 | t.doesNotThrow(() => { 31 | vm.start(); 32 | vm.clear(); 33 | vm.setCompatibilityMode(false); 34 | vm.setTurboMode(false); 35 | vm.loadProject(project).then(() => { 36 | const blockContainer = vm.runtime.targets[1].blocks; 37 | const allBlocks = blockContainer._blocks; 38 | 39 | // Confirm the editing target is initially at 0 40 | t.equal(vm.editingTarget.x, 0); 41 | 42 | // Find hat for greater than and click it 43 | for (const blockId in allBlocks) { 44 | if (allBlocks[blockId].opcode === 'event_whengreaterthan') { 45 | blockContainer.blocklyListen({ 46 | blockId: blockId, 47 | element: 'stackclick' 48 | }); 49 | } 50 | } 51 | 52 | // After two seconds, get playground data and stop 53 | setTimeout(() => { 54 | vm.getPlaygroundData(); 55 | vm.stopAll(); 56 | }, 2000); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/integration/unknown-opcode-as-reporter-block.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/unknown-opcode-as-reporter-block.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('unknown opcode', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | vm.start(); 15 | vm.clear(); 16 | vm.setCompatibilityMode(false); 17 | vm.setTurboMode(false); 18 | vm.loadProject(project).then(() => { 19 | vm.greenFlag(); 20 | 21 | // The project has 4 blocks in a single stack: 22 | // when green flag 23 | // if "unknown block" 24 | // set volume to "unknown block" 25 | // play sound "unknown block" 26 | // the "unknown block" has unknown opcode and was created by 27 | // dragging a discontinued extension. 28 | // It should be parsed in without error and a shadow block 29 | // should be created where appropriate. 30 | const blocks = vm.runtime.targets[0].blocks; 31 | const topBlockId = blocks.getScripts()[0]; 32 | const secondBlockId = blocks.getNextBlock(topBlockId); 33 | const thirdBlockId = blocks.getNextBlock(secondBlockId); 34 | const fourthBlockId = blocks.getNextBlock(thirdBlockId); 35 | 36 | t.equal(blocks.getBlock(topBlockId).opcode, 'event_whenflagclicked'); 37 | t.equal(blocks.getBlock(secondBlockId).opcode, 'control_wait_until'); 38 | t.equal(blocks.getBlock(thirdBlockId).opcode, 'sound_setvolumeto'); 39 | t.equal(blocks.getBlock(fourthBlockId).opcode, 'sound_play'); 40 | 41 | const secondBlockInputId = blocks.getBlock(secondBlockId).inputs.CONDITION.block; 42 | const thirdBlockInputId = blocks.getBlock(thirdBlockId).inputs.VOLUME.block; 43 | const fourthBlockInputId = blocks.getBlock(fourthBlockId).inputs.SOUND_MENU.block; 44 | 45 | t.equal(secondBlockInputId, null); 46 | t.true(blocks.getBlock(thirdBlockInputId).shadow); 47 | t.equal(blocks.getBlock(thirdBlockInputId).opcode, 'math_number'); 48 | t.true(blocks.getBlock(fourthBlockInputId).shadow); 49 | t.equal(blocks.getBlock(fourthBlockInputId).opcode, 'sound_sounds_menu'); 50 | 51 | vm.quit(); 52 | t.end(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/integration/unknown-opcode-in-c-block.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/unknown-opcode-in-c-block.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('unknown opcode', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | vm.start(); 15 | vm.clear(); 16 | vm.setCompatibilityMode(false); 17 | vm.setTurboMode(false); 18 | vm.loadProject(project).then(() => { 19 | vm.greenFlag(); 20 | 21 | // The project has 3 blocks in a single stack: 22 | // when green flag => forever [ => "undefined"] 23 | // the "undefined" block has opcode "foo" and was created by dragging 24 | // a custom procedure caller named foo from the backpack into a project. 25 | // It should be parsed in without error and it should 26 | // leave the forever block empty. 27 | const blocks = vm.runtime.targets[1].blocks; 28 | const topBlockId = blocks.getScripts()[0]; 29 | const secondBlockId = blocks.getNextBlock(topBlockId); 30 | const innerBlockId = blocks.getBranch(secondBlockId, 0); 31 | 32 | t.equal(blocks.getBlock(topBlockId).opcode, 'event_whenflagclicked'); 33 | t.equal(blocks.getBlock(secondBlockId).opcode, 'control_forever'); 34 | t.equal(innerBlockId, null); 35 | 36 | vm.quit(); 37 | t.end(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/integration/unknown-opcode.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const uri = path.resolve(__dirname, '../fixtures/unknown-opcode.sb2'); 8 | const project = readFileToBuffer(uri); 9 | 10 | test('unknown opcode', t => { 11 | const vm = new VirtualMachine(); 12 | vm.attachStorage(makeTestStorage()); 13 | 14 | vm.start(); 15 | vm.clear(); 16 | vm.setCompatibilityMode(false); 17 | vm.setTurboMode(false); 18 | vm.loadProject(project).then(() => { 19 | vm.greenFlag(); 20 | 21 | // The project has 3 blocks in a single stack: 22 | // play sound => "undefined" => play sound 23 | // the "undefined" block has opcode "0" and was found in the wild. 24 | // It should be parsed in without error and it should bridge together 25 | // the two play sound blocks, so there should not be a third block. 26 | const blocks = vm.runtime.targets[0].blocks; 27 | const topBlockId = blocks.getScripts()[0]; 28 | const secondBlockId = blocks.getNextBlock(topBlockId); 29 | const thirdBlockId = blocks.getNextBlock(secondBlockId); 30 | 31 | t.equal(blocks.getBlock(topBlockId).opcode, 'sound_play'); 32 | t.equal(blocks.getBlock(secondBlockId).opcode, 'sound_play'); 33 | t.equal(thirdBlockId, null); 34 | 35 | const target = vm.runtime.targets[0]; 36 | const topCommentId = blocks.getBlock(topBlockId).comment; 37 | const secondCommentId = blocks.getBlock(secondBlockId).comment; 38 | 39 | t.equal(target.comments[topCommentId].text, 'pop1 comment'); 40 | t.equal(target.comments[secondCommentId].text, 'pop2 comment'); 41 | 42 | // The comment previously attached to the undefined block should become 43 | // a workspace comment, at 0/0, with the same text as it had. 44 | const undefinedCommentId = Object.keys(target.comments).filter(id => 45 | id !== topCommentId && id !== secondCommentId)[0]; 46 | const undefinedComment = target.comments[undefinedCommentId]; 47 | t.equal(undefinedComment.blockId, null); 48 | t.equal(undefinedComment.text, 'undefined comment'); 49 | t.equal(undefinedComment.x, 0); 50 | t.equal(undefinedComment.y, 0); 51 | 52 | vm.quit(); 53 | t.end(); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /test/integration/variable_monitor_reset.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const test = require('tap').test; 3 | const makeTestStorage = require('../fixtures/make-test-storage'); 4 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 5 | const VirtualMachine = require('../../src/index'); 6 | 7 | const projectUri = path.resolve(__dirname, '../fixtures/monitored_variables.sb3'); 8 | const project = readFileToBuffer(projectUri); 9 | 10 | const anotherProjectUri = path.resolve(__dirname, '../fixtures/default.sb2'); 11 | const anotherProject = readFileToBuffer(anotherProjectUri); 12 | 13 | test('importing one project after the other resets monitored variables', t => { 14 | const vm = new VirtualMachine(); 15 | vm.attachStorage(makeTestStorage()); 16 | 17 | // Start VM, load project, and run 18 | vm.start(); 19 | vm.clear(); 20 | vm.setCompatibilityMode(false); 21 | vm.setTurboMode(false); 22 | vm.loadProject(project).then(() => { 23 | const refSprite = vm.runtime.targets[1]; 24 | const refVarId = Object.keys(refSprite.variables)[0]; 25 | const refBlock = vm.runtime.monitorBlocks.getBlock(refVarId); 26 | t.ok(refBlock); 27 | const jamalSprite = vm.runtime.targets[2]; 28 | const jamalVarId = Object.keys(jamalSprite.variables)[0]; 29 | const jamalBlock = vm.runtime.monitorBlocks.getBlock(jamalVarId); 30 | t.ok(jamalBlock); 31 | vm.loadProject(anotherProject).then(() => { 32 | // Blocks from the previously loaded project should be gone. 33 | const refVarBlock = vm.runtime.monitorBlocks.getBlock(refVarId); 34 | t.notOk(refVarBlock); 35 | const jamalVarBlock = vm.runtime.monitorBlocks.getBlock(jamalVarId); 36 | t.notOk(jamalVarBlock); 37 | 38 | vm.quit(); 39 | t.end(); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/blocks_data.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Data = require('../../src/blocks/scratch3_data'); 3 | 4 | const blocks = new Data(); 5 | 6 | const lists = {}; 7 | const util = { 8 | target: { 9 | lookupOrCreateList (id, name) { 10 | if (!(name in lists)) { 11 | lists[name] = {value: []}; 12 | } 13 | return lists[name]; 14 | } 15 | } 16 | }; 17 | 18 | test('getItemNumOfList returns the index of an item (basic)', t => { 19 | lists.list = {value: ['apple', 'taco', 'burrito', 'extravaganza']}; 20 | const args = {ITEM: 'burrito', LIST: {name: 'list'}}; 21 | const index = blocks.getItemNumOfList(args, util); 22 | t.strictEqual(index, 3); 23 | t.end(); 24 | }); 25 | 26 | test('getItemNumOfList returns 0 when an item is not found', t => { 27 | lists.list = {value: ['aaaaapple', 'burrito']}; 28 | const args = {ITEM: 'jump', LIST: {name: 'list'}}; 29 | const index = blocks.getItemNumOfList(args, util); 30 | t.strictEqual(index, 0); 31 | t.end(); 32 | }); 33 | 34 | test('getItemNumOfList uses Scratch comparison', t => { 35 | lists.list = {value: ['jump', 'Jump', '123', 123, 800]}; 36 | const args = {LIST: {name: 'list'}}; 37 | 38 | // Be case-insensitive: 39 | args.ITEM = 'Jump'; 40 | t.strictEqual(blocks.getItemNumOfList(args, util), 1); 41 | 42 | // Be type-insensitive: 43 | args.ITEM = 123; 44 | t.strictEqual(blocks.getItemNumOfList(args, util), 3); 45 | args.ITEM = '800'; 46 | t.strictEqual(blocks.getItemNumOfList(args, util), 5); 47 | 48 | t.end(); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/blocks_motion.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Motion = require('../../src/blocks/scratch3_motion'); 3 | const Runtime = require('../../src/engine/runtime'); 4 | const Sprite = require('../../src/sprites/sprite.js'); 5 | const RenderedTarget = require('../../src/sprites/rendered-target.js'); 6 | 7 | test('getPrimitives', t => { 8 | const rt = new Runtime(); 9 | const motion = new Motion(rt); 10 | t.type(motion.getPrimitives(), 'object'); 11 | t.end(); 12 | }); 13 | 14 | test('Coordinates have limited precision', t => { 15 | const rt = new Runtime(); 16 | const motion = new Motion(rt); 17 | const sprite = new Sprite(null, rt); 18 | const target = new RenderedTarget(sprite, rt); 19 | const util = {target}; 20 | 21 | motion.goToXY({X: 0.999999999, Y: 0.999999999}, util); 22 | 23 | t.equals(motion.getX({}, util), 1); 24 | t.equals(motion.getY({}, util), 1); 25 | t.end(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/blocks_procedures.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Procedures = require('../../src/blocks/scratch3_procedures'); 3 | 4 | const blocks = new Procedures(null); 5 | 6 | test('getPrimitives', t => { 7 | t.type(blocks.getPrimitives(), 'object'); 8 | t.end(); 9 | }); 10 | 11 | // Originally inspired by https://github.com/scratchfoundation/scratch-gui/issues/809 12 | test('calling a custom block with no definition does not throw', t => { 13 | const args = { 14 | mutation: { 15 | proccode: 'undefined proc' 16 | } 17 | }; 18 | const util = { 19 | getProcedureParamNamesIdsAndDefaults: () => null, 20 | stackFrame: { 21 | executed: false 22 | } 23 | }; 24 | t.doesNotThrow(() => { 25 | blocks.call(args, util); 26 | }); 27 | t.end(); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/blocks_sounds.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Sound = require('../../src/blocks/scratch3_sound'); 3 | let playedSound; 4 | 5 | const blocks = new Sound(); 6 | const util = { 7 | target: { 8 | sprite: { 9 | sounds: [ 10 | {name: 'first name', soundId: 'first soundId'}, 11 | {name: 'second name', soundId: 'second soundId'}, 12 | {name: 'third name', soundId: 'third soundId'}, 13 | {name: '6', soundId: 'fourth soundId'} 14 | ], 15 | soundBank: { 16 | playSound: (target, soundId) => (playedSound = soundId) 17 | } 18 | } 19 | } 20 | }; 21 | 22 | test('playSound with a name string works', t => { 23 | const args = {SOUND_MENU: 'second name'}; 24 | blocks.playSound(args, util); 25 | t.strictEqual(playedSound, 'second soundId'); 26 | t.end(); 27 | }); 28 | 29 | test('playSound with a number string works 1-indexed', t => { 30 | let args = {SOUND_MENU: '5'}; 31 | blocks.playSound(args, util); 32 | t.strictEqual(playedSound, 'first soundId'); 33 | 34 | args = {SOUND_MENU: '1'}; 35 | blocks.playSound(args, util); 36 | t.strictEqual(playedSound, 'first soundId'); 37 | 38 | args = {SOUND_MENU: '0'}; 39 | blocks.playSound(args, util); 40 | t.strictEqual(playedSound, 'fourth soundId'); 41 | t.end(); 42 | }); 43 | 44 | test('playSound with a number works 1-indexed', t => { 45 | let args = {SOUND_MENU: 5}; 46 | blocks.playSound(args, util); 47 | t.strictEqual(playedSound, 'first soundId'); 48 | 49 | args = {SOUND_MENU: 1}; 50 | blocks.playSound(args, util); 51 | t.strictEqual(playedSound, 'first soundId'); 52 | 53 | args = {SOUND_MENU: 0}; 54 | blocks.playSound(args, util); 55 | t.strictEqual(playedSound, 'fourth soundId'); 56 | t.end(); 57 | }); 58 | 59 | test('playSound prioritizes sound index if given a number', t => { 60 | const args = {SOUND_MENU: 6}; 61 | blocks.playSound(args, util); 62 | // Ignore the sound named '6', wrapClamp to the second instead 63 | t.strictEqual(playedSound, 'second soundId'); 64 | t.end(); 65 | }); 66 | 67 | test('playSound prioritizes sound name if given a string', t => { 68 | const args = {SOUND_MENU: '6'}; 69 | blocks.playSound(args, util); 70 | // Use the sound named '6', which is the fourth 71 | t.strictEqual(playedSound, 'fourth soundId'); 72 | t.end(); 73 | }); 74 | -------------------------------------------------------------------------------- /test/unit/engine_mutation-adapter.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | 3 | const mutationAdapter = require('../../src/engine/mutation-adapter'); 4 | 5 | test('spec', t => { 6 | t.type(mutationAdapter, 'function'); 7 | t.end(); 8 | }); 9 | 10 | test('convert DOM to Scratch object', t => { 11 | const testStringRaw = '"arbitrary" & \'complicated\' test string'; 12 | const testStringEscaped = '\\"arbitrary\\" & 'complicated' test string'; 13 | const xml = ``; 14 | const expectedMutation = { 15 | tagName: 'mutation', 16 | children: [], 17 | blockInfo: { 18 | text: testStringRaw 19 | } 20 | }; 21 | 22 | // TODO: do we want to test passing a DOM node to `mutationAdapter`? Node.js doesn't have built-in DOM support... 23 | const mutationFromString = mutationAdapter(xml); 24 | t.deepEqual(mutationFromString, expectedMutation); 25 | t.end(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/extension_microbit.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | // const MicroBit = require('../../src/extensions/scratch3_microbit/index.js'); 3 | 4 | test('displayText', t => { 5 | t.end(); 6 | }); 7 | 8 | test('displayMatrix', t => { 9 | t.end(); 10 | }); 11 | 12 | // etc... 13 | -------------------------------------------------------------------------------- /test/unit/extension_music.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Music = require('../../src/extensions/scratch3_music/index.js'); 3 | 4 | const fakeRuntime = { 5 | getTargetForStage: () => ({tempo: 60}), 6 | on: () => {} // Stub out listener methods used in constructor. 7 | }; 8 | 9 | const blocks = new Music(fakeRuntime); 10 | 11 | const util = { 12 | stackFrame: Object.create(null), 13 | target: { 14 | audioPlayer: null 15 | }, 16 | yield: () => null 17 | }; 18 | 19 | test('playDrum uses 1-indexing and wrap clamps', t => { 20 | // Stub playDrumNum 21 | let playedDrum; 22 | blocks._playDrumNum = (_util, drum) => (playedDrum = drum); 23 | 24 | let args = {DRUM: 1}; 25 | blocks.playDrumForBeats(args, util); 26 | t.strictEqual(playedDrum, 0); 27 | 28 | args = {DRUM: blocks.DRUM_INFO.length + 1}; 29 | blocks.playDrumForBeats(args, util); 30 | t.strictEqual(playedDrum, 0); 31 | 32 | t.end(); 33 | }); 34 | 35 | test('setInstrument uses 1-indexing and wrap clamps', t => { 36 | // Stub getMusicState 37 | const state = {currentInstrument: 0}; 38 | blocks._getMusicState = () => state; 39 | 40 | let args = {INSTRUMENT: 1}; 41 | blocks.setInstrument(args, util); 42 | t.strictEqual(state.currentInstrument, 0); 43 | 44 | args = {INSTRUMENT: blocks.INSTRUMENT_INFO.length + 1}; 45 | blocks.setInstrument(args, util); 46 | t.strictEqual(state.currentInstrument, 0); 47 | 48 | t.end(); 49 | }); 50 | -------------------------------------------------------------------------------- /test/unit/extension_text_to_speech.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const TextToSpeech = require('../../src/extensions/scratch3_text2speech/index.js'); 3 | 4 | const fakeStage = { 5 | textToSpeechLanguage: null 6 | }; 7 | 8 | const fakeRuntime = { 9 | getTargetForStage: () => fakeStage, 10 | on: () => {} // Stub out listener methods used in constructor. 11 | }; 12 | 13 | const ext = new TextToSpeech(fakeRuntime); 14 | 15 | test('if no language is saved in the project, use default', t => { 16 | t.strictEqual(ext.getCurrentLanguage(), 'en'); 17 | t.end(); 18 | }); 19 | 20 | test('if an unsupported language is dropped onto the set language block, use default', t => { 21 | ext.setLanguage({LANGUAGE: 'nope'}); 22 | t.strictEqual(ext.getCurrentLanguage(), 'en'); 23 | t.end(); 24 | }); 25 | 26 | test('if a supported language name is dropped onto the set language block, use it', t => { 27 | ext.setLanguage({LANGUAGE: 'español'}); 28 | t.strictEqual(ext.getCurrentLanguage(), 'es'); 29 | t.end(); 30 | }); 31 | 32 | test('get the extension locale for a supported locale that differs', t => { 33 | ext.setLanguage({LANGUAGE: 'ja-hira'}); 34 | t.strictEqual(ext.getCurrentLanguage(), 'ja'); 35 | t.end(); 36 | }); 37 | 38 | test('use localized spoken language name in place of localized written language name', t => { 39 | ext.getEditorLanguage = () => 'es'; 40 | const languageMenu = ext.getLanguageMenu(); 41 | const localizedNameForChineseInSpanish = languageMenu.find(el => el.value === 'zh-cn').text; 42 | t.strictEqual(localizedNameForChineseInSpanish, 'Chino (Mandarín)'); // i.e. should not be 'Chino (simplificado)' 43 | t.end(); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unit/extension_video_sensing_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/unit/extension_video_sensing_center.png -------------------------------------------------------------------------------- /test/unit/extension_video_sensing_down-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/unit/extension_video_sensing_down-10.png -------------------------------------------------------------------------------- /test/unit/extension_video_sensing_left-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/unit/extension_video_sensing_left-10.png -------------------------------------------------------------------------------- /test/unit/extension_video_sensing_left-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scratchfoundation/scratch-vm/bb9ac402523604451b32c3da9089b14adf6d3f60/test/unit/extension_video_sensing_left-5.png -------------------------------------------------------------------------------- /test/unit/io_clock.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Clock = require('../../src/io/clock'); 3 | const Runtime = require('../../src/engine/runtime'); 4 | 5 | test('spec', t => { 6 | const rt = new Runtime(); 7 | const c = new Clock(rt); 8 | 9 | t.type(Clock, 'function'); 10 | t.type(c, 'object'); 11 | t.type(c.projectTimer, 'function'); 12 | t.type(c.pause, 'function'); 13 | t.type(c.resume, 'function'); 14 | t.type(c.resetProjectTimer, 'function'); 15 | t.end(); 16 | }); 17 | 18 | test('cycle', t => { 19 | const rt = new Runtime(); 20 | const c = new Clock(rt); 21 | 22 | t.ok(c.projectTimer() <= 0.1); 23 | setTimeout(() => { 24 | c.resetProjectTimer(); 25 | setTimeout(() => { 26 | // The timer shouldn't advance until all threads have been stepped 27 | t.ok(c.projectTimer() === 0); 28 | c.pause(); 29 | t.ok(c.projectTimer() === 0); 30 | c.resume(); 31 | t.ok(c.projectTimer() === 0); 32 | t.end(); 33 | }, 100); 34 | }, 100); 35 | rt._step(); 36 | t.ok(c.projectTimer() > 0); 37 | }); 38 | -------------------------------------------------------------------------------- /test/unit/io_mousewheel.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const MouseWheel = require('../../src/io/mouseWheel'); 3 | const Runtime = require('../../src/engine/runtime'); 4 | 5 | test('spec', t => { 6 | const rt = new Runtime(); 7 | const mw = new MouseWheel(rt); 8 | 9 | t.type(mw, 'object'); 10 | t.type(mw.postData, 'function'); 11 | t.end(); 12 | }); 13 | 14 | test('blocks activated by scrolling', t => { 15 | let _startHatsArgs; 16 | const rt = { 17 | startHats: (...args) => { 18 | _startHatsArgs = args; 19 | } 20 | }; 21 | const mw = new MouseWheel(rt); 22 | 23 | _startHatsArgs = null; 24 | mw.postData({ 25 | deltaY: -1 26 | }); 27 | t.strictEquals(_startHatsArgs[0], 'event_whenkeypressed'); 28 | t.strictEquals(_startHatsArgs[1].KEY_OPTION, 'up arrow'); 29 | 30 | _startHatsArgs = null; 31 | mw.postData({ 32 | deltaY: +1 33 | }); 34 | t.strictEquals(_startHatsArgs[0], 'event_whenkeypressed'); 35 | t.strictEquals(_startHatsArgs[1].KEY_OPTION, 'down arrow'); 36 | 37 | _startHatsArgs = null; 38 | mw.postData({ 39 | deltaY: 0 40 | }); 41 | t.strictEquals(_startHatsArgs, null); 42 | 43 | t.end(); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unit/io_scratchBLE.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | // const ScratchBLE = require('../../src/io/scratchBLE'); 3 | 4 | test('constructor', t => { 5 | t.end(); 6 | }); 7 | 8 | test('waitForSocket', t => { 9 | t.end(); 10 | }); 11 | 12 | test('requestPeripheral', t => { 13 | t.end(); 14 | }); 15 | 16 | test('didReceiveCall', t => { 17 | t.end(); 18 | }); 19 | 20 | test('read', t => { 21 | t.end(); 22 | }); 23 | 24 | test('write', t => { 25 | t.end(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/io_scratchBT.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | // const ScratchBT = require('../../src/io/scratchBT'); 3 | 4 | test('constructor', t => { 5 | t.end(); 6 | }); 7 | 8 | test('requestPeripheral', t => { 9 | t.end(); 10 | }); 11 | 12 | test('connectPeripheral', t => { 13 | t.end(); 14 | }); 15 | 16 | test('sendMessage', t => { 17 | t.end(); 18 | }); 19 | 20 | test('didReceiveCall', t => { 21 | t.end(); 22 | }); 23 | -------------------------------------------------------------------------------- /test/unit/io_userData.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const UserData = require('../../src/io/userData'); 3 | 4 | test('spec', t => { 5 | const userData = new UserData(); 6 | 7 | t.type(userData, 'object'); 8 | t.type(userData.postData, 'function'); 9 | t.type(userData.getUsername, 'function'); 10 | t.end(); 11 | }); 12 | 13 | test('getUsername returns empty string initially', t => { 14 | const userData = new UserData(); 15 | 16 | t.strictEquals(userData.getUsername(), ''); 17 | t.end(); 18 | }); 19 | 20 | test('postData sets the username', t => { 21 | const userData = new UserData(); 22 | userData.postData({username: 'TEST'}); 23 | t.strictEquals(userData.getUsername(), 'TEST'); 24 | t.end(); 25 | }); 26 | -------------------------------------------------------------------------------- /test/unit/maybe_format_message.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const maybeFormatMessage = require('../../src/util/maybe-format-message'); 3 | 4 | const nonMessages = [ 5 | 'hi', 6 | 42, 7 | true, 8 | function () { 9 | return 'unused'; 10 | }, 11 | { 12 | a: 1, 13 | b: 2 14 | }, 15 | { 16 | id: 'almost a message', 17 | notDefault: 'but missing the "default" property' 18 | }, 19 | { 20 | notId: 'this one is missing the "id" property', 21 | default: 'but has "default"' 22 | } 23 | ]; 24 | 25 | const argsQuick = { 26 | speed: 'quick' 27 | }; 28 | 29 | const argsOther = { 30 | speed: 'slow' 31 | }; 32 | 33 | const argsEmpty = {}; 34 | 35 | const simpleMessage = { 36 | id: 'test.simpleMessage', 37 | default: 'The quick brown fox jumped over the lazy dog.' 38 | }; 39 | 40 | const complexMessage = { 41 | id: 'test.complexMessage', 42 | default: '{speed, select, quick {The quick brown fox jumped over the lazy dog.} other {Too slow, Gobo!}}' 43 | }; 44 | 45 | const quickExpectedResult = 'The quick brown fox jumped over the lazy dog.'; 46 | const otherExpectedResult = 'Too slow, Gobo!'; 47 | 48 | test('preserve non-messages', t => { 49 | t.plan(nonMessages.length); 50 | 51 | for (const x of nonMessages) { 52 | const result = maybeFormatMessage(x); 53 | t.strictSame(x, result); 54 | } 55 | 56 | t.end(); 57 | }); 58 | 59 | test('format messages', t => { 60 | const quickResult1 = maybeFormatMessage(simpleMessage); 61 | t.strictNotSame(quickResult1, simpleMessage); 62 | t.same(quickResult1, quickExpectedResult); 63 | 64 | const quickResult2 = maybeFormatMessage(complexMessage, argsQuick); 65 | t.strictNotSame(quickResult2, complexMessage); 66 | t.same(quickResult2, quickExpectedResult); 67 | 68 | const otherResult1 = maybeFormatMessage(complexMessage, argsOther); 69 | t.strictNotSame(otherResult1, complexMessage); 70 | t.same(otherResult1, otherExpectedResult); 71 | 72 | const otherResult2 = maybeFormatMessage(complexMessage, argsEmpty); 73 | t.strictNotSame(otherResult2, complexMessage); 74 | t.same(otherResult2, otherExpectedResult); 75 | 76 | t.end(); 77 | }); 78 | -------------------------------------------------------------------------------- /test/unit/mock-timer.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const MockTimer = require('../fixtures/mock-timer'); 3 | 4 | test('spec', t => { 5 | const timer = new MockTimer(); 6 | 7 | t.type(MockTimer, 'function'); 8 | t.type(timer, 'object'); 9 | 10 | // Most members of MockTimer mimic members of Timer. 11 | t.type(timer.startTime, 'number'); 12 | t.type(timer.time, 'function'); 13 | t.type(timer.start, 'function'); 14 | t.type(timer.timeElapsed, 'function'); 15 | t.type(timer.setTimeout, 'function'); 16 | t.type(timer.clearTimeout, 'function'); 17 | 18 | // A few members of MockTimer have no Timer equivalent and should only be used in tests. 19 | t.type(timer.advanceMockTime, 'function'); 20 | t.type(timer.advanceMockTimeAsync, 'function'); 21 | t.type(timer.hasTimeouts, 'function'); 22 | 23 | t.end(); 24 | }); 25 | 26 | test('time', t => { 27 | const timer = new MockTimer(); 28 | const delta = 1; 29 | 30 | const time1 = timer.time(); 31 | const time2 = timer.time(); 32 | timer.advanceMockTime(delta); 33 | const time3 = timer.time(); 34 | 35 | t.equal(time1, time2); 36 | t.equal(time2 + delta, time3); 37 | t.end(); 38 | }); 39 | 40 | test('start / timeElapsed', t => new Promise(resolve => { 41 | const timer = new MockTimer(); 42 | const halfDelay = 1; 43 | const fullDelay = halfDelay + halfDelay; 44 | 45 | timer.start(); 46 | 47 | let timeoutCalled = 0; 48 | 49 | // Wait and measure timer 50 | timer.setTimeout(() => { 51 | t.equal(timeoutCalled, 0); 52 | ++timeoutCalled; 53 | 54 | const timeElapsed = timer.timeElapsed(); 55 | t.equal(timeElapsed, fullDelay); 56 | t.end(); 57 | 58 | resolve(); 59 | }, fullDelay); 60 | 61 | // this should not trigger the callback 62 | timer.advanceMockTime(halfDelay); 63 | 64 | // give the mock timer a chance to run tasks 65 | global.setTimeout(() => { 66 | // we've only mock-waited for half the delay so it should not have run yet 67 | t.equal(timeoutCalled, 0); 68 | 69 | // this should trigger the callback 70 | timer.advanceMockTime(halfDelay); 71 | }, 0); 72 | })); 73 | 74 | test('clearTimeout / hasTimeouts', t => new Promise((resolve, reject) => { 75 | const timer = new MockTimer(); 76 | 77 | const timeoutId = timer.setTimeout(() => { 78 | reject(new Error('Canceled task ran')); 79 | }, 1); 80 | 81 | timer.setTimeout(() => { 82 | resolve('Non-canceled task ran'); 83 | t.end(); 84 | }, 2); 85 | 86 | timer.clearTimeout(timeoutId); 87 | 88 | while (timer.hasTimeouts()) { 89 | timer.advanceMockTime(1); 90 | } 91 | })); 92 | -------------------------------------------------------------------------------- /test/unit/project_load_changed_state.js: -------------------------------------------------------------------------------- 1 | const tap = require('tap'); 2 | const path = require('path'); 3 | const readFileToBuffer = require('../fixtures/readProjectFile').readFileToBuffer; 4 | const makeTestStorage = require('../fixtures/make-test-storage'); 5 | const VirtualMachine = require('../../src/virtual-machine'); 6 | 7 | const test = tap.test; 8 | 9 | // Test that loading a project does not emit a project change 10 | // This is in its own file so that it does not affect the test setup 11 | // and results of the other project changed state tests 12 | test('Loading a project should not emit a project changed event', t => { 13 | const projectUri = path.resolve(__dirname, '../fixtures/default.sb2'); 14 | const project = readFileToBuffer(projectUri); 15 | 16 | const vm = new VirtualMachine(); 17 | 18 | let projectChanged = false; 19 | vm.runtime.addListener('PROJECT_CHANGED', () => { 20 | projectChanged = true; 21 | }); 22 | 23 | vm.attachStorage(makeTestStorage()); 24 | return vm.loadProject(project).then(() => { 25 | t.equal(projectChanged, false); 26 | t.end(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/unit/spec.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const VirtualMachine = require('../../src/index'); 3 | 4 | test('interface', t => { 5 | const vm = new VirtualMachine(); 6 | t.type(vm, 'object'); 7 | t.type(vm.start, 'function'); 8 | t.type(vm.greenFlag, 'function'); 9 | t.type(vm.setTurboMode, 'function'); 10 | t.type(vm.setCompatibilityMode, 'function'); 11 | t.type(vm.stopAll, 'function'); 12 | t.type(vm.clear, 'function'); 13 | 14 | t.type(vm.getPlaygroundData, 'function'); 15 | t.type(vm.postIOData, 'function'); 16 | 17 | t.type(vm.loadProject, 'function'); 18 | t.type(vm.addSprite, 'function'); 19 | t.type(vm.addCostume, 'function'); 20 | t.type(vm.addBackdrop, 'function'); 21 | t.type(vm.addSound, 'function'); 22 | t.type(vm.deleteCostume, 'function'); 23 | t.type(vm.deleteSound, 'function'); 24 | t.type(vm.renameSprite, 'function'); 25 | t.type(vm.deleteSprite, 'function'); 26 | 27 | t.type(vm.attachRenderer, 'function'); 28 | t.type(vm.blockListener, 'function'); 29 | t.type(vm.flyoutBlockListener, 'function'); 30 | t.type(vm.setEditingTarget, 'function'); 31 | 32 | t.type(vm.emitTargetsUpdate, 'function'); 33 | t.type(vm.emitWorkspaceUpdate, 'function'); 34 | t.type(vm.postSpriteInfo, 'function'); 35 | t.end(); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/util_base64.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | // const Base64Util = require('../../src/util/base64-util'); 3 | 4 | test('base64ToUint8Array', t => { 5 | t.end(); 6 | }); 7 | 8 | test('uint8ArrayToBase64', t => { 9 | t.end(); 10 | }); 11 | -------------------------------------------------------------------------------- /test/unit/util_jsonrpc-web-socket.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | // const JSONRPCWebSocket = require('../../src/util/jsonrpc-web-socket'); 3 | 4 | test('constructor', t => { 5 | t.end(); 6 | }); 7 | 8 | test('dispose', t => { 9 | t.end(); 10 | }); 11 | -------------------------------------------------------------------------------- /test/unit/util_jsonrpc.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | // const JSONRPC = require('../../src/util/jsonrpc'); 3 | 4 | test('constructor', t => { 5 | t.end(); 6 | }); 7 | 8 | test('sendRemoteRequest', t => { 9 | t.end(); 10 | }); 11 | 12 | test('sendRemoteNotification', t => { 13 | t.end(); 14 | }); 15 | 16 | test('didReceiveCall', t => { 17 | t.end(); 18 | }); 19 | -------------------------------------------------------------------------------- /test/unit/util_math.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const math = require('../../src/util/math-util'); 3 | 4 | test('degToRad', t => { 5 | t.strictEqual(math.degToRad(0), 0); 6 | t.strictEqual(math.degToRad(1), 0.017453292519943295); 7 | t.strictEqual(math.degToRad(180), Math.PI); 8 | t.strictEqual(math.degToRad(360), 2 * Math.PI); 9 | t.strictEqual(math.degToRad(720), 4 * Math.PI); 10 | t.end(); 11 | }); 12 | 13 | test('radToDeg', t => { 14 | t.strictEqual(math.radToDeg(0), 0); 15 | t.strictEqual(math.radToDeg(1), 57.29577951308232); 16 | t.strictEqual(math.radToDeg(180), 10313.240312354817); 17 | t.strictEqual(math.radToDeg(360), 20626.480624709635); 18 | t.strictEqual(math.radToDeg(720), 41252.96124941927); 19 | t.end(); 20 | }); 21 | 22 | test('clamp', t => { 23 | t.strictEqual(math.clamp(0, 0, 10), 0); 24 | t.strictEqual(math.clamp(1, 0, 10), 1); 25 | t.strictEqual(math.clamp(-10, 0, 10), 0); 26 | t.strictEqual(math.clamp(100, 0, 10), 10); 27 | t.end(); 28 | }); 29 | 30 | test('wrapClamp', t => { 31 | t.strictEqual(math.wrapClamp(0, 0, 10), 0); 32 | t.strictEqual(math.wrapClamp(1, 0, 10), 1); 33 | t.strictEqual(math.wrapClamp(-10, 0, 10), 1); 34 | t.strictEqual(math.wrapClamp(100, 0, 10), 1); 35 | t.end(); 36 | }); 37 | 38 | test('tan', t => { 39 | t.strictEqual(math.tan(90), Infinity); 40 | t.strictEqual(math.tan(180), 0); 41 | t.strictEqual(math.tan(-90), -Infinity); 42 | t.strictEqual(math.tan(33), 0.6494075932); 43 | t.end(); 44 | }); 45 | 46 | test('reducedSortOrdering', t => { 47 | t.deepEqual(math.reducedSortOrdering([5, 18, 6, 3]), [1, 3, 2, 0]); 48 | t.deepEqual(math.reducedSortOrdering([5, 1, 56, 19]), [1, 0, 3, 2]); 49 | t.end(); 50 | }); 51 | 52 | test('inclusiveRandIntWithout', t => { 53 | const withRandomValue = function (randValue, ...args) { 54 | const oldMathRandom = Math.random; 55 | Object.assign(global.Math, {random: () => randValue}); 56 | const result = math.inclusiveRandIntWithout(...args); 57 | Object.assign(global.Math, {random: oldMathRandom}); 58 | return result; 59 | }; 60 | 61 | t.strictEqual(withRandomValue(3 / 6, 0, 6, 2), 4); 62 | t.strictEqual(withRandomValue(2 / 6, 0, 6, 2), 3); 63 | t.strictEqual(withRandomValue(1 / 6, 0, 6, 2), 1); 64 | t.strictEqual(withRandomValue(1.9 / 6, 0, 6, 2), 1); 65 | 66 | t.strictEqual(withRandomValue(3 / 4, 10, 14, 10), 14); 67 | t.strictEqual(withRandomValue(0 / 4, 10, 14, 10), 11); 68 | 69 | t.end(); 70 | }); 71 | -------------------------------------------------------------------------------- /test/unit/util_new-block-ids.js: -------------------------------------------------------------------------------- 1 | const newBlockIds = require('../../src/util/new-block-ids'); 2 | const simpleStack = require('../fixtures/simple-stack'); 3 | const tap = require('tap'); 4 | const test = tap.test; 5 | 6 | let originals; 7 | let newBlocks; 8 | 9 | tap.beforeEach(() => { 10 | originals = simpleStack; 11 | // Will be mutated so make a copy first 12 | newBlocks = JSON.parse(JSON.stringify(simpleStack)); 13 | newBlockIds(newBlocks); 14 | }); 15 | 16 | 17 | /** 18 | * The structure of the simple stack is: 19 | * moveTo (looks_size) -> stopAllSounds 20 | * The list of blocks is 21 | * 0: moveTo (TO input block: 1, shadow: 2) 22 | * 1: looks_size (parent: 0) 23 | * 2: obscured shadow for moveTo input (parent: 0) 24 | * 3: stopAllSounds (parent: 0) 25 | * Inspect fixtures/simple-stack for the full object. 26 | */ 27 | 28 | test('top-level block IDs have all changed', t => { 29 | newBlocks.forEach((block, i) => { 30 | t.notEqual(block.id, originals[i].id); 31 | }); 32 | t.end(); 33 | }); 34 | 35 | test('input reference is maintained on parent for attached block', t => { 36 | t.equal(newBlocks[0].inputs.TO.block, newBlocks[1].id); 37 | t.end(); 38 | }); 39 | 40 | test('input reference is maintained on parent for obscured shadow', t => { 41 | t.equal(newBlocks[0].inputs.TO.shadow, newBlocks[2].id); 42 | t.end(); 43 | }); 44 | 45 | test('parent reference is maintained for attached input', t => { 46 | t.equal(newBlocks[1].parent, newBlocks[0].id); 47 | t.end(); 48 | }); 49 | 50 | test('parent reference is maintained for obscured shadow', t => { 51 | t.equal(newBlocks[2].parent, newBlocks[0].id); 52 | t.end(); 53 | }); 54 | 55 | test('parent reference is maintained for next block', t => { 56 | t.equal(newBlocks[3].parent, newBlocks[0].id); 57 | t.end(); 58 | }); 59 | 60 | test('next reference is maintained for previous block', t => { 61 | t.equal(newBlocks[0].next, newBlocks[3].id); 62 | t.end(); 63 | }); 64 | -------------------------------------------------------------------------------- /test/unit/util_rateLimiter.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const RateLimiter = require('../../src/util/rateLimiter.js'); 3 | 4 | test('rate limiter', t => { 5 | // Create a rate limiter with maximum of 20 sends per second 6 | const rate = 20; 7 | const limiter = new RateLimiter(rate); 8 | 9 | // Simulate time passing with a stubbed timer 10 | let simulatedTime = Date.now(); 11 | limiter._timer = {timeElapsed: () => simulatedTime}; 12 | 13 | // The rate limiter starts with a number of tokens equal to the max rate 14 | t.equal(limiter._count, rate); 15 | 16 | // Running okayToSend a number of times equal to the max rate 17 | // uses up all of the tokens 18 | for (let i = 0; i < rate; i++) { 19 | t.true(limiter.okayToSend()); 20 | // Tokens are counting down 21 | t.equal(limiter._count, rate - (i + 1)); 22 | } 23 | t.false(limiter.okayToSend()); 24 | 25 | // Advance the timer enough so we get exactly one more token 26 | // One extra millisecond is required to get over the threshold 27 | simulatedTime += (1000 / rate) + 1; 28 | t.true(limiter.okayToSend()); 29 | t.false(limiter.okayToSend()); 30 | 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/util_timer.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const Timer = require('../../src/util/timer'); 3 | 4 | // Stubbed current time 5 | let NOW = 0; 6 | 7 | const testNow = { 8 | now: () => { 9 | NOW += 100; 10 | return NOW; 11 | } 12 | }; 13 | 14 | test('spec', t => { 15 | const timer = new Timer(testNow); 16 | 17 | t.type(Timer, 'function'); 18 | t.type(timer, 'object'); 19 | 20 | t.type(timer.startTime, 'number'); 21 | t.type(timer.time, 'function'); 22 | t.type(timer.start, 'function'); 23 | t.type(timer.timeElapsed, 'function'); 24 | t.type(timer.setTimeout, 'function'); 25 | t.type(timer.clearTimeout, 'function'); 26 | 27 | t.end(); 28 | }); 29 | 30 | test('time', t => { 31 | const timer = new Timer(testNow); 32 | const time = timer.time(); 33 | 34 | t.ok(testNow.now() >= time); 35 | t.end(); 36 | }); 37 | 38 | test('start / timeElapsed', t => { 39 | const timer = new Timer(testNow); 40 | const delay = 100; 41 | const threshold = 1000 / 60; // 60 hz 42 | 43 | // Start timer 44 | timer.start(); 45 | 46 | // Measure timer 47 | const timeElapsed = timer.timeElapsed(); 48 | t.ok(timeElapsed >= 0); 49 | t.ok(timeElapsed >= (delay - threshold) && 50 | timeElapsed <= (delay + threshold)); 51 | t.end(); 52 | }); 53 | 54 | test('setTimeout / clearTimeout', t => new Promise((resolve, reject) => { 55 | const timer = new Timer(testNow); 56 | const cancelId = timer.setTimeout(() => { 57 | reject(new Error('Canceled task ran')); 58 | }, 1); 59 | timer.setTimeout(() => { 60 | resolve('Non-canceled task ran'); 61 | t.end(); 62 | }, 2); 63 | timer.clearTimeout(cancelId); 64 | })); 65 | -------------------------------------------------------------------------------- /test/unit/util_variable.js: -------------------------------------------------------------------------------- 1 | const tap = require('tap'); 2 | const Target = require('../../src/engine/target'); 3 | const Runtime = require('../../src/engine/runtime'); 4 | const VariableUtil = require('../../src/util/variable-util'); 5 | 6 | let target1; 7 | let target2; 8 | 9 | tap.beforeEach(() => { 10 | const runtime = new Runtime(); 11 | target1 = new Target(runtime); 12 | target1.blocks.createBlock({ 13 | id: 'a block', 14 | fields: { 15 | VARIABLE: { 16 | id: 'id1', 17 | value: 'foo' 18 | } 19 | } 20 | }); 21 | target1.blocks.createBlock({ 22 | id: 'another block', 23 | fields: { 24 | TEXT: { 25 | value: 'not a variable' 26 | } 27 | } 28 | }); 29 | 30 | target2 = new Target(runtime); 31 | target2.blocks.createBlock({ 32 | id: 'a different block', 33 | fields: { 34 | VARIABLE: { 35 | id: 'id2', 36 | value: 'bar' 37 | } 38 | } 39 | }); 40 | target2.blocks.createBlock({ 41 | id: 'another var block', 42 | fields: { 43 | VARIABLE: { 44 | id: 'id1', 45 | value: 'foo' 46 | } 47 | } 48 | }); 49 | 50 | return Promise.resolve(null); 51 | }); 52 | 53 | const test = tap.test; 54 | 55 | test('get all var refs', t => { 56 | const allVarRefs = VariableUtil.getAllVarRefsForTargets([target1, target2]); 57 | t.equal(Object.keys(allVarRefs).length, 2); 58 | t.equal(allVarRefs.id1.length, 2); 59 | t.equal(allVarRefs.id2.length, 1); 60 | t.equal(allVarRefs['not a variable'], undefined); 61 | 62 | t.end(); 63 | }); 64 | 65 | test('merge variable ids', t => { 66 | // Redo the id for the variable with 'id1' 67 | VariableUtil.updateVariableIdentifiers(target1.blocks.getAllVariableAndListReferences().id1, 'renamed id'); 68 | const varField = target1.blocks.getBlock('a block').fields.VARIABLE; 69 | t.equals(varField.id, 'renamed id'); 70 | t.equals(varField.value, 'foo'); 71 | 72 | t.end(); 73 | }); 74 | 75 | test('merge variable ids but with new name too', t => { 76 | // Redo the id for the variable with 'id1' 77 | VariableUtil.updateVariableIdentifiers(target1.blocks.getAllVariableAndListReferences().id1, 'renamed id', 'baz'); 78 | const varField = target1.blocks.getBlock('a block').fields.VARIABLE; 79 | t.equals(varField.id, 'renamed id'); 80 | t.equals(varField.value, 'baz'); 81 | 82 | t.end(); 83 | }); 84 | -------------------------------------------------------------------------------- /test/unit/util_xml.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | const xml = require('../../src/util/xml-escape'); 3 | 4 | test('escape', t => { 5 | const input = ''; 6 | const output = '<foo bar="he & llo '"></foo>'; 7 | t.strictEqual(xml(input), output); 8 | t.end(); 9 | }); 10 | 11 | test('xmlEscape (more)', t => { 12 | const empty = ''; 13 | t.equal(xml(empty), empty); 14 | 15 | const safe = 'hello'; 16 | t.equal(xml(safe), safe); 17 | 18 | const unsafe = '< > & \' "'; 19 | t.equal(xml(unsafe), '< > & ' "'); 20 | 21 | const single = '&'; 22 | t.equal(xml(single), '&'); 23 | 24 | const mix = 'b& c\'def_-"'; 25 | t.equal(xml(mix), '<a>b& c'def_-"'); 26 | 27 | const dupes = '<<&_"_"_&>>'; 28 | t.equal(xml(dupes), '<<&_"_"_&>>'); 29 | 30 | const emoji = '(>^_^)>'; 31 | t.equal(xml(emoji), '(>^_^)>'); 32 | 33 | t.end(); 34 | }); 35 | 36 | test('xmlEscape should handle non strings', t => { 37 | const array = ['hello', 'world']; 38 | t.equal(xml(array), String(array)); 39 | 40 | const arrayWithSpecialChar = ['hello', '']; 41 | t.equal(xml(arrayWithSpecialChar), 'hello,<world>'); 42 | 43 | const arrayWithNumbers = [1, 2, 3]; 44 | t.equal(xml(arrayWithNumbers), '1,2,3'); 45 | 46 | // Objects shouldn't get provided to replaceUnsafeChars, but in the event 47 | // they do, it should just return the object (and log an error) 48 | const object = {hello: 'world'}; 49 | t.equal(xml(object), object); 50 | 51 | t.end(); 52 | }); 53 | -------------------------------------------------------------------------------- /test/unit/vm_collectAssets.js: -------------------------------------------------------------------------------- 1 | const test = require('tap').test; 2 | 3 | const RenderedTarget = require('../../src/sprites/rendered-target'); 4 | const Sprite = require('../../src/sprites/sprite'); 5 | const VirtualMachine = require('../../src/virtual-machine'); 6 | 7 | test('collectAssets', t => { 8 | const vm = new VirtualMachine(); 9 | const sprite = new Sprite(null, vm.runtime); 10 | const target = new RenderedTarget(sprite, vm.runtime); 11 | vm.runtime.targets = [target]; 12 | const [ 13 | soundAsset1, 14 | soundAsset2, 15 | costumeAsset1 16 | ] = [{assetId: 1}, {assetId: 2}, {assetId: 3}]; 17 | sprite.sounds = [{id: 1, asset: soundAsset1}, {id: 2, asset: soundAsset2}]; 18 | sprite.costumes = [{id: 1, asset: costumeAsset1}]; 19 | const assets = vm.assets; 20 | t.type(assets.length, 'number'); 21 | t.equal(assets.length, 3); 22 | t.deepEqual(assets, [soundAsset1, soundAsset2, costumeAsset1]); 23 | t.end(); 24 | }); 25 | --------------------------------------------------------------------------------