Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4174908
feat: validate Ruby code before switching to DNCL mode
takaokouji Mar 29, 2026
c8331c0
test: add integration tests for DNCL mode validation
takaokouji Mar 29, 2026
41e3709
chore: add data-testid attributes to ruby toolbar for Playwright testing
takaokouji Mar 29, 2026
c9a8a69
fix: clear existing errors before DNCL mode switch validation
takaokouji Mar 29, 2026
015d246
feat: internationalize DNCL validation error message
takaokouji Mar 29, 2026
0be5bd6
feat: fallback to furigana mode when switching from Code tab in DNCL …
takaokouji Mar 29, 2026
a7ee7bb
fix: trigger pending DNCL switch for empty code and tab visibility
takaokouji Mar 29, 2026
b4b1332
fix: use requestAnimationFrame for deferred DNCL switch on tab change
takaokouji Mar 30, 2026
7b47830
refactor: replace intermediate Smalruby markers with file-level marker
takaokouji Mar 30, 2026
22366b5
docs: clarify marker rules - Smalruby-specific files need no markers
takaokouji Mar 30, 2026
0bb349a
fix: use data-testid in integration tests instead of seleniumHelper.By
takaokouji Mar 30, 2026
2c111c4
feat: filter Code tab blocks and disable extensions in DNCL mode
takaokouji Mar 30, 2026
1d0c68f
fix: wait for editor content before DNCL validation in integration tests
takaokouji Mar 30, 2026
a2a9228
fix: use polling waits instead of sleep in DNCL integration tests
takaokouji Mar 30, 2026
2545b8f
fix: use rubyMode=ruby URL param to prevent localStorage interference
takaokouji Mar 30, 2026
21725e9
fix: make DNCL validation error check locale-independent
takaokouji Mar 30, 2026
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
17 changes: 13 additions & 4 deletions .claude/rules/code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
118 changes: 118 additions & 0 deletions .claude/rules/scratch-gui/e2e-test.md
Original file line number Diff line number Diff line change
@@ -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>-<element>` の形式:
- 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();
});
```
45 changes: 6 additions & 39 deletions .claude/rules/scratch-gui/smalruby-markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. **マーカーを削除する場合は、このセクションからも削除する**
マーカーを追加・削除した場合は、下記の一覧を更新すること。

## 現在のマーカー一覧

Expand Down Expand Up @@ -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 |

## 関連ファイル

マーカーで囲まれたコードが参照するファイル:
Expand Down
1 change: 1 addition & 0 deletions .claude/rules/scratch-gui/smalruby-prettier-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
3 changes: 3 additions & 0 deletions .claude/rules/scratch-gui/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 3 additions & 16 deletions .claude/rules/scratch-vm/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ファイルに埋め込んだマーカーのみ**。

### 現在のマーカー一覧

Expand Down
1 change: 1 addition & 0 deletions packages/scratch-gui/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Box className={styles.extensionButtonContainer}>
<button
className={classNames(styles.extensionButton)}
className={classNames(
styles.extensionButton,
{[styles.extensionButtonDisabled]: dnclMode}
)}
title={intl.formatMessage(messages.addExtension)}
onClick={handleExtensionButtonClick}
aria-label={intl.formatMessage(messages.addExtension)}
data-testid="extension-button"
>
<img
className={styles.extensionButtonIcon}
Expand All @@ -48,6 +66,7 @@ const ExtensionButton = props => {
};

ExtensionButton.propTypes = {
dnclMode: PropTypes.bool,
intl: intlShape.isRequired,
onExtensionButtonClick: PropTypes.func
};
Expand Down
5 changes: 5 additions & 0 deletions packages/scratch-gui/src/components/gui/gui.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const GUIComponent = props => {
blockDisplayModalVisible,
blocksId,
blocksTabVisible,
dnclMode, // === Smalruby: DNCL block filtering ===
cardsVisible,
canChangeLanguage,
canChangeColorMode,
Expand Down Expand Up @@ -594,10 +595,13 @@ const GUIComponent = props => {
colorMode={colorMode}
/>
</Box>
{/* === Smalruby: Start of DNCL extension button === */}
<ExtensionsButton
intl={intl}
dnclMode={dnclMode}
onExtensionButtonClick={onExtensionButtonClick}
/>
{/* === Smalruby: End of DNCL extension button === */}
<Box className={styles.watermark}>
<Watermark />
</Box>
Expand Down Expand Up @@ -697,6 +701,7 @@ GUIComponent.propTypes = {
basePath: PropTypes.string,
blockDisplayModalVisible: PropTypes.bool,
blocksTabVisible: PropTypes.bool,
dnclMode: PropTypes.bool, // === Smalruby: DNCL block filtering ===
blocksId: PropTypes.string,
canChangeLanguage: PropTypes.bool,
canChangeColorMode: PropTypes.bool,
Expand Down
Loading
Loading