diff --git a/docs/tutorial/progress.md b/docs/tutorial/progress.md
index 953866d7e0f..c0a4984f257 100644
--- a/docs/tutorial/progress.md
+++ b/docs/tutorial/progress.md
@@ -11,7 +11,7 @@
| Phase 1 — Mesh 再分類 | [#678](https://github.com/smalruby/smalruby3-editor/issues/678) | ✅ マージ済み (PR #683) | 1 PR | 不要 |
| 基盤 — `setup` プロパティ | (Phase 2 sub-issue 内) | ✅ マージ済み (PR #684) | 1 PR | 不要 |
| Phase 2 — Ruby 拡充 | [#679](https://github.com/smalruby/smalruby3-editor/issues/679) | 🟢 7/7 deck 実装済み (`ruby-basics-1-numbers` 〜 `-7-next`。deck 7 は TryRuby 導線) | 2〜3 PR | ~50 枚 |
-| Phase 3 — Block 4 シリーズ | [#680](https://github.com/smalruby/smalruby3-editor/issues/680) | ⚪️ 未着手 (書誌情報待ち) | 4 PR | ~76 枚 |
+| Phase 3 — Block シリーズ (2 章) | [#680](https://github.com/smalruby/smalruby3-editor/issues/680) | 🟡 第1章 `blockBasics` 4 deck 実装済み (#957)。第4章 `blockGames` は別 PR。書誌・引用は #962 で確定 | 2 PR | ~40 枚 |
| Phase 4 — DNCL | [#681](https://github.com/smalruby/smalruby3-editor/issues/681) | ⚪️ 未着手 | 3〜4 PR | ~70 枚 |
凡例: ⚪️ 未着手 / 🟡 進行中 / 🟢 完了 (PR レビュー待ち含む) / ✅ マージ済み / ❌ 中断
@@ -60,10 +60,20 @@
- [x] ふりがなフラグも考慮した rubyMode の動作 (`smalruby:furiganaEnabled` localStorage の同期)
- [x] `test/unit/lib/deck-setup.test.js` で 13 ケースの単体テスト (全 pass)
-### Phase 3 着手前に必要 (外部要因)
+### Phase 3 着手前に必要 (外部要因) — 解決済み
-- [ ] 書籍「キラキラRuby」(仮称) の正式タイトル・出版社・ISBN・購入リンク確定
-- [ ] 著者 (藤村健吾氏) からの書籍引用許諾
+- [x] 書誌情報・購入リンク確定 → `book-promo.jsx` の `BOOK` 定数 (プレースホルダ) + `docs/tutorial/book-kirakira-ruby-source.md` (#962) に集約
+- [x] 書籍引用許諾 (共著者 takaokouji 本人が引用の範囲での利用に同意・#962)
+
+### Phase 3 第1章 `blockBasics` (#957)
+
+「ネコからにげるゲーム冒頭」を基本 3 deck + 発展 1 deck で実装:
+
+- [x] `block-basics-lv0` (コード挿入体験) / `block-basics-lv2` (ブロック手組み) / `block-basics-lv3` (Ruby・`setup: {tab:'ruby', rubyMode:'ruby'}`) / `block-basics-advanced` (Lv0 のみ・完成デモ)
+- [x] 各 deck 冒頭に書誌 step、末尾に「続きは書籍で」+ 購入リンク step (`book-promo.jsx` の共通ファクトリを利用)
+- [x] locale 3 ファイル (`block-series.locale.js` の `ja`/`jaHira`/`en`)
+- [x] step 画像 20 枚 (**プレースホルダ** — 本番スクリーンショットは headful キャプチャ待ち) + サムネイル 4 枚
+- [x] `npm run lint` 緑 / `build:dev` 成功 / pw-check で 4 deck 起動確認
## 削除タイミング
diff --git a/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.jsx b/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.jsx
index cd9c97dd676..e60fbcce443 100644
--- a/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.jsx
+++ b/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.jsx
@@ -1,8 +1,342 @@
// Deck definitions for the blockBasics/blockGames tutorial categories
-// (Phase 3, issue #680). Stub — populated by #680 so
-// that category can be developed without touching index.jsx or the
-// other tutorial categories (issue #932).
+// (Phase 3, issue #680). This module owns the Block-axis book-excerpt decks
+// so they can be developed without touching index.jsx or the other tutorial
+// categories (issue #932).
+//
+// blockBasics 第1章「ネコからにげるゲーム」 (issue #957). The chapter has a
+// basic 3-deck ladder (Lv0 code-insert / Lv2 build-blocks / Lv3 Ruby) plus a
+// single "発展" demo deck (Lv0 only). Every deck opens with the book's
+// bibliographic info and closes with a purchase link; those shared steps come
+// from book-promo.jsx (issue #956). Source snippets:
+// docs/tutorial/book-kirakira-ruby-source.md 第1章.
+import React from 'react';
+import {FormattedMessage} from 'react-intl';
-const decks = {};
+import libraryBlockBasicsLv0 from '../thumbnails/block-basics-lv0.png';
+import libraryBlockBasicsLv2 from '../thumbnails/block-basics-lv2.png';
+import libraryBlockBasicsLv3 from '../thumbnails/block-basics-lv3.png';
+import libraryBlockBasicsAdvanced from '../thumbnails/block-basics-advanced.png';
+import {CATEGORIES} from '../../tutorial-tags';
+import GreenFlagIcon from '../green-flag-icon.jsx';
+import {bookOpeningStep, bookClosingStep, bookAdvancedClosingStep} from './book-promo.jsx';
+
+// The book chapter this series excerpts from (used by the book-promo steps).
+const CHAPTER = 1;
+
+// The completed "ネコからにげるゲーム" program. Lv0 / Lv3 / advanced all insert
+// this same program; Lv2 has the user assemble the equivalent blocks by hand.
+// (docs/tutorial/book-kirakira-ruby-source.md 第1章 の完成プログラム)
+const FULL_PROGRAM = `when_flag_clicked do
+ loop do
+ point_towards("_mouse_")
+ move(10)
+ if touching?("_mouse_")
+ say("こんにちは!", 2)
+ end
+ end
+end`;
+
+// Blocks surfaced in the palette while these decks are active. Kept to the
+// blocks the ネコからにげるゲーム program uses so the palette stays focused.
+const allowedBlocks = {
+ motion: ['motion_movesteps', 'motion_pointtowards'],
+ looks: ['looks_sayforsecs', 'looks_say'],
+ sound: [],
+ event: ['event_whenflagclicked'],
+ control: ['control_forever', 'control_if'],
+ sensing: ['sensing_touchingobject'],
+ operators: [],
+};
+
+const decks = {
+ // ─── Lv0: 完成プログラムを挿入して動かす(コード挿入体験) ───────────────
+ 'block-basics-lv0': {
+ name: (
+
+ ),
+ tags: [],
+ category: CATEGORIES.blockBasics,
+ img: libraryBlockBasicsLv0,
+ nameMessageId: 'gui.howtos.block-basics-lv0.name',
+ allowedBlocks,
+ steps: [
+ bookOpeningStep(CHAPTER, 'blockBasicsBookOpening'),
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv0Intro',
+ startTutorial: true,
+ animationTarget: 'startTutorialButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv0Insert',
+ code: FULL_PROGRAM,
+ codeType: 'blocks',
+ animationTarget: 'insertCodeButton',
+ },
+ {
+ title: (
+ }}
+ />
+ ),
+ image: 'blockBasicsLv0Run',
+ animationTarget: 'nextButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv0Modify',
+ animationTarget: 'nextButton',
+ },
+ bookClosingStep(CHAPTER, 'blockBasicsBookClosing'),
+ ],
+ urlId: 'blockBasicsLv0',
+ },
+
+ // ─── Lv2: ブロックを自分で組み立てる ────────────────────────────────────
+ 'block-basics-lv2': {
+ name: (
+
+ ),
+ tags: [],
+ category: CATEGORIES.blockBasics,
+ img: libraryBlockBasicsLv2,
+ nameMessageId: 'gui.howtos.block-basics-lv2.name',
+ allowedBlocks,
+ steps: [
+ bookOpeningStep(CHAPTER, 'blockBasicsBookOpening'),
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv2Intro',
+ startTutorial: true,
+ animationTarget: 'startTutorialButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv2Block1',
+ animationTarget: 'nextButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv2Block2',
+ animationTarget: 'nextButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv2Block3',
+ animationTarget: 'nextButton',
+ },
+ {
+ title: (
+ }}
+ />
+ ),
+ image: 'blockBasicsLv2Run',
+ animationTarget: 'nextButton',
+ },
+ bookClosingStep(CHAPTER, 'blockBasicsBookClosing'),
+ ],
+ urlId: 'blockBasicsLv2',
+ },
+
+ // ─── Lv3: 同じゲームを Ruby で書く ──────────────────────────────────────
+ 'block-basics-lv3': {
+ name: (
+
+ ),
+ tags: ['ruby'],
+ category: CATEGORIES.blockBasics,
+ img: libraryBlockBasicsLv3,
+ nameMessageId: 'gui.howtos.block-basics-lv3.name',
+ // Open the Ruby tab in Ruby (not DNCL/furigana) mode.
+ setup: {
+ tab: 'ruby',
+ rubyMode: 'ruby',
+ },
+ allowedBlocks,
+ steps: [
+ bookOpeningStep(CHAPTER, 'blockBasicsBookOpening'),
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv3Intro',
+ startTutorial: true,
+ animationTarget: 'startTutorialButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv3RubyTab',
+ animationTarget: 'nextButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv3Insert',
+ code: FULL_PROGRAM,
+ animationTarget: 'insertCodeButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsLv3Modify',
+ animationTarget: 'nextButton',
+ },
+ {
+ title: (
+ }}
+ />
+ ),
+ image: 'blockBasicsLv3Run',
+ animationTarget: 'nextButton',
+ },
+ bookClosingStep(CHAPTER, 'blockBasicsBookClosing'),
+ ],
+ urlId: 'blockBasicsLv3',
+ },
+
+ // ─── 発展: 完成デモを動かして見せるだけ(Lv0 相当) ─────────────────────
+ 'block-basics-advanced': {
+ name: (
+
+ ),
+ tags: [],
+ category: CATEGORIES.blockBasics,
+ img: libraryBlockBasicsAdvanced,
+ nameMessageId: 'gui.howtos.block-basics-advanced.name',
+ allowedBlocks,
+ steps: [
+ bookOpeningStep(CHAPTER, 'blockBasicsBookOpening'),
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsAdvIntro',
+ startTutorial: true,
+ animationTarget: 'startTutorialButton',
+ },
+ {
+ title: (
+
+ ),
+ image: 'blockBasicsAdvInsert',
+ code: FULL_PROGRAM,
+ codeType: 'blocks',
+ animationTarget: 'insertCodeButton',
+ },
+ {
+ title: (
+ }}
+ />
+ ),
+ image: 'blockBasicsAdvRun',
+ animationTarget: 'nextButton',
+ },
+ bookAdvancedClosingStep(CHAPTER, 'blockBasicsBookAdvancedClosing'),
+ ],
+ urlId: 'blockBasicsAdvanced',
+ },
+};
export default decks;
diff --git a/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.locale.js b/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.locale.js
index b46326f9dfc..c4e81ca7642 100644
--- a/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.locale.js
+++ b/packages/scratch-gui/src/lib/libraries/decks/categories/block-series.locale.js
@@ -44,14 +44,124 @@ const bookPromoEn = {
'gui.howtos.book-promo.purchase': '📖 Buy the book',
};
+// Per-deck howto strings for blockBasics 第1章「ネコからにげるゲーム」(issue #957).
+const blockBasics = {
+ 'gui.howtos.block-basics-lv0.name': '【Lv0】ネコからにげるゲームを動かそう',
+ 'gui.howtos.block-basics-lv0.intro':
+ '【Lv0】ネコからにげるゲームを作るよ。ネコがマウスのポインターを追いかけてくるよ!',
+ 'gui.howtos.block-basics-lv0.insert': '「コードを挿入」ボタンを押して、完成プログラムを入れてみよう',
+ 'gui.howtos.block-basics-lv0.run':
+ '{greenFlag}を押すと、ネコがマウスポインターを追いかけるよ。マウスを動かしてにげよう!',
+ 'gui.howtos.block-basics-lv0.modify':
+ '「こんにちは!」の言葉や、「10歩動かす」の歩数を変えて、動きを変えてみよう',
+
+ 'gui.howtos.block-basics-lv2.name': '【Lv2】ブロックを組み立てよう',
+ 'gui.howtos.block-basics-lv2.intro': '【Lv2】同じゲームを、ブロックを自分で組み立てて作ってみよう',
+ 'gui.howtos.block-basics-lv2.block1':
+ '「🏁が押されたとき」の中に「ずっと」を入れて、「マウスのポインターへ向ける」を置こう',
+ 'gui.howtos.block-basics-lv2.block2': '「10歩動かす」を追加しよう。これでネコがマウスに向かって進むよ',
+ 'gui.howtos.block-basics-lv2.block3':
+ '「もし〈マウスのポインターに触れた〉なら」の中に「こんにちは!と2秒言う」を入れよう',
+ 'gui.howtos.block-basics-lv2.run': '{greenFlag}を押して、自分で組み立てたゲームを動かそう',
+
+ 'gui.howtos.block-basics-lv3.name': '【Lv3】Rubyで書こう',
+ 'gui.howtos.block-basics-lv3.intro': '【Lv3】同じゲームを、Rubyのプログラムで書いてみよう',
+ 'gui.howtos.block-basics-lv3.rubyTab': 'ルビー(Ruby)タブが開いていることを確認しよう',
+ 'gui.howtos.block-basics-lv3.insert': '「コードを挿入」ボタンを押して、Rubyのプログラムを入れてみよう',
+ 'gui.howtos.block-basics-lv3.modify': 'say("こんにちは!", 2) の言葉や秒数を変えて、自分だけのゲームにしよう',
+ 'gui.howtos.block-basics-lv3.run': '{greenFlag}を押すと、Rubyで書いたゲームが動くよ',
+
+ 'gui.howtos.block-basics-advanced.name': '【発展】完成デモを動かそう',
+ 'gui.howtos.block-basics-advanced.intro':
+ '【発展】本書 第1章では、このあとゲームをもっと楽しくしていきます。ここでは完成デモを動かして見てみよう',
+ 'gui.howtos.block-basics-advanced.insert': '「コードを挿入」ボタンで、完成した「ネコからにげるゲーム」を入れよう',
+ 'gui.howtos.block-basics-advanced.run':
+ '{greenFlag}を押して、完成デモを動かそう。マウスを動かしてネコからにげよう!',
+};
+
+const blockBasicsHira = {
+ 'gui.howtos.block-basics-lv0.name': '【Lv0】ネコからにげるゲームをうごかそう',
+ 'gui.howtos.block-basics-lv0.intro':
+ '【Lv0】ネコからにげるゲームをつくるよ。ネコがマウスのポインターをおいかけてくるよ!',
+ 'gui.howtos.block-basics-lv0.insert': '「コードをそうにゅう」ボタンをおして、かんせいプログラムをいれてみよう',
+ 'gui.howtos.block-basics-lv0.run':
+ '{greenFlag}をおすと、ネコがマウスポインターをおいかけるよ。マウスをうごかしてにげよう!',
+ 'gui.howtos.block-basics-lv0.modify':
+ '「こんにちは!」のことばや、「10ぽうごかす」のほすうをかえて、うごきをかえてみよう',
+
+ 'gui.howtos.block-basics-lv2.name': '【Lv2】ブロックをくみたてよう',
+ 'gui.howtos.block-basics-lv2.intro': '【Lv2】おなじゲームを、ブロックをじぶんでくみたててつくってみよう',
+ 'gui.howtos.block-basics-lv2.block1':
+ '「🏁がおされたとき」のなかに「ずっと」をいれて、「マウスのポインターへむける」をおこう',
+ 'gui.howtos.block-basics-lv2.block2': '「10ぽうごかす」をついかしよう。これでネコがマウスにむかってすすむよ',
+ 'gui.howtos.block-basics-lv2.block3':
+ '「もし〈マウスのポインターにふれた〉なら」のなかに「こんにちは!と2びょういう」をいれよう',
+ 'gui.howtos.block-basics-lv2.run': '{greenFlag}をおして、じぶんでくみたてたゲームをうごかそう',
+
+ 'gui.howtos.block-basics-lv3.name': '【Lv3】Rubyでかこう',
+ 'gui.howtos.block-basics-lv3.intro': '【Lv3】おなじゲームを、Rubyのプログラムでかいてみよう',
+ 'gui.howtos.block-basics-lv3.rubyTab': 'ルビー(Ruby)タブがひらいていることをかくにんしよう',
+ 'gui.howtos.block-basics-lv3.insert': '「コードをそうにゅう」ボタンをおして、Rubyのプログラムをいれてみよう',
+ 'gui.howtos.block-basics-lv3.modify':
+ 'say("こんにちは!", 2) のことばやびょうすうをかえて、じぶんだけのゲームにしよう',
+ 'gui.howtos.block-basics-lv3.run': '{greenFlag}をおすと、Rubyでかいたゲームがうごくよ',
+
+ 'gui.howtos.block-basics-advanced.name': '【はってん】かんせいデモをうごかそう',
+ 'gui.howtos.block-basics-advanced.intro':
+ '【はってん】ほんしょ 第1しょうでは、このあとゲームをもっとたのしくしていきます。ここではかんせいデモをうごかしてみてみよう',
+ 'gui.howtos.block-basics-advanced.insert':
+ '「コードをそうにゅう」ボタンで、かんせいした「ネコからにげるゲーム」をいれよう',
+ 'gui.howtos.block-basics-advanced.run':
+ '{greenFlag}をおして、かんせいデモをうごかそう。マウスをうごかしてネコからにげよう!',
+};
+
+const blockBasicsEn = {
+ 'gui.howtos.block-basics-lv0.name': '[Lv0] Run the cat-chase game',
+ 'gui.howtos.block-basics-lv0.intro':
+ "[Lv0] Let's make a cat-chase game. The cat chases the mouse pointer!",
+ 'gui.howtos.block-basics-lv0.insert': 'Press the "Insert code" button to add the finished program',
+ 'gui.howtos.block-basics-lv0.run':
+ '{greenFlag} Press it and the cat chases the mouse pointer. Move the mouse to run away!',
+ 'gui.howtos.block-basics-lv0.modify':
+ 'Change the "Hello!" words or the "move 10 steps" number to change how it moves',
+
+ 'gui.howtos.block-basics-lv2.name': '[Lv2] Build it with blocks',
+ 'gui.howtos.block-basics-lv2.intro': '[Lv2] Build the same game yourself with blocks',
+ 'gui.howtos.block-basics-lv2.block1':
+ 'Inside "when 🏁 clicked", add "forever" and place "point towards mouse pointer"',
+ 'gui.howtos.block-basics-lv2.block2': 'Add "move 10 steps" so the cat moves towards the mouse',
+ 'gui.howtos.block-basics-lv2.block3':
+ 'Inside "if touching mouse pointer", add "say Hello! for 2 seconds"',
+ 'gui.howtos.block-basics-lv2.run': '{greenFlag} Press it to run the game you built',
+
+ 'gui.howtos.block-basics-lv3.name': '[Lv3] Write it in Ruby',
+ 'gui.howtos.block-basics-lv3.intro': '[Lv3] Write the same game as a Ruby program',
+ 'gui.howtos.block-basics-lv3.rubyTab': 'Make sure the Ruby tab is open',
+ 'gui.howtos.block-basics-lv3.insert': 'Press the "Insert code" button to add the Ruby program',
+ 'gui.howtos.block-basics-lv3.modify':
+ 'Change the words or seconds in say("Hello!", 2) to make it your own game',
+ 'gui.howtos.block-basics-lv3.run': '{greenFlag} Press it and the game you wrote in Ruby runs',
+
+ 'gui.howtos.block-basics-advanced.name': '[Advanced] Run the finished demo',
+ 'gui.howtos.block-basics-advanced.intro':
+ '[Advanced] Chapter 1 of the book keeps making the game more fun. Here, just run the finished demo',
+ 'gui.howtos.block-basics-advanced.insert':
+ 'Use the "Insert code" button to add the finished cat-chase game',
+ 'gui.howtos.block-basics-advanced.run':
+ '{greenFlag} Press it to run the finished demo. Move the mouse to run away from the cat!',
+};
+
export const ja = {
...bookPromo,
+ ...blockBasics,
};
export const jaHira = {
...bookPromoHira,
+ ...blockBasicsHira,
};
export const en = {
...bookPromoEn,
+ ...blockBasicsEn,
};
diff --git a/packages/scratch-gui/src/lib/libraries/decks/categories/book-promo.jsx b/packages/scratch-gui/src/lib/libraries/decks/categories/book-promo.jsx
index 920134a1d14..321a217ae02 100644
--- a/packages/scratch-gui/src/lib/libraries/decks/categories/book-promo.jsx
+++ b/packages/scratch-gui/src/lib/libraries/decks/categories/book-promo.jsx
@@ -6,25 +6,24 @@
// Fujimura's Smalruby book, so every deck opens with the book's
// bibliographic info and closes with a "続きは書籍で" purchase link.
//
-// The bibliographic details below are PLACEHOLDERS. The book's official
-// title, publisher, ISBN and purchase URL are still being confirmed with
-// the author; once fixed, update the single `BOOK` constant here and every
-// deck picks up the change (see docs/tutorial/improvement-plan.md Phase 3,
+// Confirmed bibliographic details (co-author takaokouji provided; see
+// docs/tutorial/book-kirakira-ruby-source.md and issue #962). Update the
+// single `BOOK` constant here and every deck picks up the change (Phase 3,
// issue #956).
import React from 'react';
import {FormattedMessage} from 'react-intl';
/**
- * Placeholder bibliographic info for the Smalruby book. Replace these values
- * in one place once the official details are confirmed (issue #956).
+ * Bibliographic info for the Smalruby book (confirmed via issue #962). Change
+ * these values in one place and every book-promo step picks it up.
* @type {{title: string, author: string, publisher: string, isbn: string, url: string}}
*/
export const BOOK = {
- title: 'キラキラRuby', // 仮題 — 確定後に差し替え
- author: '藤村健吾',
- publisher: '', // 未確定
- isbn: '', // 未確定
- url: 'https://smalruby.app/', // プレースホルダ購入リンク — 確定後に差し替え
+ title: '小学生から楽しむ きらきらRubyプログラミング',
+ author: '高尾宏治・藤村健吾',
+ publisher: '日経BP',
+ isbn: '978-4-8222-8639-2',
+ url: 'https://bookplus.nikkei.com/atcl/catalog/20/P60510/',
};
/**
diff --git a/packages/scratch-gui/src/lib/libraries/decks/en-steps.js b/packages/scratch-gui/src/lib/libraries/decks/en-steps.js
index e09179c26e7..dd62cf59343 100644
--- a/packages/scratch-gui/src/lib/libraries/decks/en-steps.js
+++ b/packages/scratch-gui/src/lib/libraries/decks/en-steps.js
@@ -115,6 +115,30 @@ import rubyBasics6Step5 from './steps/ruby-basics-6-5-modify.png';
// only the final step shows the TryRuby screenshot (see tutorial.md image reuse rule).
import rubyBasics7TryRuby from './thumbnails/ruby-basics-1-tryruby.png';
+// Block Basics 第1章「ネコからにげるゲーム」(issue #957). Placeholder step
+// images pending headful screenshot capture (see tutorial.md screenshot
+// workflow). Same image files as ja-steps.js.
+import blockBasicsBookOpening from './steps/block-basics-book-opening.png';
+import blockBasicsBookClosing from './steps/block-basics-book-closing.png';
+import blockBasicsBookAdvancedClosing from './steps/block-basics-book-advanced-closing.png';
+import blockBasicsLv0Intro from './steps/block-basics-lv0-1-intro.png';
+import blockBasicsLv0Insert from './steps/block-basics-lv0-2-insert.png';
+import blockBasicsLv0Run from './steps/block-basics-lv0-3-run.png';
+import blockBasicsLv0Modify from './steps/block-basics-lv0-4-modify.png';
+import blockBasicsLv2Intro from './steps/block-basics-lv2-1-intro.png';
+import blockBasicsLv2Block1 from './steps/block-basics-lv2-2-block1.png';
+import blockBasicsLv2Block2 from './steps/block-basics-lv2-3-block2.png';
+import blockBasicsLv2Block3 from './steps/block-basics-lv2-4-block3.png';
+import blockBasicsLv2Run from './steps/block-basics-lv2-5-run.png';
+import blockBasicsLv3Intro from './steps/block-basics-lv3-1-intro.png';
+import blockBasicsLv3RubyTab from './steps/block-basics-lv3-2-ruby-tab.png';
+import blockBasicsLv3Insert from './steps/block-basics-lv3-3-insert.png';
+import blockBasicsLv3Modify from './steps/block-basics-lv3-4-modify.png';
+import blockBasicsLv3Run from './steps/block-basics-lv3-5-run.png';
+import blockBasicsAdvIntro from './steps/block-basics-adv-1-intro.png';
+import blockBasicsAdvInsert from './steps/block-basics-adv-2-insert.png';
+import blockBasicsAdvRun from './steps/block-basics-adv-3-run.png';
+
const enImages = {
// Getting Started
introRubyTab: introRubyTab,
@@ -214,7 +238,29 @@ const enImages = {
rubyBasics6Step4: rubyBasics6Step4,
rubyBasics6Step5: rubyBasics6Step5,
- rubyBasics7TryRuby: rubyBasics7TryRuby
+ rubyBasics7TryRuby: rubyBasics7TryRuby,
+
+ // Block Basics 第1章: ネコからにげるゲーム (issue #957)
+ blockBasicsBookOpening: blockBasicsBookOpening,
+ blockBasicsBookClosing: blockBasicsBookClosing,
+ blockBasicsBookAdvancedClosing: blockBasicsBookAdvancedClosing,
+ blockBasicsLv0Intro: blockBasicsLv0Intro,
+ blockBasicsLv0Insert: blockBasicsLv0Insert,
+ blockBasicsLv0Run: blockBasicsLv0Run,
+ blockBasicsLv0Modify: blockBasicsLv0Modify,
+ blockBasicsLv2Intro: blockBasicsLv2Intro,
+ blockBasicsLv2Block1: blockBasicsLv2Block1,
+ blockBasicsLv2Block2: blockBasicsLv2Block2,
+ blockBasicsLv2Block3: blockBasicsLv2Block3,
+ blockBasicsLv2Run: blockBasicsLv2Run,
+ blockBasicsLv3Intro: blockBasicsLv3Intro,
+ blockBasicsLv3RubyTab: blockBasicsLv3RubyTab,
+ blockBasicsLv3Insert: blockBasicsLv3Insert,
+ blockBasicsLv3Modify: blockBasicsLv3Modify,
+ blockBasicsLv3Run: blockBasicsLv3Run,
+ blockBasicsAdvIntro: blockBasicsAdvIntro,
+ blockBasicsAdvInsert: blockBasicsAdvInsert,
+ blockBasicsAdvRun: blockBasicsAdvRun
};
export {enImages};
diff --git a/packages/scratch-gui/src/lib/libraries/decks/ja-steps.js b/packages/scratch-gui/src/lib/libraries/decks/ja-steps.js
index 6f8c65d90fc..befc35ab7c4 100644
--- a/packages/scratch-gui/src/lib/libraries/decks/ja-steps.js
+++ b/packages/scratch-gui/src/lib/libraries/decks/ja-steps.js
@@ -116,6 +116,30 @@ import rubyBasics6Step5 from './steps/ruby-basics-6-5-modify.png';
// (docs/tutorial/improvement-plan.md Phase 2 / tutorial.md「画像の流用ルール」)。
import rubyBasics7TryRuby from './thumbnails/ruby-basics-1-tryruby.png';
+// Block Basics 第1章「ネコからにげるゲーム」(issue #957). Placeholder step
+// images pending headful screenshot capture (see tutorial.md screenshot
+// workflow). Shared book-promo opening/closing images plus per-deck steps.
+import blockBasicsBookOpening from './steps/block-basics-book-opening.png';
+import blockBasicsBookClosing from './steps/block-basics-book-closing.png';
+import blockBasicsBookAdvancedClosing from './steps/block-basics-book-advanced-closing.png';
+import blockBasicsLv0Intro from './steps/block-basics-lv0-1-intro.png';
+import blockBasicsLv0Insert from './steps/block-basics-lv0-2-insert.png';
+import blockBasicsLv0Run from './steps/block-basics-lv0-3-run.png';
+import blockBasicsLv0Modify from './steps/block-basics-lv0-4-modify.png';
+import blockBasicsLv2Intro from './steps/block-basics-lv2-1-intro.png';
+import blockBasicsLv2Block1 from './steps/block-basics-lv2-2-block1.png';
+import blockBasicsLv2Block2 from './steps/block-basics-lv2-3-block2.png';
+import blockBasicsLv2Block3 from './steps/block-basics-lv2-4-block3.png';
+import blockBasicsLv2Run from './steps/block-basics-lv2-5-run.png';
+import blockBasicsLv3Intro from './steps/block-basics-lv3-1-intro.png';
+import blockBasicsLv3RubyTab from './steps/block-basics-lv3-2-ruby-tab.png';
+import blockBasicsLv3Insert from './steps/block-basics-lv3-3-insert.png';
+import blockBasicsLv3Modify from './steps/block-basics-lv3-4-modify.png';
+import blockBasicsLv3Run from './steps/block-basics-lv3-5-run.png';
+import blockBasicsAdvIntro from './steps/block-basics-adv-1-intro.png';
+import blockBasicsAdvInsert from './steps/block-basics-adv-2-insert.png';
+import blockBasicsAdvRun from './steps/block-basics-adv-3-run.png';
+
const jaImages = {
// Getting Started
introRubyTab: introRubyTab,
@@ -215,7 +239,29 @@ const jaImages = {
rubyBasics6Step4: rubyBasics6Step4,
rubyBasics6Step5: rubyBasics6Step5,
- rubyBasics7TryRuby: rubyBasics7TryRuby
+ rubyBasics7TryRuby: rubyBasics7TryRuby,
+
+ // Block Basics 第1章: ネコからにげるゲーム (issue #957)
+ blockBasicsBookOpening: blockBasicsBookOpening,
+ blockBasicsBookClosing: blockBasicsBookClosing,
+ blockBasicsBookAdvancedClosing: blockBasicsBookAdvancedClosing,
+ blockBasicsLv0Intro: blockBasicsLv0Intro,
+ blockBasicsLv0Insert: blockBasicsLv0Insert,
+ blockBasicsLv0Run: blockBasicsLv0Run,
+ blockBasicsLv0Modify: blockBasicsLv0Modify,
+ blockBasicsLv2Intro: blockBasicsLv2Intro,
+ blockBasicsLv2Block1: blockBasicsLv2Block1,
+ blockBasicsLv2Block2: blockBasicsLv2Block2,
+ blockBasicsLv2Block3: blockBasicsLv2Block3,
+ blockBasicsLv2Run: blockBasicsLv2Run,
+ blockBasicsLv3Intro: blockBasicsLv3Intro,
+ blockBasicsLv3RubyTab: blockBasicsLv3RubyTab,
+ blockBasicsLv3Insert: blockBasicsLv3Insert,
+ blockBasicsLv3Modify: blockBasicsLv3Modify,
+ blockBasicsLv3Run: blockBasicsLv3Run,
+ blockBasicsAdvIntro: blockBasicsAdvIntro,
+ blockBasicsAdvInsert: blockBasicsAdvInsert,
+ blockBasicsAdvRun: blockBasicsAdvRun
};
export {jaImages};
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-1-intro.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-1-intro.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-1-intro.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-2-insert.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-2-insert.png
new file mode 100644
index 00000000000..1a5174bb9e9
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-2-insert.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-3-run.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-3-run.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-adv-3-run.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-advanced-closing.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-advanced-closing.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-advanced-closing.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-closing.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-closing.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-closing.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-opening.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-opening.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-book-opening.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-1-intro.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-1-intro.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-1-intro.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-2-insert.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-2-insert.png
new file mode 100644
index 00000000000..1a5174bb9e9
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-2-insert.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-3-run.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-3-run.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-3-run.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-4-modify.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-4-modify.png
new file mode 100644
index 00000000000..1a5174bb9e9
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv0-4-modify.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-1-intro.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-1-intro.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-1-intro.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-2-block1.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-2-block1.png
new file mode 100644
index 00000000000..a54ad2de942
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-2-block1.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-3-block2.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-3-block2.png
new file mode 100644
index 00000000000..c6fdd36a7bb
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-3-block2.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-4-block3.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-4-block3.png
new file mode 100644
index 00000000000..1a5174bb9e9
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-4-block3.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-5-run.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-5-run.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv2-5-run.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-1-intro.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-1-intro.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-1-intro.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-2-ruby-tab.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-2-ruby-tab.png
new file mode 100644
index 00000000000..4087d58794a
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-2-ruby-tab.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-3-insert.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-3-insert.png
new file mode 100644
index 00000000000..4087d58794a
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-3-insert.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-4-modify.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-4-modify.png
new file mode 100644
index 00000000000..de6c759f43a
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-4-modify.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-5-run.png b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-5-run.png
new file mode 100644
index 00000000000..e3bd49a1fcd
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/steps/block-basics-lv3-5-run.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-advanced.png b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-advanced.png
new file mode 100644
index 00000000000..3c866093339
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-advanced.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv0.png b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv0.png
new file mode 100644
index 00000000000..e3cdd4679fe
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv0.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv2.png b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv2.png
new file mode 100644
index 00000000000..e68b13494cc
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv2.png differ
diff --git a/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv3.png b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv3.png
new file mode 100644
index 00000000000..8ba6de75837
Binary files /dev/null and b/packages/scratch-gui/src/lib/libraries/decks/thumbnails/block-basics-lv3.png differ
diff --git a/packages/scratch-gui/test/unit/lib/libraries/decks/block-series-decks.test.js b/packages/scratch-gui/test/unit/lib/libraries/decks/block-series-decks.test.js
new file mode 100644
index 00000000000..5ac843d52e8
--- /dev/null
+++ b/packages/scratch-gui/test/unit/lib/libraries/decks/block-series-decks.test.js
@@ -0,0 +1,142 @@
+/**
+ * Structural + locale + conversion coverage for the blockBasics 第1章
+ * 「ネコからにげるゲーム」tutorial decks (issue #957, Phase 3 / EPIC #680).
+ *
+ * These decks are content (not logic), so the meaningful regressions are:
+ * 1. deck wiring — category / urlId / step shape / Lv3 Ruby-tab setup
+ * 2. locale completeness — every FormattedMessage id resolves in ja / ja-Hira / en
+ * 3. book-promo framing — every deck opens with the bibliographic step and
+ * closes with a purchase-link (externalUrl) step
+ * 4. runnability — every step's `code` snippet actually converts to blocks
+ * (so the "run the finished cat-chase game" promise is real)
+ * 5. image wiring — every step image key is registered in ja-steps / en-steps
+ *
+ * Mirrors ruby-basics-decks.test.js. See docs/tutorial/improvement-plan.md
+ * Phase 3.
+ */
+import decks from '../../../../../src/lib/libraries/decks/index.jsx';
+import {CATEGORIES} from '../../../../../src/lib/libraries/tutorial-tags';
+import {jaImages} from '../../../../../src/lib/libraries/decks/ja-steps';
+import {enImages} from '../../../../../src/lib/libraries/decks/en-steps';
+import {BOOK_URL} from '../../../../../src/lib/libraries/decks/categories/book-promo.jsx';
+import ja from '../../../../../src/locales/ja';
+import jaHira from '../../../../../src/locales/ja-Hira';
+import en from '../../../../../src/locales/en';
+import RubyToBlocksConverter from '../../../../../src/lib/ruby-to-blocks-converter';
+
+const LOCALES = {ja, 'ja-Hira': jaHira, en};
+
+// The four decks this suite guards: a basic 3-deck ladder + one 発展 demo deck.
+const EXPECTED_DECK_IDS = [
+ 'block-basics-lv0',
+ 'block-basics-lv2',
+ 'block-basics-lv3',
+ 'block-basics-advanced',
+];
+
+// Collect every FormattedMessage id referenced by a deck (name + step titles +
+// external-link button labels).
+const collectMessageIds = deck => {
+ const ids = [];
+ if (deck.name && deck.name.props && deck.name.props.id) {
+ ids.push(deck.name.props.id);
+ }
+ deck.steps.forEach(step => {
+ if (step.title && step.title.props && step.title.props.id) {
+ ids.push(step.title.props.id);
+ }
+ if (step.externalUrlLabel && step.externalUrlLabel.props && step.externalUrlLabel.props.id) {
+ ids.push(step.externalUrlLabel.props.id);
+ }
+ });
+ return ids;
+};
+
+describe('Block Basics tutorial decks (issue #957)', () => {
+ test('all four decks exist under the blockBasics category', () => {
+ EXPECTED_DECK_IDS.forEach(id => {
+ expect(decks[id]).toBeDefined();
+ expect(decks[id].category).toBe(CATEGORIES.blockBasics);
+ });
+ });
+
+ describe.each(EXPECTED_DECK_IDS)('%s', deckId => {
+ let deck;
+ beforeEach(() => {
+ deck = decks[deckId];
+ });
+
+ test('has a urlId and at least 3 steps', () => {
+ expect(typeof deck.urlId).toBe('string');
+ expect(deck.urlId.length).toBeGreaterThan(0);
+ expect(Array.isArray(deck.steps)).toBe(true);
+ expect(deck.steps.length).toBeGreaterThanOrEqual(3);
+ });
+
+ test('opens with a bibliographic step and closes with a purchase-link step', () => {
+ // First step is the shared book-opening step (image only, no code).
+ const first = deck.steps[0];
+ expect(first.title).toBeTruthy();
+ expect(typeof first.image).toBe('string');
+
+ // Last step links to the book purchase page via externalUrl.
+ const last = deck.steps[deck.steps.length - 1];
+ expect(last.externalUrl).toBe(BOOK_URL);
+ expect(last.externalUrlLabel).toBeTruthy();
+ });
+
+ test('every message id resolves in ja / ja-Hira / en', () => {
+ const ids = collectMessageIds(deck);
+ expect(ids.length).toBeGreaterThan(0);
+ const missing = [];
+ ids.forEach(id => {
+ Object.entries(LOCALES).forEach(([locale, table]) => {
+ if (!Object.prototype.hasOwnProperty.call(table, id)) {
+ missing.push(`${id} missing in ${locale}`);
+ }
+ });
+ });
+ expect(missing).toEqual([]);
+ });
+
+ test('every step image key is registered in ja-steps and en-steps', () => {
+ const missing = [];
+ deck.steps.forEach(step => {
+ if (typeof step.image !== 'string') return;
+ if (!Object.prototype.hasOwnProperty.call(jaImages, step.image)) {
+ missing.push(`${step.image} missing in ja-steps`);
+ }
+ if (!Object.prototype.hasOwnProperty.call(enImages, step.image)) {
+ missing.push(`${step.image} missing in en-steps`);
+ }
+ });
+ expect(missing).toEqual([]);
+ });
+
+ test('every step code snippet converts to blocks without errors', async () => {
+ const codeSteps = deck.steps.filter(step => step.code);
+ for (const step of codeSteps) {
+ const converter = new RubyToBlocksConverter(null, {version: '2'});
+ const res = await converter.targetCodeToBlocks(null, step.code);
+ expect(converter.errors).toHaveLength(0);
+ expect(res).toBeTruthy();
+ }
+ });
+ });
+
+ test('the Lv3 deck opens the Ruby tab in Ruby mode via setup', () => {
+ expect(decks['block-basics-lv3'].setup).toEqual({tab: 'ruby', rubyMode: 'ruby'});
+ });
+
+ test('the code-insert decks (Lv0 / Lv3 / advanced) each carry a runnable snippet', () => {
+ ['block-basics-lv0', 'block-basics-lv3', 'block-basics-advanced'].forEach(id => {
+ const codeSteps = decks[id].steps.filter(step => step.code);
+ expect(codeSteps.length).toBeGreaterThan(0);
+ });
+ });
+
+ test('the Lv2 deck has the user assemble blocks by hand (no code-insert step)', () => {
+ const codeSteps = decks['block-basics-lv2'].steps.filter(step => step.code);
+ expect(codeSteps).toHaveLength(0);
+ });
+});
diff --git a/packages/scratch-gui/test/unit/lib/libraries/decks/tutorial-categories-refactor.test.js b/packages/scratch-gui/test/unit/lib/libraries/decks/tutorial-categories-refactor.test.js
index 1513ca0875d..a64c5fd7d9f 100644
--- a/packages/scratch-gui/test/unit/lib/libraries/decks/tutorial-categories-refactor.test.js
+++ b/packages/scratch-gui/test/unit/lib/libraries/decks/tutorial-categories-refactor.test.js
@@ -1,8 +1,13 @@
/**
* Guards the decks/index.jsx + locale-file split into per-category modules
- * (issue #932): deck order/content must stay identical, and the Phase 3
- * (#680) / Phase 4 (#681) stub categories must be independent, empty, and
- * not collide with the existing categories.
+ * (issue #932): the getting-started / chat / ruby-basics deck order/content
+ * must stay identical after the split, and the Phase 3 (#680) / Phase 4
+ * (#681) categories must be independent and not collide with the existing
+ * categories.
+ *
+ * Phase 3 第1章 (issue #957) populated the block-series category with the four
+ * blockBasics decks, so it is no longer an empty stub; dncl (Phase 4, #681)
+ * remains an empty stub for now.
*/
import decks from '../../../../../src/lib/libraries/decks/index.jsx';
import gettingStartedDecks from '../../../../../src/lib/libraries/decks/categories/getting-started.jsx';
@@ -25,6 +30,8 @@ import ja from '../../../../../src/locales/ja';
import jaHira from '../../../../../src/locales/ja-Hira';
import en from '../../../../../src/locales/en';
+// The getting-started / chat / ruby-basics decks, in the order they appeared
+// before the #932 split. index.jsx must keep this order intact.
const EXPECTED_DECK_ID_ORDER = [
'intro-getting-started',
'chat-1-basic-1',
@@ -45,9 +52,20 @@ const EXPECTED_DECK_ID_ORDER = [
'ruby-basics-7-next'
];
+// The blockBasics decks added by Phase 3 第1章 (issue #957). They are spread
+// after ...rubyBasics (and before the still-empty ...dncl) in index.jsx.
+const BLOCK_SERIES_DECK_ID_ORDER = [
+ 'block-basics-lv0',
+ 'block-basics-lv2',
+ 'block-basics-lv3',
+ 'block-basics-advanced'
+];
+
describe('Tutorial deck category split (issue #932)', () => {
- test('index.jsx exposes the same deck ids in the same order as before the split', () => {
- expect(Object.keys(decks)).toEqual(EXPECTED_DECK_ID_ORDER);
+ test('index.jsx keeps the original deck order and appends the blockBasics decks', () => {
+ // The split (#932) preserved the original order; #957 appended the
+ // blockBasics decks after ruby-basics. dncl is still empty so adds none.
+ expect(Object.keys(decks)).toEqual([...EXPECTED_DECK_ID_ORDER, ...BLOCK_SERIES_DECK_ID_ORDER]);
});
test('getting-started and ruby-basics decks are composed from their category modules', () => {
@@ -60,13 +78,18 @@ describe('Tutorial deck category split (issue #932)', () => {
expect(decks['ruby-basics-6-methods']).toBe(rubyBasicsDecks['ruby-basics-6-methods']);
});
- test('block-series and dncl are empty stub categories that add no deck ids', () => {
- expect(blockSeriesDecks).toEqual({});
+ test('block-series holds exactly the four blockBasics decks (issue #957); dncl is still an empty stub', () => {
+ expect(Object.keys(blockSeriesDecks)).toEqual(BLOCK_SERIES_DECK_ID_ORDER);
expect(dnclDecks).toEqual({});
+ // Neither category may collide with the pre-existing deck ids.
EXPECTED_DECK_ID_ORDER.forEach(id => {
expect(Object.prototype.hasOwnProperty.call(blockSeriesDecks, id)).toBe(false);
expect(Object.prototype.hasOwnProperty.call(dnclDecks, id)).toBe(false);
});
+ // block-series decks are composed from their category module.
+ BLOCK_SERIES_DECK_ID_ORDER.forEach(id => {
+ expect(decks[id]).toBe(blockSeriesDecks[id]);
+ });
});
test('dncl locale stub is empty for all three locales', () => {
@@ -75,15 +98,20 @@ describe('Tutorial deck category split (issue #932)', () => {
expect(dnclLocale.en).toEqual({});
});
- test('block-series locale holds only the shared book-promo foundation (issue #956), no deck-id howtos', () => {
- // #956 populates the Block-axis common book-promotion strings; the
- // per-deck howto strings still come later (#680).
+ test('block-series locale holds the shared book-promo foundation (#956) plus the blockBasics howtos (#957)', () => {
+ // #956 populates the Block-axis common book-promotion strings; #957
+ // adds the per-deck blockBasics howto strings on top of them.
[blockSeriesLocale.ja, blockSeriesLocale.jaHira, blockSeriesLocale.en].forEach(table => {
const keys = Object.keys(table);
expect(keys.length).toBeGreaterThan(0);
keys.forEach(id => {
- expect(id.startsWith('gui.howtos.book-promo.')).toBe(true);
+ expect(
+ id.startsWith('gui.howtos.book-promo.') || id.startsWith('gui.howtos.block-basics-')
+ ).toBe(true);
});
+ // Both families must actually be present.
+ expect(keys.some(id => id.startsWith('gui.howtos.book-promo.'))).toBe(true);
+ expect(keys.some(id => id.startsWith('gui.howtos.block-basics-'))).toBe(true);
});
});