@robotical/martyblocksjr 3.5.34 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/editions/free/src/app.bundle.js +1 -1
- package/editions/free/src/css/base.css +78 -1
- package/editions/free/src/css/connection-modal.css +6 -1
- package/editions/free/src/css/editor.css +86 -34
- package/editions/free/src/css/editorleftpanel.css +8 -0
- package/editions/free/src/css/editorstage.css +5 -1
- package/editions/free/src/css/lobby.css +28 -9
- package/editions/free/src/css/thumbs.css +18 -0
- package/editions/free/src/css/tutorial.css +6 -1
- package/editions/free/src/home.html +46 -26
- package/editions/free/src/inapp/blocks.html +43 -43
- package/editions/free/src/inapp/interface.html +49 -49
- package/editions/free/src/inapp/paint.html +40 -40
- package/editions/free/src/inapp/style/interface.css +2 -0
- package/editions/free/src/inapp/style/paint.css +2 -0
- package/editions/free/src/inapp/style/style.css +44 -0
- package/editions/free/src/localizations/bg.json +36 -2
- package/editions/free/src/localizations/ca.json +36 -2
- package/editions/free/src/localizations/cs.json +36 -2
- package/editions/free/src/localizations/cy.json +36 -2
- package/editions/free/src/localizations/da.json +36 -2
- package/editions/free/src/localizations/de.json +36 -2
- package/editions/free/src/localizations/el.json +36 -2
- package/editions/free/src/localizations/en.json +35 -1
- package/editions/free/src/localizations/es.json +36 -2
- package/editions/free/src/localizations/fi.json +36 -2
- package/editions/free/src/localizations/fr.json +36 -2
- package/editions/free/src/localizations/it.json +36 -2
- package/editions/free/src/localizations/ja.json +36 -2
- package/editions/free/src/localizations/ko.json +36 -2
- package/editions/free/src/localizations/nl.json +36 -2
- package/editions/free/src/localizations/no.json +36 -2
- package/editions/free/src/localizations/pl.json +36 -2
- package/editions/free/src/localizations/pt-br.json +36 -2
- package/editions/free/src/localizations/pt.json +36 -2
- package/editions/free/src/localizations/sv.json +36 -2
- package/editions/free/src/localizations/th.json +36 -2
- package/editions/free/src/localizations/tr.json +36 -2
- package/editions/free/src/localizations/uk.json +36 -2
- package/editions/free/src/localizations/zh-cn.json +36 -2
- package/editions/free/src/localizations/zh-tw.json +36 -2
- package/package.json +6 -2
- package/scripts/a11y-structure.mjs +177 -0
- package/tests/e2e/accessibility.e2e.test.js +596 -0
- package/vitest.config.js +2 -1
|
@@ -1122,5 +1122,39 @@
|
|
|
1122
1122
|
"MARTY_JRBLOCKS5_STEP_21_TEXT": "สุดท้ายให้เพิ่มบล็อกการทำซ้ำแบบตลอดไปที่ส่วนท้าย",
|
|
1123
1123
|
"MARTY_JRBLOCKS5_STEP_22_TEXT": "เกือบเสร็จแล้ว! สุดท้าย เพิ่มบล็อกธงสีเขียวอีกอันลงในโค้ดตาข่ายฟุตบอล",
|
|
1124
1124
|
"MARTY_JRBLOCKS5_STEP_23_TEXT": "ปิดท้ายด้วยบล็อก<b/>เคาน์เตอร์เริ่ม/หยุด</b> เพื่อให้คะแนนของคุณรีเซ็ตทุกครั้งที่คุณเริ่มเกมใหม่",
|
|
1125
|
-
"MARTY_JRBLOCKS5_END": "ทำได้แล้ว! กดธงเขียวแล้วเล่นเกมยิงประตูของมาร์ตี้!"
|
|
1126
|
-
|
|
1125
|
+
"MARTY_JRBLOCKS5_END": "ทำได้แล้ว! กดธงเขียวแล้วเล่นเกมยิงประตูของมาร์ตี้!",
|
|
1126
|
+
"A11Y_APP_NAVIGATION": "App navigation",
|
|
1127
|
+
"A11Y_CLEAR_TRACE": "Clear trace",
|
|
1128
|
+
"A11Y_CLOSE": "Close",
|
|
1129
|
+
"A11Y_CONNECTION_LOST": "Connection lost",
|
|
1130
|
+
"A11Y_CONNECT": "Connect",
|
|
1131
|
+
"A11Y_CREATE": "Create",
|
|
1132
|
+
"A11Y_DELETE": "Delete",
|
|
1133
|
+
"A11Y_DISCONNECT": "Disconnect",
|
|
1134
|
+
"A11Y_GUIDE_SECTIONS": "Guide sections",
|
|
1135
|
+
"A11Y_HELP": "Help",
|
|
1136
|
+
"A11Y_MAIN_CONTENT": "Main content",
|
|
1137
|
+
"A11Y_MARTY_MODE": "Marty mode",
|
|
1138
|
+
"A11Y_NEXT": "Next",
|
|
1139
|
+
"A11Y_OPEN": "Open",
|
|
1140
|
+
"A11Y_PAGE_TITLE_EDITOR": "Project Editor",
|
|
1141
|
+
"A11Y_PARENTAL_GATE_DIALOG": "Parent approval",
|
|
1142
|
+
"A11Y_PLAY": "Play",
|
|
1143
|
+
"A11Y_PREVIOUS": "Previous",
|
|
1144
|
+
"A11Y_PROJECT_INFO": "Project information",
|
|
1145
|
+
"A11Y_PROJECT_INFO_DIALOG": "Project information",
|
|
1146
|
+
"A11Y_READ_ALOUD": "Read aloud",
|
|
1147
|
+
"A11Y_RECORD": "Record",
|
|
1148
|
+
"A11Y_RECORD_DIALOG": "Record sound",
|
|
1149
|
+
"A11Y_RESET_ALL_CHARACTERS": "Reset all characters",
|
|
1150
|
+
"A11Y_RUN_PROJECT": "Run project",
|
|
1151
|
+
"A11Y_SKIP_TO_MAIN": "Skip to main content",
|
|
1152
|
+
"A11Y_STOP": "Stop",
|
|
1153
|
+
"A11Y_TRACE": "Trace",
|
|
1154
|
+
"A11Y_TUTORIAL_DIALOG": "Tutorial media",
|
|
1155
|
+
"A11Y_ADD_PAGE": "Add page",
|
|
1156
|
+
"A11Y_COPY_TO_PAGE": "Copy to page {PAGE}",
|
|
1157
|
+
"A11Y_DELETE_PAGE": "Delete page {PAGE}",
|
|
1158
|
+
"A11Y_MOVE_PAGE_EARLIER": "Move page {PAGE} earlier",
|
|
1159
|
+
"A11Y_MOVE_PAGE_LATER": "Move page {PAGE} later"
|
|
1160
|
+
}
|
|
@@ -1122,5 +1122,39 @@
|
|
|
1122
1122
|
"MARTY_JRBLOCKS5_STEP_21_TEXT": "Son olarak, sonuna sonsuza kadar tekrar eden bir blok ekleyin.",
|
|
1123
1123
|
"MARTY_JRBLOCKS5_STEP_22_TEXT": "Neredeyse bitti! Son olarak, futbol ağı koduna bir yeşil bayrak bloğu daha ekleyin",
|
|
1124
1124
|
"MARTY_JRBLOCKS5_STEP_23_TEXT": "Bunu bir <b/>başlat/durdur sayacı</b> bloğuyla tamamlayın, böylece her yeni oyuna başladığınızda puanınız sıfırlanır.",
|
|
1125
|
-
"MARTY_JRBLOCKS5_END": "Başardın! Yeşil bayrağa bas ve Marty'nin gol atıcısı ol!"
|
|
1126
|
-
|
|
1125
|
+
"MARTY_JRBLOCKS5_END": "Başardın! Yeşil bayrağa bas ve Marty'nin gol atıcısı ol!",
|
|
1126
|
+
"A11Y_APP_NAVIGATION": "App navigation",
|
|
1127
|
+
"A11Y_CLEAR_TRACE": "Clear trace",
|
|
1128
|
+
"A11Y_CLOSE": "Close",
|
|
1129
|
+
"A11Y_CONNECTION_LOST": "Connection lost",
|
|
1130
|
+
"A11Y_CONNECT": "Connect",
|
|
1131
|
+
"A11Y_CREATE": "Create",
|
|
1132
|
+
"A11Y_DELETE": "Delete",
|
|
1133
|
+
"A11Y_DISCONNECT": "Disconnect",
|
|
1134
|
+
"A11Y_GUIDE_SECTIONS": "Guide sections",
|
|
1135
|
+
"A11Y_HELP": "Help",
|
|
1136
|
+
"A11Y_MAIN_CONTENT": "Main content",
|
|
1137
|
+
"A11Y_MARTY_MODE": "Marty mode",
|
|
1138
|
+
"A11Y_NEXT": "Next",
|
|
1139
|
+
"A11Y_OPEN": "Open",
|
|
1140
|
+
"A11Y_PAGE_TITLE_EDITOR": "Project Editor",
|
|
1141
|
+
"A11Y_PARENTAL_GATE_DIALOG": "Parent approval",
|
|
1142
|
+
"A11Y_PLAY": "Play",
|
|
1143
|
+
"A11Y_PREVIOUS": "Previous",
|
|
1144
|
+
"A11Y_PROJECT_INFO": "Project information",
|
|
1145
|
+
"A11Y_PROJECT_INFO_DIALOG": "Project information",
|
|
1146
|
+
"A11Y_READ_ALOUD": "Read aloud",
|
|
1147
|
+
"A11Y_RECORD": "Record",
|
|
1148
|
+
"A11Y_RECORD_DIALOG": "Record sound",
|
|
1149
|
+
"A11Y_RESET_ALL_CHARACTERS": "Reset all characters",
|
|
1150
|
+
"A11Y_RUN_PROJECT": "Run project",
|
|
1151
|
+
"A11Y_SKIP_TO_MAIN": "Skip to main content",
|
|
1152
|
+
"A11Y_STOP": "Stop",
|
|
1153
|
+
"A11Y_TRACE": "Trace",
|
|
1154
|
+
"A11Y_TUTORIAL_DIALOG": "Tutorial media",
|
|
1155
|
+
"A11Y_ADD_PAGE": "Add page",
|
|
1156
|
+
"A11Y_COPY_TO_PAGE": "Copy to page {PAGE}",
|
|
1157
|
+
"A11Y_DELETE_PAGE": "Delete page {PAGE}",
|
|
1158
|
+
"A11Y_MOVE_PAGE_EARLIER": "Move page {PAGE} earlier",
|
|
1159
|
+
"A11Y_MOVE_PAGE_LATER": "Move page {PAGE} later"
|
|
1160
|
+
}
|
|
@@ -1122,5 +1122,39 @@
|
|
|
1122
1122
|
"MARTY_JRBLOCKS5_STEP_21_TEXT": "Нарешті, додайте блок постійного повторення в кінці.",
|
|
1123
1123
|
"MARTY_JRBLOCKS5_STEP_22_TEXT": "Майже готово! Нарешті, додайте ще один блок зеленого прапора до коду футбольної сітки",
|
|
1124
1124
|
"MARTY_JRBLOCKS5_STEP_23_TEXT": "Завершіть це блоком <b/>лічильника запуску/зупинки</b>, щоб ваш рахунок скидався щоразу, коли ви починаєте нову гру.",
|
|
1125
|
-
"MARTY_JRBLOCKS5_END": "Ви зробили це! Натисніть зелений прапорець і грайте в гру \"Гол-боут Марті\"!"
|
|
1126
|
-
|
|
1125
|
+
"MARTY_JRBLOCKS5_END": "Ви зробили це! Натисніть зелений прапорець і грайте в гру \"Гол-боут Марті\"!",
|
|
1126
|
+
"A11Y_APP_NAVIGATION": "App navigation",
|
|
1127
|
+
"A11Y_CLEAR_TRACE": "Clear trace",
|
|
1128
|
+
"A11Y_CLOSE": "Close",
|
|
1129
|
+
"A11Y_CONNECTION_LOST": "Connection lost",
|
|
1130
|
+
"A11Y_CONNECT": "Connect",
|
|
1131
|
+
"A11Y_CREATE": "Create",
|
|
1132
|
+
"A11Y_DELETE": "Delete",
|
|
1133
|
+
"A11Y_DISCONNECT": "Disconnect",
|
|
1134
|
+
"A11Y_GUIDE_SECTIONS": "Guide sections",
|
|
1135
|
+
"A11Y_HELP": "Help",
|
|
1136
|
+
"A11Y_MAIN_CONTENT": "Main content",
|
|
1137
|
+
"A11Y_MARTY_MODE": "Marty mode",
|
|
1138
|
+
"A11Y_NEXT": "Next",
|
|
1139
|
+
"A11Y_OPEN": "Open",
|
|
1140
|
+
"A11Y_PAGE_TITLE_EDITOR": "Project Editor",
|
|
1141
|
+
"A11Y_PARENTAL_GATE_DIALOG": "Parent approval",
|
|
1142
|
+
"A11Y_PLAY": "Play",
|
|
1143
|
+
"A11Y_PREVIOUS": "Previous",
|
|
1144
|
+
"A11Y_PROJECT_INFO": "Project information",
|
|
1145
|
+
"A11Y_PROJECT_INFO_DIALOG": "Project information",
|
|
1146
|
+
"A11Y_READ_ALOUD": "Read aloud",
|
|
1147
|
+
"A11Y_RECORD": "Record",
|
|
1148
|
+
"A11Y_RECORD_DIALOG": "Record sound",
|
|
1149
|
+
"A11Y_RESET_ALL_CHARACTERS": "Reset all characters",
|
|
1150
|
+
"A11Y_RUN_PROJECT": "Run project",
|
|
1151
|
+
"A11Y_SKIP_TO_MAIN": "Skip to main content",
|
|
1152
|
+
"A11Y_STOP": "Stop",
|
|
1153
|
+
"A11Y_TRACE": "Trace",
|
|
1154
|
+
"A11Y_TUTORIAL_DIALOG": "Tutorial media",
|
|
1155
|
+
"A11Y_ADD_PAGE": "Add page",
|
|
1156
|
+
"A11Y_COPY_TO_PAGE": "Copy to page {PAGE}",
|
|
1157
|
+
"A11Y_DELETE_PAGE": "Delete page {PAGE}",
|
|
1158
|
+
"A11Y_MOVE_PAGE_EARLIER": "Move page {PAGE} earlier",
|
|
1159
|
+
"A11Y_MOVE_PAGE_LATER": "Move page {PAGE} later"
|
|
1160
|
+
}
|
|
@@ -1122,5 +1122,39 @@
|
|
|
1122
1122
|
"MARTY_JRBLOCKS5_STEP_21_TEXT": "最后,在末尾添加一个无限重复模块。",
|
|
1123
1123
|
"MARTY_JRBLOCKS5_STEP_22_TEXT": "快完成了!最后,在足球网代码中添加另一个绿色旗帜模块。",
|
|
1124
1124
|
"MARTY_JRBLOCKS5_STEP_23_TEXT": "最后添加一个<b/>开始/停止计数器</b>模块,这样每次开始新游戏时,你的分数都会重置。",
|
|
1125
|
-
"MARTY_JRBLOCKS5_END": "你成功了!按下绿旗,开始玩马蒂的射门游戏吧!"
|
|
1126
|
-
|
|
1125
|
+
"MARTY_JRBLOCKS5_END": "你成功了!按下绿旗,开始玩马蒂的射门游戏吧!",
|
|
1126
|
+
"A11Y_APP_NAVIGATION": "App navigation",
|
|
1127
|
+
"A11Y_CLEAR_TRACE": "Clear trace",
|
|
1128
|
+
"A11Y_CLOSE": "Close",
|
|
1129
|
+
"A11Y_CONNECTION_LOST": "Connection lost",
|
|
1130
|
+
"A11Y_CONNECT": "Connect",
|
|
1131
|
+
"A11Y_CREATE": "Create",
|
|
1132
|
+
"A11Y_DELETE": "Delete",
|
|
1133
|
+
"A11Y_DISCONNECT": "Disconnect",
|
|
1134
|
+
"A11Y_GUIDE_SECTIONS": "Guide sections",
|
|
1135
|
+
"A11Y_HELP": "Help",
|
|
1136
|
+
"A11Y_MAIN_CONTENT": "Main content",
|
|
1137
|
+
"A11Y_MARTY_MODE": "Marty mode",
|
|
1138
|
+
"A11Y_NEXT": "Next",
|
|
1139
|
+
"A11Y_OPEN": "Open",
|
|
1140
|
+
"A11Y_PAGE_TITLE_EDITOR": "Project Editor",
|
|
1141
|
+
"A11Y_PARENTAL_GATE_DIALOG": "Parent approval",
|
|
1142
|
+
"A11Y_PLAY": "Play",
|
|
1143
|
+
"A11Y_PREVIOUS": "Previous",
|
|
1144
|
+
"A11Y_PROJECT_INFO": "Project information",
|
|
1145
|
+
"A11Y_PROJECT_INFO_DIALOG": "Project information",
|
|
1146
|
+
"A11Y_READ_ALOUD": "Read aloud",
|
|
1147
|
+
"A11Y_RECORD": "Record",
|
|
1148
|
+
"A11Y_RECORD_DIALOG": "Record sound",
|
|
1149
|
+
"A11Y_RESET_ALL_CHARACTERS": "Reset all characters",
|
|
1150
|
+
"A11Y_RUN_PROJECT": "Run project",
|
|
1151
|
+
"A11Y_SKIP_TO_MAIN": "Skip to main content",
|
|
1152
|
+
"A11Y_STOP": "Stop",
|
|
1153
|
+
"A11Y_TRACE": "Trace",
|
|
1154
|
+
"A11Y_TUTORIAL_DIALOG": "Tutorial media",
|
|
1155
|
+
"A11Y_ADD_PAGE": "Add page",
|
|
1156
|
+
"A11Y_COPY_TO_PAGE": "Copy to page {PAGE}",
|
|
1157
|
+
"A11Y_DELETE_PAGE": "Delete page {PAGE}",
|
|
1158
|
+
"A11Y_MOVE_PAGE_EARLIER": "Move page {PAGE} earlier",
|
|
1159
|
+
"A11Y_MOVE_PAGE_LATER": "Move page {PAGE} later"
|
|
1160
|
+
}
|
|
@@ -1122,5 +1122,39 @@
|
|
|
1122
1122
|
"MARTY_JRBLOCKS5_STEP_21_TEXT": "最後,在末尾添加一個無限重複模組。",
|
|
1123
1123
|
"MARTY_JRBLOCKS5_STEP_22_TEXT": "快完成了!最後,在足球網代碼中加入另一個綠色旗幟模組。",
|
|
1124
1124
|
"MARTY_JRBLOCKS5_STEP_23_TEXT": "最後增加一個<b/>開始/停止計數器</b>模組,這樣每次開始新遊戲時,你的分數都會重置。",
|
|
1125
|
-
"MARTY_JRBLOCKS5_END": "你成功了!按下綠旗,開始玩馬蒂的射門遊戲吧!"
|
|
1126
|
-
|
|
1125
|
+
"MARTY_JRBLOCKS5_END": "你成功了!按下綠旗,開始玩馬蒂的射門遊戲吧!",
|
|
1126
|
+
"A11Y_APP_NAVIGATION": "App navigation",
|
|
1127
|
+
"A11Y_CLEAR_TRACE": "Clear trace",
|
|
1128
|
+
"A11Y_CLOSE": "Close",
|
|
1129
|
+
"A11Y_CONNECTION_LOST": "Connection lost",
|
|
1130
|
+
"A11Y_CONNECT": "Connect",
|
|
1131
|
+
"A11Y_CREATE": "Create",
|
|
1132
|
+
"A11Y_DELETE": "Delete",
|
|
1133
|
+
"A11Y_DISCONNECT": "Disconnect",
|
|
1134
|
+
"A11Y_GUIDE_SECTIONS": "Guide sections",
|
|
1135
|
+
"A11Y_HELP": "Help",
|
|
1136
|
+
"A11Y_MAIN_CONTENT": "Main content",
|
|
1137
|
+
"A11Y_MARTY_MODE": "Marty mode",
|
|
1138
|
+
"A11Y_NEXT": "Next",
|
|
1139
|
+
"A11Y_OPEN": "Open",
|
|
1140
|
+
"A11Y_PAGE_TITLE_EDITOR": "Project Editor",
|
|
1141
|
+
"A11Y_PARENTAL_GATE_DIALOG": "Parent approval",
|
|
1142
|
+
"A11Y_PLAY": "Play",
|
|
1143
|
+
"A11Y_PREVIOUS": "Previous",
|
|
1144
|
+
"A11Y_PROJECT_INFO": "Project information",
|
|
1145
|
+
"A11Y_PROJECT_INFO_DIALOG": "Project information",
|
|
1146
|
+
"A11Y_READ_ALOUD": "Read aloud",
|
|
1147
|
+
"A11Y_RECORD": "Record",
|
|
1148
|
+
"A11Y_RECORD_DIALOG": "Record sound",
|
|
1149
|
+
"A11Y_RESET_ALL_CHARACTERS": "Reset all characters",
|
|
1150
|
+
"A11Y_RUN_PROJECT": "Run project",
|
|
1151
|
+
"A11Y_SKIP_TO_MAIN": "Skip to main content",
|
|
1152
|
+
"A11Y_STOP": "Stop",
|
|
1153
|
+
"A11Y_TRACE": "Trace",
|
|
1154
|
+
"A11Y_TUTORIAL_DIALOG": "Tutorial media",
|
|
1155
|
+
"A11Y_ADD_PAGE": "Add page",
|
|
1156
|
+
"A11Y_COPY_TO_PAGE": "Copy to page {PAGE}",
|
|
1157
|
+
"A11Y_DELETE_PAGE": "Delete page {PAGE}",
|
|
1158
|
+
"A11Y_MOVE_PAGE_EARLIER": "Move page {PAGE} earlier",
|
|
1159
|
+
"A11Y_MOVE_PAGE_LATER": "Move page {PAGE} later"
|
|
1160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robotical/martyblocksjr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "ScratchJr",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"build-prod:copy": "npm run build && cp -f src/build/bundles/* editions/free/src",
|
|
18
18
|
"build:start": "npm run build:copy && npm start",
|
|
19
19
|
"test": "vitest",
|
|
20
|
-
"test:e2e": "vitest run tests/e2e"
|
|
20
|
+
"test:e2e": "vitest run tests/e2e",
|
|
21
|
+
"a11y:structure": "node scripts/a11y-structure.mjs",
|
|
22
|
+
"test:e2e-accessibility": "vitest run tests/e2e/accessibility.e2e.test.js",
|
|
23
|
+
"a11y:ci": "npm run a11y:structure && npm run test:e2e-accessibility"
|
|
21
24
|
},
|
|
22
25
|
"author": "Massachusetts Institute of Technology",
|
|
23
26
|
"license": "BSD-3-Clause",
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@babel/polyfill": "^7.10.4",
|
|
30
33
|
"@babel/preset-env": "^7.25.4",
|
|
34
|
+
"axe-core": "^4.11.2",
|
|
31
35
|
"babel-core": "^6.4.0",
|
|
32
36
|
"babel-eslint": "^4.1.6",
|
|
33
37
|
"babel-loader": "^8.2.5",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
6
|
+
const REPORT_PATH = path.join(ROOT, 'reports', 'a11y', 'structure.json');
|
|
7
|
+
|
|
8
|
+
function read(relativePath) {
|
|
9
|
+
return fs.readFileSync(path.join(ROOT, relativePath), 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function countMatches(source, regex) {
|
|
13
|
+
const matches = source.match(regex);
|
|
14
|
+
return matches ? matches.length : 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const findings = [];
|
|
18
|
+
|
|
19
|
+
function addFinding(file, message) {
|
|
20
|
+
findings.push({ file, message });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function requireMatch(file, regex, message) {
|
|
24
|
+
if (!regex.test(read(file))) {
|
|
25
|
+
addFinding(file, message);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function forbidMatch(file, regex, message) {
|
|
30
|
+
if (regex.test(read(file))) {
|
|
31
|
+
addFinding(file, message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requireButtonIds(file, ids) {
|
|
36
|
+
const source = read(file);
|
|
37
|
+
ids.forEach((id) => {
|
|
38
|
+
if (!new RegExp(`<button[^>]*id="${id}"`, 's').test(source)) {
|
|
39
|
+
addFinding(file, `Expected \`${id}\` to be implemented as a <button>.`);
|
|
40
|
+
}
|
|
41
|
+
if (new RegExp(`<div[^>]*id="${id}"`, 's').test(source)) {
|
|
42
|
+
addFinding(file, `Found legacy <div> markup for \`${id}\`.`);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function requireButtonClassCount(file, className, expectedCount) {
|
|
48
|
+
const source = read(file);
|
|
49
|
+
const buttonCount = countMatches(source, new RegExp(`<button[^>]*class="[^"]*${className}[^"]*"`, 'g'));
|
|
50
|
+
const legacyDivCount = countMatches(source, new RegExp(`<div[^>]*class="[^"]*${className}[^"]*"`, 'g'));
|
|
51
|
+
|
|
52
|
+
if (buttonCount !== expectedCount) {
|
|
53
|
+
addFinding(file, `Expected ${expectedCount} \`${className}\` buttons but found ${buttonCount}.`);
|
|
54
|
+
}
|
|
55
|
+
if (legacyDivCount > 0) {
|
|
56
|
+
addFinding(file, `Found ${legacyDivCount} legacy <div> elements with \`${className}\`.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function requireDecorativeAlt(file, className) {
|
|
61
|
+
const source = read(file);
|
|
62
|
+
const matches = source.match(new RegExp(`<img[^>]*class="[^"]*${className}[^"]*"[^>]*>`, 'g')) || [];
|
|
63
|
+
if (matches.length === 0) {
|
|
64
|
+
addFinding(file, `Expected at least one \`${className}\` image.`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
matches.forEach((match) => {
|
|
68
|
+
if (!/alt=""/.test(match)) {
|
|
69
|
+
addFinding(file, `Expected \`${className}\` images to use decorative alt text.`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function requireFocusRecovery(file) {
|
|
75
|
+
const source = read(file);
|
|
76
|
+
if (/(outline\s*:\s*(none|0))/i.test(source) && !/:focus-visible|:focus\b/i.test(source)) {
|
|
77
|
+
addFinding(file, 'Found outline suppression without a replacement focus rule.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
requireMatch(
|
|
82
|
+
'src/utils/accessibility.js',
|
|
83
|
+
/export function ensureSkipLink[\s\S]*export function registerDialog[\s\S]*export function openDialog[\s\S]*export function closeDialog/,
|
|
84
|
+
'Expected shared skip-link and dialog accessibility helpers.'
|
|
85
|
+
);
|
|
86
|
+
requireMatch(
|
|
87
|
+
'src/entry/app.js',
|
|
88
|
+
/setDocumentLanguage\(Localization\.currentLocale\)[\s\S]*setLocalizedDocumentTitle\(titleKey\)[\s\S]*ensureSkipLink\(skipLinkTargetId, Localization\.localize\('A11Y_SKIP_TO_MAIN'\)\)/,
|
|
89
|
+
'Expected app bootstrap to set language, title, and skip link.'
|
|
90
|
+
);
|
|
91
|
+
requireMatch(
|
|
92
|
+
'src/lobby/Lobby.js',
|
|
93
|
+
/iframe\.setAttribute\('title', Lobby\.getGuideFrameTitle\(url\)\)/,
|
|
94
|
+
'Expected lobby guide iframe titles to be localized.'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
requireButtonIds('editions/free/src/home.html', [
|
|
98
|
+
'logotab',
|
|
99
|
+
'hometab',
|
|
100
|
+
'geartab',
|
|
101
|
+
'booktab',
|
|
102
|
+
'tabicon',
|
|
103
|
+
'abouttab',
|
|
104
|
+
'interfacetab',
|
|
105
|
+
'painttab',
|
|
106
|
+
'blockstab',
|
|
107
|
+
'privacytab'
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
requireButtonClassCount('editions/free/src/inapp/interface.html', 'interface-button', 16);
|
|
111
|
+
requireButtonClassCount('editions/free/src/inapp/paint.html', 'paint-button', 13);
|
|
112
|
+
requireDecorativeAlt('editions/free/src/inapp/interface.html', 'ipad-project-view');
|
|
113
|
+
requireDecorativeAlt('editions/free/src/inapp/paint.html', 'ipad-project-view');
|
|
114
|
+
requireDecorativeAlt('editions/free/src/inapp/blocks.html', 'block-image');
|
|
115
|
+
|
|
116
|
+
requireMatch(
|
|
117
|
+
'src/editor/ui/UI.js',
|
|
118
|
+
/newButton\('flipme'[\s\S]*newButton\('addsprite'[\s\S]*newButton\('addText'[\s\S]*newButton\('changeBkg'/,
|
|
119
|
+
'Expected the remaining editor shell controls to use semantic buttons.'
|
|
120
|
+
);
|
|
121
|
+
forbidMatch(
|
|
122
|
+
'src/editor/ui/UI.js',
|
|
123
|
+
/newHTML\('div', 'flipme'|newHTML\('div', 'addsprite'|newHTML\('div', 'addText'|newHTML\('div', 'changeBkg'/,
|
|
124
|
+
'Found legacy editor shell controls still created as clickable divs.'
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
requireMatch(
|
|
128
|
+
'src/editor/ui/Record.js',
|
|
129
|
+
/registerDialog\(modal[\s\S]*openDialog\(gn\('recorddialog'\)\)[\s\S]*closeDialog\(gn\('recorddialog'\)\)/,
|
|
130
|
+
'Expected the recording overlay to be wired through the shared dialog helper.'
|
|
131
|
+
);
|
|
132
|
+
requireMatch(
|
|
133
|
+
'src/editor/ui/TutorialUI.js',
|
|
134
|
+
/registerDialog\(TutorialUI\.modal[\s\S]*openDialog\(TutorialUI\.modal\)[\s\S]*closeDialog\(TutorialUI\.modal\)/,
|
|
135
|
+
'Expected the tutorial modal to be wired through the shared dialog helper.'
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
requireMatch('editions/free/src/css/base.css', /\.skip-link/, 'Expected global skip-link styles.');
|
|
139
|
+
requireMatch('editions/free/src/css/base.css', /:focus-visible/, 'Expected global visible focus styles.');
|
|
140
|
+
requireMatch('editions/free/src/css/connection-modal.css', /:focus-visible/, 'Expected visible focus styles for connection buttons.');
|
|
141
|
+
requireMatch('editions/free/src/css/editorstage.css', /\.edittext:focus-visible/, 'Expected visible focus styles for editor text input.');
|
|
142
|
+
requireMatch('editions/free/src/css/editor.css', /\.textinput:focus/, 'Expected visible focus styles for the info dialog input.');
|
|
143
|
+
|
|
144
|
+
[
|
|
145
|
+
'editions/free/src/css/base.css',
|
|
146
|
+
'editions/free/src/css/connection-modal.css',
|
|
147
|
+
'editions/free/src/css/editor.css',
|
|
148
|
+
'editions/free/src/css/editorstage.css',
|
|
149
|
+
'editions/free/src/css/lobby.css',
|
|
150
|
+
'editions/free/src/css/thumbs.css',
|
|
151
|
+
'editions/free/src/css/tutorial.css',
|
|
152
|
+
'editions/free/src/inapp/style/style.css',
|
|
153
|
+
'editions/free/src/inapp/style/interface.css',
|
|
154
|
+
'editions/free/src/inapp/style/paint.css'
|
|
155
|
+
].forEach(requireFocusRecovery);
|
|
156
|
+
|
|
157
|
+
const report = {
|
|
158
|
+
checkedAt: new Date().toISOString(),
|
|
159
|
+
summary: {
|
|
160
|
+
filesChecked: 15,
|
|
161
|
+
findingCount: findings.length
|
|
162
|
+
},
|
|
163
|
+
findings
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
fs.mkdirSync(path.dirname(REPORT_PATH), { recursive: true });
|
|
167
|
+
fs.writeFileSync(REPORT_PATH, JSON.stringify(report, null, 2) + '\n');
|
|
168
|
+
|
|
169
|
+
if (findings.length > 0) {
|
|
170
|
+
console.error(`Accessibility structure scan found ${findings.length} issue(s).`);
|
|
171
|
+
findings.forEach((finding) => {
|
|
172
|
+
console.error(`- ${finding.file}: ${finding.message}`);
|
|
173
|
+
});
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(`Accessibility structure scan passed. Report written to ${path.relative(ROOT, REPORT_PATH)}.`);
|