Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/tutorial/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@
### 基盤 (Phase 2 前半): `setup` プロパティ

- [x] `src/lib/deck-setup.js` 新規追加 (`applyDeckSetup` ヘルパー)
- [x] deck 定義の type 拡張 (`{ tab, rubyMode, extensions, rubyVersion }`) — `rubyVersion` は将来用フックのみ
- [x] deck 定義の type 拡張 (`{ tab, rubyMode, extensions, rubyVersion }`) — `rubyVersion` も適用 (`dispatch(setRubyVersion)` + `persistRubyVersion`、不正値は無視)
- [x] `tips-library.jsx` で deck 起動時に setup を適用 (vm prop 接続含む)
- [x] `activateTab` / `setDnclMode` / `vm.extensionManager.loadExtensionURL` の冪等な呼び出し
- [x] ロード失敗時のグレースフルデグレード (`console.warn` のみ、deck は開く)
- [x] ふりがなフラグも考慮した rubyMode の動作 (`smalruby:furiganaEnabled` localStorage の同期)
- [x] `test/unit/lib/deck-setup.test.js` で 10 ケースの単体テスト (全 pass)
- [x] `test/unit/lib/deck-setup.test.js` で 13 ケースの単体テスト (全 pass)

### Phase 3 着手前に必要 (外部要因)

Expand Down
24 changes: 20 additions & 4 deletions packages/scratch-gui/src/lib/deck-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
RUBY_TAB_INDEX,
SOUNDS_TAB_INDEX,
} from '../reducers/editor-tab';
import { setRubyVersion } from '../reducers/settings';
import { VERSION_1, VERSION_2 } from './settings/ruby-version';
import { persistRubyVersion } from './settings/ruby-version/persistence';

const FURIGANA_ENABLED_KEY = 'smalruby:furiganaEnabled';

Expand All @@ -20,6 +23,14 @@ const TAB_INDEX_MAP = {

const VALID_RUBY_MODES = new Set(['ruby', 'furigana', 'dncl']);

// setup.rubyVersion may be given as a number (1 | 2) per the design doc, or
// as the string form ('1' | '2') used internally by the settings reducer.
// Normalize to the internal string form, or null if unsupported.
const normalizeRubyVersion = (value) => {
const asString = String(value);
return asString === VERSION_1 || asString === VERSION_2 ? asString : null;
};

/**
* Apply tutorial deck setup: switch editor tab, set Ruby mode, load
* required extensions. Each operation is idempotent — calling this with the
Expand All @@ -32,6 +43,7 @@ const VALID_RUBY_MODES = new Set(['ruby', 'furigana', 'dncl']);
* tab: 'ruby', // 'code' | 'costumes' | 'sounds' | 'ruby'
* rubyMode: 'dncl', // 'ruby' | 'furigana' | 'dncl'
* extensions: ['pen', 'microbitMore'],
* rubyVersion: 2, // 1 | 2 (omit to keep current)
* },
* // ...
* }
Expand Down Expand Up @@ -88,8 +100,12 @@ export const applyDeckSetup = async (setup, dispatch, vm) => {
}
}

// 4. Ruby version is intentionally not handled in Phase 2 foundation —
// no deck currently needs it. The hook is left here as a documentation
// marker for future expansion (would dispatch settings reducer action).
// if (setup.rubyVersion === 1 || setup.rubyVersion === 2) { /* TBD */ }
// 4. Ruby version (optional). Mirrors how the settings menu / URL loader
// switch versions: dispatch the reducer action and persist the choice so
// it survives a remount. Unsupported values are ignored.
const rubyVersion = normalizeRubyVersion(setup.rubyVersion);
if (rubyVersion) {
dispatch(setRubyVersion(rubyVersion));
persistRubyVersion(rubyVersion);
}
};
38 changes: 38 additions & 0 deletions packages/scratch-gui/test/unit/lib/deck-setup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { applyDeckSetup } from '../../../src/lib/deck-setup';
import { SET_DNCL_MODE } from '../../../src/reducers/dncl-mode';

const ACTIVATE_TAB = 'scratch-gui/navigation/ACTIVATE_TAB';
const SET_RUBY_VERSION = 'scratch-gui/settings/SET_RUBY_VERSION';
const RUBY_VERSION_KEY = 'smalruby:rubyVersion';

const BLOCKS_TAB_INDEX = 0;
const COSTUMES_TAB_INDEX = 1;
Expand Down Expand Up @@ -105,6 +107,42 @@ describe('applyDeckSetup', () => {
expect(dispatch.calls.filter((a) => a.type === SET_DNCL_MODE)).toEqual([]);
});

test('rubyVersion (number) dispatches setRubyVersion and persists to localStorage', async () => {
for (const [input, expected] of [
[1, '1'],
[2, '2'],
]) {
const dispatch = makeDispatch();
window.localStorage.clear();
await applyDeckSetup({ rubyVersion: input }, dispatch, makeVM());
expect(dispatch.calls).toContainEqual({
type: SET_RUBY_VERSION,
rubyVersion: expected,
});
expect(window.localStorage.getItem(RUBY_VERSION_KEY)).toBe(expected);
}
});

test('rubyVersion (string) is accepted as well', async () => {
const dispatch = makeDispatch();
await applyDeckSetup({ rubyVersion: '1' }, dispatch, makeVM());
expect(dispatch.calls).toContainEqual({
type: SET_RUBY_VERSION,
rubyVersion: '1',
});
expect(window.localStorage.getItem(RUBY_VERSION_KEY)).toBe('1');
});

test('invalid or omitted rubyVersion is ignored', async () => {
for (const rubyVersion of [undefined, 3, 0, '9', 'bogus', null]) {
const dispatch = makeDispatch();
window.localStorage.clear();
await applyDeckSetup({ rubyVersion }, dispatch, makeVM());
expect(dispatch.calls.filter((a) => a.type === SET_RUBY_VERSION)).toEqual([]);
expect(window.localStorage.getItem(RUBY_VERSION_KEY)).toBeNull();
}
});

test('loads required extensions sequentially', async () => {
const dispatch = makeDispatch();
const vm = makeVM();
Expand Down
Loading