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); }); });