diff --git a/.claude/rules/code-style.md b/.claude/rules/code-style.md index 26308072d92..6d8fdb47ae2 100644 --- a/.claude/rules/code-style.md +++ b/.claude/rules/code-style.md @@ -83,6 +83,16 @@ export default MyComponent; ## Smalruby Marker Comments +### マーカーが必要なファイル / 不要なファイルの判定 + +**Smalruby 固有ファイル(= Prettier 対象ファイル)にはマーカー不要。** + +- Prettier 対象ファイル一覧(`smalruby-prettier-files.md`)に含まれるファイルは Smalruby 独自ファイル +- これらのファイルを修正する際、`=== Smalruby:` マーカーは **一切不要**(ファイル内のどこにも付けない) +- upstream(Scratch)のファイルを修正する際のみ、修正箇所にマーカーコメントを付ける + +### upstream ファイルのマーカー記法 + upstream のファイルに Smalruby 固有のコードを追加する際は、必ず **マーカーコメント** で囲む。 ```javascript @@ -93,11 +103,10 @@ upstream のファイルに Smalruby 固有のコードを追加する際は、 - Start と End は必ずペアにする - `<機能名>` は英語で、何の機能かわかる名前にする -- ファイル全体が Smalruby 固有の場合はファイル冒頭に `// === Smalruby: This file is Smalruby-specific (<説明>) ===` -- マーカーを追加・削除したら、該当パッケージの `development.md` のマーカー一覧を更新する +- マーカーを追加・削除したら、該当パッケージのマーカー一覧を更新する -詳細は各パッケージの development.md を参照: -- `.claude/rules/scratch-gui/development.md` — scratch-gui のマーカー一覧 +マーカー一覧: +- `.claude/rules/scratch-gui/smalruby-markers.md` — scratch-gui のマーカー一覧 - `.claude/rules/scratch-vm/development.md` — scratch-vm のマーカー一覧 ## Documentation diff --git a/.claude/rules/scratch-gui/e2e-test.md b/.claude/rules/scratch-gui/e2e-test.md new file mode 100644 index 00000000000..3c1de207e6d --- /dev/null +++ b/.claude/rules/scratch-gui/e2e-test.md @@ -0,0 +1,118 @@ +# E2E Testing (Playwright MCP & Selenium Integration Tests) + +## data-testid Convention + +**Playwright MCP と Selenium integration tests の両方で `data-testid` 属性を使用する。** + +- 新しいボタン・フォーム要素を追加する際は、必ず `data-testid` を設定する +- Integration tests では `data-testid` を優先的に使い、XPath や title 属性での要素指定を避ける +- `data-testid` を使うことで、属性の追加漏れを防ぎ、テストの安定性を高める + +### Integration Tests での data-testid 使用パターン + +```javascript +// ヘルパー関数(テストファイル内で定義) +const clickByTestId = (d, testId) => + d.executeScript(`document.querySelector('[data-testid="${testId}"]').click()`); + +const isActiveByTestId = (d, testId) => + d.executeScript( + `return document.querySelector('[data-testid="${testId}"]')` + + `?.className?.includes('Active') ?? false`, + ); + +// 使用例 +await clickByTestId(driver, 'ruby-toolbar-mode-dncl'); +expect(await isActiveByTestId(driver, 'ruby-toolbar-mode-dncl')).toBe(true); +``` + +### Naming Convention + +`data-testid` は `-` の形式: +- Component prefix: コンポーネント名をケバブケース(例: `ruby-toolbar`) +- Element suffix: 要素の役割をケバブケース(例: `mode-dncl`) + +### Ruby Toolbar (`ruby-toolbar.jsx`) + +| data-testid | 要素 | 説明 | +|------------|------|------| +| `ruby-toolbar-execute` | button | 実行/停止ボタン | +| `ruby-toolbar-undo` | button | 元に戻す | +| `ruby-toolbar-redo` | button | やり直す | +| `ruby-toolbar-search` | button | 検索 | +| `ruby-toolbar-auto-correct` | button | 自動置換トグル | +| `ruby-toolbar-rubytee` | button | ルビティー(AI) | +| `ruby-toolbar-mode-furigana` | button | ふりがなモード | +| `ruby-toolbar-mode-ruby` | button | Rubyモード | +| `ruby-toolbar-mode-dncl` | button | 日本語(DNCL)モード | +| `ruby-toolbar-more-menu` | button | その他メニュー | +| `ruby-toolbar-menu-download` | div | Rubyスクリプト保存 | +| `ruby-toolbar-menu-insert-class` | div | クラス挿入 | +| `ruby-toolbar-menu-preview` | div | プレビュー | +| `ruby-toolbar-menu-auto-correct-settings` | div | 自動置換設定 | + +### Target Selector (`target-selector.jsx`) + +| data-testid | 要素 | 説明 | +|------------|------|------| +| `ruby-toolbar-prev-sprite` | button | 前のスプライト | +| `ruby-toolbar-next-sprite` | button | 次のスプライト | +| `ruby-toolbar-sprite-search` | input | スプライト検索 | + +## Playwright MCP での操作例 + +```javascript +// data-testid でボタンをクリック +// Playwright MCP の browser_click で ref を使う代わりに、 +// browser_evaluate で data-testid を使って操作する +await page.evaluate(() => { + document.querySelector('[data-testid="ruby-toolbar-mode-dncl"]').click(); +}); + +// または Playwright のロケーターを使う +await page.getByTestId('ruby-toolbar-mode-dncl').click(); +``` + +## URL Parameters for Testing + +Playwright MCP でテストする際は以下の URL パラメータを使用: + +``` +http://localhost:8601?no_beforeunload=1&tab=ruby&ruby_version=2&rubyMode=ruby +``` + +| Parameter | Values | Description | +|-----------|--------|-------------| +| `no_beforeunload` | `1` | beforeunload ダイアログを無効化(必須) | +| `tab` | `ruby` | Ruby タブを初期表示 | +| `ruby_version` | `2` | Ruby バージョン | +| `rubyMode` | `ruby`, `furigana`, `dncl` | Ruby タブの初期モード | + +## Monaco Editor の操作 + +```javascript +// エディタの内容を設定 +await page.evaluate(() => { + monaco.editor.getEditors()[0].setValue('move(10)\n'); +}); + +// エディタの内容を取得 +const content = await page.evaluate(() => { + return monaco.editor.getEditors()[0].getValue(); +}); + +// エラーマーカーを取得 +const markers = await page.evaluate(() => { + const model = monaco.editor.getEditors()[0].getModel(); + return monaco.editor.getModelMarkers({ resource: model.uri }).map(m => ({ + line: m.startLineNumber, + message: m.message, + severity: m.severity + })); +}); + +// エディタの言語を取得 +const lang = await page.evaluate(() => { + return monaco.editor.getEditors()[0].getModel().getLanguageId(); +}); +``` diff --git a/.claude/rules/scratch-gui/smalruby-markers.md b/.claude/rules/scratch-gui/smalruby-markers.md index 6e0bd79acac..c94817a4a82 100644 --- a/.claude/rules/scratch-gui/smalruby-markers.md +++ b/.claude/rules/scratch-gui/smalruby-markers.md @@ -11,30 +11,15 @@ paths: description: "Smalruby マーカーコメントの一覧と規約。upstream ファイルへの Smalruby 固有コード追加や upstream merge 時に使用。" --- -# Smalruby Marker Blocks +# Smalruby Marker Blocks (scratch-gui) -Smalruby のカスタムコードは upstream ファイルの中に **マーカーコメント** で囲んで配置する。 -upstream merge 時にコンフリクトを解決しやすくするための仕組み。 +upstream ファイルに追加した Smalruby 固有コードのマーカー一覧。 +マーカーの書式・ルールは `.claude/rules/code-style.md` の「Smalruby Marker Comments」を参照。 -## マーカーの書式 +**重要**: Smalruby 固有ファイル(`smalruby-prettier-files.md` に記載されたファイル)にはマーカー不要。 +このファイルに記載するのは **upstream ファイルに埋め込んだマーカーのみ**。 -```javascript -// === Smalruby: Start of <機能名> === -// ... Smalruby 固有のコード ... -// === Smalruby: End of <機能名> === -``` - -ファイル全体が Smalruby 固有の場合: -```javascript -// === Smalruby: This file is Smalruby-specific (<説明>) === -``` - -## ルール - -1. **upstream ファイルに Smalruby コードを追加するときは必ずマーカーで囲む** -2. **マーカー内のコードだけを変更する** — マーカー外は upstream の管轄 -3. **新しいマーカーを追加したら、このセクションに記載する** -4. **マーカーを削除する場合は、このセクションからも削除する** +マーカーを追加・削除した場合は、下記の一覧を更新すること。 ## 現在のマーカー一覧 @@ -68,24 +53,6 @@ upstream merge 時にコンフリクトを解決しやすくするための仕 | `src/reducers/settings.js` | URL params for Playwright | URL パラメーター import | | `src/reducers/settings.js` | ruby_version URL param | Ruby バージョン URL パラメーター | -## Smalruby 固有ファイル(ファイル全体がマーカー) - -| ファイル | 説明 | -|----------|------| -| `src/components/connection-modal/mesh-v2-initial-step.jsx` | Mesh v2 初期接続ステップコンポーネント | -| `src/components/connection-modal/mesh-v2-network-filtered-step.jsx` | Mesh v2 ネットワークフィルター検出コンポーネント | -| `src/reducers/smalruby-registry.ts` | Smalruby reducer/state の一括エクスポート | -| `src/lib/blocks-gesture-recovery.js` | ジェスチャー復旧ハンドラー(ブロックドラッグのスタック防止) | -| `src/lib/url-params.js` | Playwright テスト用 URL パラメーター解析ユーティリティ | -| `src/containers/ruby-tab/debug-globals.js` | Playwright MCP 用デバッググローバル変数 | -| `src/lib/rubytee-api.js` | Rubytee Relay API クライアント | -| `src/lib/rubytee-context.js` | Rubytee 状態コンテキスト構築 | -| `src/containers/rubytee-modal-hoc.jsx` | Rubytee モーダル HOC(同意フロー + チャット) | -| `src/components/rubytee-modal/rubytee-modal.jsx` | Rubytee チャット UI | -| `src/components/rubytee-modal/rubytee-modal.css` | Rubytee チャット CSS | -| `src/components/rubytee-consent/rubytee-consent.jsx` | Rubytee 同意確認ダイアログ | -| `src/components/rubytee-consent/rubytee-consent.css` | Rubytee 同意確認 CSS | - ## 関連ファイル マーカーで囲まれたコードが参照するファイル: diff --git a/.claude/rules/scratch-gui/smalruby-prettier-files.md b/.claude/rules/scratch-gui/smalruby-prettier-files.md index 5ca6c548731..88a3bf8b692 100644 --- a/.claude/rules/scratch-gui/smalruby-prettier-files.md +++ b/.claude/rules/scratch-gui/smalruby-prettier-files.md @@ -128,6 +128,7 @@ upstream (Scratch) ファイルは対象外。 - `test/integration/block-display-modal.test.js` - `test/integration/block-palette.test.js` - `test/integration/debug_defaults.test.js` +- `test/integration/dncl-mode-validation.test.js` - `test/integration/feedback-link.test.js` - `test/integration/ruby-editor-actions.test.js` - `test/integration/ruby-module.test.js` diff --git a/.claude/rules/scratch-gui/testing.md b/.claude/rules/scratch-gui/testing.md index 3bc1ef387d0..fd507b87574 100644 --- a/.claude/rules/scratch-gui/testing.md +++ b/.claude/rules/scratch-gui/testing.md @@ -99,6 +99,9 @@ describe('motion converter', () => { ## Integration Test Development Workflow +**重要**: Integration tests では `data-testid` を優先的に使用する。 +詳細は `.claude/rules/scratch-gui/e2e-test.md` を参照。 + 1. **Build the application**: ```bash docker compose run --rm app bash -c "cd /app/packages/scratch-gui && npm run build:dev" diff --git a/.claude/rules/scratch-vm/development.md b/.claude/rules/scratch-vm/development.md index a86556c1215..f0efcc0dc40 100644 --- a/.claude/rules/scratch-vm/development.md +++ b/.claude/rules/scratch-vm/development.md @@ -203,23 +203,10 @@ The mesh v2 extension uses AWS AppSync for real-time collaboration: ## Smalruby Marker Blocks -Smalruby のカスタムコードは upstream ファイルの中に **マーカーコメント** で囲んで配置する。 -upstream merge 時にコンフリクトを解決しやすくするための仕組み。 +マーカーの書式・ルールは `.claude/rules/code-style.md` の「Smalruby Marker Comments」を参照。 -### マーカーの書式 - -```javascript -// === Smalruby: Start of <機能名> === -// ... Smalruby 固有のコード ... -// === Smalruby: End of <機能名> === -``` - -### ルール - -1. **upstream ファイルに Smalruby コードを追加するときは必ずマーカーで囲む** -2. **マーカー内のコードだけを変更する** — マーカー外は upstream の管轄 -3. **新しいマーカーを追加したら、このセクションに記載する** -4. **マーカーを削除する場合は、このセクションからも削除する** +**重要**: Smalruby 固有ファイル(`smalruby-prettier-files.md` に記載されたファイル)にはマーカー不要。 +このセクションに記載するのは **upstream ファイルに埋め込んだマーカーのみ**。 ### 現在のマーカー一覧 diff --git a/packages/scratch-gui/.prettierignore b/packages/scratch-gui/.prettierignore index 355f8cd0c17..fcd3de8668a 100644 --- a/packages/scratch-gui/.prettierignore +++ b/packages/scratch-gui/.prettierignore @@ -172,6 +172,7 @@ test/integration/* !test/integration/block-display-modal.test.js !test/integration/block-palette.test.js !test/integration/debug_defaults.test.js +!test/integration/dncl-mode-validation.test.js !test/integration/feedback-link.test.js !test/integration/ruby-editor-actions.test.js !test/integration/ruby-module.test.js diff --git a/packages/scratch-gui/src/components/extension-button/extension-button.css b/packages/scratch-gui/src/components/extension-button/extension-button.css index c0a4af961a4..3fd14369d45 100644 --- a/packages/scratch-gui/src/components/extension-button/extension-button.css +++ b/packages/scratch-gui/src/components/extension-button/extension-button.css @@ -42,6 +42,13 @@ $fade-out-distance: 15px; outline: revert; } +/* === Smalruby: Start of DNCL extension disabled === */ +.extension-button-disabled { + opacity: 0.5; + cursor: not-allowed; +} +/* === Smalruby: End of DNCL extension disabled === */ + .extension-button-icon { width: 1.75rem; height: 1.75rem; diff --git a/packages/scratch-gui/src/components/extension-button/extension-button.jsx b/packages/scratch-gui/src/components/extension-button/extension-button.jsx index ce7f3b77bc1..0293bdf73da 100644 --- a/packages/scratch-gui/src/components/extension-button/extension-button.jsx +++ b/packages/scratch-gui/src/components/extension-button/extension-button.jsx @@ -14,28 +14,46 @@ const messages = defineMessages({ id: 'gui.gui.addExtension', description: 'Button to add an extension in the target pane', defaultMessage: 'Add Extension' + }, + // === Smalruby: Start of DNCL extension alert === + dnclExtensionDisabled: { + id: 'gui.extensionButton.dnclExtensionDisabled', + description: 'Alert message when extension button is clicked in DNCL mode', + defaultMessage: 'Extensions are not available in Japanese mode.' } + // === Smalruby: End of DNCL extension alert === }); const ExtensionButton = props => { const { intl, + dnclMode, onExtensionButtonClick } = props; const {captureFocus} = useContext(ModalFocusContext); const handleExtensionButtonClick = useCallback(() => { + // === Smalruby: Start of DNCL extension alert === + if (dnclMode) { + window.alert(intl.formatMessage(messages.dnclExtensionDisabled)); // eslint-disable-line no-alert + return; + } + // === Smalruby: End of DNCL extension alert === captureFocus(); onExtensionButtonClick?.(); - }, [captureFocus, onExtensionButtonClick]); + }, [captureFocus, onExtensionButtonClick, dnclMode, intl]); return ( @@ -289,6 +306,7 @@ const RubyToolbar = props => { >