From c81f7ee9a6852612799414ebdd7e511499f8630e Mon Sep 17 00:00:00 2001
From: jokebookservice1 <jokebookyeye@gmail.com>
Date: Wed, 22 Aug 2018 20:38:46 +0300
Subject: [PATCH] Create unit tests for SWITCH COSTUME and SWITCH BACKDROP

---
 test/unit/blocks_looks.js | 142 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 142 insertions(+)

diff --git a/test/unit/blocks_looks.js b/test/unit/blocks_looks.js
index a9602a706..74d4a6fe8 100644
--- a/test/unit/blocks_looks.js
+++ b/test/unit/blocks_looks.js
@@ -1,6 +1,8 @@
 const test = require('tap').test;
 const Looks = require('../../src/blocks/scratch3_looks');
 const Runtime = require('../../src/engine/runtime');
+const Sprite = require('../../src/sprites/sprite.js');
+const RenderedTarget = require('../../src/sprites/rendered-target.js');
 const util = {
     target: {
         currentCostume: 0, // Internally, current costume is 0 indexed
@@ -23,6 +25,146 @@ const fakeRuntime = {
 };
 const blocks = new Looks(fakeRuntime);
 
+test('switch costume block runs correctly', t => {
+    /**
+     * Test which costume index the `switch costume`
+     * block will jump to given an argument and array
+     * of costume names.
+     *
+     * @param {string[]} costumes List of costume names as strings
+     * @param {string|number|boolean} arg The argument to provide to the block.
+     * @param {number} [currentCostume=1] The 1-indexed default costume for the sprite to start at.
+     * @return {number} The 1-indexed costume index on which the sprite lands.
+     */
+    const testCostume = (costumes, arg, currentCostume = 1) => {
+        const rt = new Runtime();
+        const looks = new Looks(rt);
+
+        const sprite = new Sprite(null, rt);
+        const target = new RenderedTarget(sprite, rt);
+
+        sprite.costumes = costumes.map(name => ({name: name}));
+        target.currentCostume = currentCostume - 1; // Convert to 0-indexed.
+
+        looks.switchCostume({COSTUME: arg}, {target});
+
+        return target.currentCostume + 1; // Convert to 1-indexed.
+    };
+
+    // Non-existant costumes do nothing
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], 'e', 3), 3);
+
+    // Difference between string and numeric arguments
+    t.strictEqual(testCostume(['a', 'b', 'c', '2'], 2), 2);
+    t.strictEqual(testCostume(['a', 'b', 'c', '2'], '2'), 4);
+
+    // 'previous costume' and 'next costume' increment/decrement
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], 'previous costume', 3), 2);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], 'next costume', 2), 3);
+
+    // 'previous costume' and 'next costume' can be overriden
+    t.strictEqual(testCostume(['a', 'previous costume', 'c', 'd'], 'previous costume'), 2);
+    t.strictEqual(testCostume(['next costume', 'b', 'c', 'd'], 'next costume'), 1);
+
+    // NaN, Infinity, and true are the first costume
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], NaN, 2), 1);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], true, 2), 1);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], Infinity, 2), 1);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], -Infinity, 2), 1);
+
+    // 'previous backdrop' and 'next backdrop' have no effect
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], 'previous backdrop', 3), 3);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], 'next backdrop', 3), 3);
+
+    // Strings with no digits are not numeric
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], '    ', 2), 2);
+
+    // False is 0 (the last costume)
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], false), 4);
+
+    // Booleans are costume names where possible.
+    t.strictEqual(testCostume(['a', 'true', 'false', 'd'], false), 3);
+    t.strictEqual(testCostume(['a', 'true', 'false', 'd'], true), 2);
+
+    // Costume indices should wrap around.
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], -1), 3);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], -4), 4);
+    t.strictEqual(testCostume(['a', 'b', 'c', 'd'], 10), 2);
+
+    t.end();
+});
+
+test('switch backdrop block runs correctly', t => {
+    /**
+     * Test which backdrop index the `switch backdrop`
+     * block will jump to given an argument and array
+     * of backdrop names.
+     *
+     * @param {string[]} backdrops List of backdrop names as strings
+     * @param {string|number|boolean} arg The argument to provide to the block.
+     * @param {number} [currentCostume=1] The 1-indexed default backdrop for the stage to start at.
+     * @return {number} The 1-indexed backdrop index on which the stage lands.
+     */
+    const testBackdrop = (backdrops, arg, currentCostume = 1) => {
+        const rt = new Runtime();
+        const looks = new Looks(rt);
+
+        const stage = new Sprite(null, rt);
+        const target = new RenderedTarget(stage, rt);
+
+        stage.costumes = backdrops.map(name => ({name: name}));
+        target.currentCostume = currentCostume - 1; // Convert to 0-indexed.
+        target.isStage = true;
+        rt.targets.push(target);
+
+        looks.switchBackdrop({BACKDROP: arg}, {target});
+
+        return target.currentCostume + 1; // Convert to 1-indexed.
+    };
+
+    // Non-existant backdrops do nothing
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], 'e', 3), 3);
+
+    // Difference between string and numeric arguments
+    t.strictEqual(testBackdrop(['a', 'b', 'c', '2'], 2), 2);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', '2'], '2'), 4);
+
+    // 'previous backdrop' and 'next backdrop' increment/decrement
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], 'previous backdrop', 3), 2);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], 'next backdrop', 2), 3);
+
+    // 'previous backdrop' and 'next backdrop' can't be overriden
+    t.strictEqual(testBackdrop(['a', 'previous backdrop', 'c', 'd'], 'previous backdrop', 4), 3);
+    t.strictEqual(testBackdrop(['next backdrop', 'b', 'c', 'd'], 'next backdrop'), 2);
+
+    // NaN, Infinity, and true are the first costume
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], NaN, 2), 1);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], true, 2), 1);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], Infinity, 2), 1);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], -Infinity, 2), 1);
+
+    // 'previous costume' and 'next costume' have no effect
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], 'previous costume', 3), 3);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], 'next costume', 3), 3);
+
+    // Strings with no digits are not numeric
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], '    ', 2), 2);
+
+    // False is 0 (the last costume)
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], false), 4);
+
+    // Booleans are backdrop names where possible.
+    t.strictEqual(testBackdrop(['a', 'true', 'false', 'd'], false), 3);
+    t.strictEqual(testBackdrop(['a', 'true', 'false', 'd'], true), 2);
+
+    // Backdrop indices should wrap around.
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], -1), 3);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], -4), 4);
+    t.strictEqual(testBackdrop(['a', 'b', 'c', 'd'], 10), 2);
+    
+    t.end();
+});
+
 test('getCostumeNumberName returns 1-indexed costume number', t => {
     util.target.currentCostume = 0; // This is 0-indexed.
     const args = {NUMBER_NAME: 'number'};