Skip to content

Commit 91e2236

Browse files
committed
Correct Audio SFX schema location and reject duplicate names - PR_26145_001-correct-audio-sfx-schema-location-and-unique-names
1 parent bf7ceac commit 91e2236

4 files changed

Lines changed: 171 additions & 9 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Audio / SFX Playground V2 Schema Location and Unique Names Validation
2+
3+
PR: `PR_26145_001-correct-audio-sfx-schema-location-and-unique-names`
4+
5+
Playwright impacted: Yes.
6+
7+
## Scope
8+
9+
- Moved the Audio / SFX Playground V2 schema to `tools/schemas/tools/audio-sfx-playground-v2.schema.json`.
10+
- Removed the old `tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json` path.
11+
- Updated Audio / SFX Playground V2 JSON import/export/copy schema references to the corrected tools schema path.
12+
- Added duplicate SFX name rejection for add, rename, export, copy, and import flows.
13+
14+
## Targeted Validation
15+
16+
PASS: JavaScript syntax validation
17+
18+
Command:
19+
20+
```powershell
21+
Get-ChildItem -Recurse -File tools/audio-sfx-playground-v2/js -Filter *.js | ForEach-Object { node --check $_.FullName }
22+
```
23+
24+
PASS: Moved schema JSON parse validation
25+
26+
Command:
27+
28+
```powershell
29+
Get-Content -Raw tools/schemas/tools/audio-sfx-playground-v2.schema.json | ConvertFrom-Json | Out-Null
30+
```
31+
32+
PASS: Diff whitespace validation
33+
34+
Command:
35+
36+
```powershell
37+
git diff --check -- tools/audio-sfx-playground-v2 tools/schemas
38+
```
39+
40+
PASS: Stale schema reference scan
41+
42+
Command:
43+
44+
```powershell
45+
rg "tool-states|audio-sfx-playground-v2\.tool-state|TOOL_STATE_SCHEMA_PATH" tools/audio-sfx-playground-v2 tools/schemas
46+
```
47+
48+
The scan returned no matches.
49+
50+
PASS: Targeted schema/export/name behavior validation
51+
52+
Validated with an inline Node module script:
53+
54+
- New schema exists at `tools/schemas/tools/audio-sfx-playground-v2.schema.json`.
55+
- Old `tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json` path is removed.
56+
- Schema `$id` uses the corrected tools schema path.
57+
- Schema `$schema` const uses the corrected tools schema path.
58+
- Serializer export uses the corrected schema path.
59+
- Serializer rejects the old schema path.
60+
- Serializer rejects duplicate imported SFX names.
61+
- Duplicate Add shows a Status error.
62+
- Duplicate Add leaves the existing sound list unchanged.
63+
- Duplicate Rename shows a Status error.
64+
- Duplicate Rename leaves the existing sound list unchanged.
65+
- Export rejects a duplicate edited name before mutation.
66+
- Copy JSON uses the corrected schema path after restoring a unique active name.
67+
68+
## Workspace V2 Validation
69+
70+
BLOCKED: `npm run test:workspace-v2`
71+
72+
Result:
73+
74+
```text
75+
npm : File C:\Program Files\nodejs\npm.ps1 cannot be loaded because running scripts is disabled on this system.
76+
```
77+
78+
BLOCKED: `npm.cmd run test:workspace-v2`
79+
80+
Result:
81+
82+
```text
83+
'playwright' is not recognized as an internal or external command,
84+
operable program or batch file.
85+
```
86+
87+
Expected Playwright pass behavior when dependencies are available:
88+
89+
- Workspace V2 launches Audio / SFX Playground V2.
90+
- Copy JSON and Export JSON reference `tools/schemas/tools/audio-sfx-playground-v2.schema.json`.
91+
- The old `tool-states` schema path is not referenced.
92+
- Duplicate add attempts are visibly rejected.
93+
- Duplicate rename attempts are visibly rejected.
94+
- The saved sound list remains unchanged after duplicate rejection.
95+
- Invalid duplicate-name imports are rejected before render.
96+
- No console errors occur during launch, copy, export, add, rename, or invalid import rejection.
97+
98+
Expected Playwright fail behavior:
99+
100+
- Fail if Copy JSON or Export JSON references the old schema path.
101+
- Fail if a duplicate name creates or mutates a saved sound.
102+
- Fail if invalid duplicate-name imports partially render.
103+
- Fail if no visible Status error is shown for duplicate names.
104+
- Fail if console errors occur.
105+
106+
## Coverage
107+
108+
Playwright V8 coverage could not be collected because Playwright is not available in this environment.
109+
110+
WARN: `tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js` - changed runtime JavaScript; coverage unavailable.
111+
WARN: `tools/audio-sfx-playground-v2/js/services/ToolStateSerializer.js` - changed runtime JavaScript; coverage unavailable.
112+
113+
## Full Samples Smoke Test
114+
115+
Skipped. This PR only impacts Audio / SFX Playground V2 schema location and name validation.

tools/audio-sfx-playground-v2/js/AudioSfxPlaygroundV2App.js

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ function exportFileName(name) {
3131
return `${baseName || "audio-sfx"}-tool-state.json`;
3232
}
3333

34+
function duplicateSoundNameMessage(name) {
35+
return `Duplicate SFX name: ${name.trim()}.`;
36+
}
37+
38+
function normalizeSoundName(name) {
39+
return name.trim().toLowerCase();
40+
}
41+
3442
function activeSoundFromToolState(toolState) {
3543
return toolState.payload.sounds.find((entry) => entry.id === toolState.payload.activeSoundId)?.sound || null;
3644
}
@@ -114,12 +122,22 @@ export class AudioSfxPlaygroundV2App {
114122

115123
handleEditorChange() {
116124
const validation = this.controls.validate();
125+
if (validation.valid && this.activeSoundId && this.hasDuplicateSoundName(validation.value.name, this.activeSoundId)) {
126+
this.statusLog.error(duplicateSoundNameMessage(validation.value.name));
127+
this.controls.showMessage("Name must be unique.", true);
128+
return;
129+
}
117130
if (validation.valid && this.updateActiveSound(validation.value)) {
118131
this.renderSoundList();
119132
}
120133
this.refreshPreview();
121134
}
122135

136+
hasDuplicateSoundName(name, excludedSoundId = "") {
137+
const normalizedName = normalizeSoundName(name);
138+
return this.soundEntries.some((entry) => entry.id !== excludedSoundId && normalizeSoundName(entry.sound.name) === normalizedName);
139+
}
140+
123141
createSoundEntry(sound) {
124142
const entry = {
125143
id: `sfx-${this.nextSoundNumber}`,
@@ -141,7 +159,18 @@ export class AudioSfxPlaygroundV2App {
141159
}
142160

143161
ensureActiveSoundEntry(sound) {
144-
return this.updateActiveSound(sound) || this.createSoundEntry(sound);
162+
const activeEntry = this.soundEntries.find((entry) => entry.id === this.activeSoundId);
163+
const excludedSoundId = activeEntry?.id || "";
164+
if (this.hasDuplicateSoundName(sound.name, excludedSoundId)) {
165+
return {
166+
valid: false,
167+
message: duplicateSoundNameMessage(sound.name)
168+
};
169+
}
170+
return {
171+
valid: true,
172+
entry: this.updateActiveSound(sound) || this.createSoundEntry(sound)
173+
};
145174
}
146175

147176
addCurrentSound() {
@@ -151,6 +180,11 @@ export class AudioSfxPlaygroundV2App {
151180
this.refreshPreview();
152181
return;
153182
}
183+
if (this.hasDuplicateSoundName(validation.value.name)) {
184+
this.statusLog.error(duplicateSoundNameMessage(validation.value.name));
185+
this.controls.showMessage("Name must be unique.", true);
186+
return;
187+
}
154188

155189
const entry = this.createSoundEntry(validation.value);
156190
this.renderSoundList();
@@ -218,7 +252,10 @@ export class AudioSfxPlaygroundV2App {
218252
if (!validation.valid) {
219253
return { valid: false, message: validation.message, toolState: null, json: "" };
220254
}
221-
this.ensureActiveSoundEntry(validation.value);
255+
const activeSoundEntry = this.ensureActiveSoundEntry(validation.value);
256+
if (!activeSoundEntry.valid) {
257+
return { valid: false, message: activeSoundEntry.message, toolState: null, json: "" };
258+
}
222259
this.renderSoundList();
223260
const toolState = this.serializer.createToolState({
224261
activeSoundId: this.activeSoundId,

tools/audio-sfx-playground-v2/js/services/ToolStateSerializer.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const TOOL_ID = "audio-sfx-playground-v2";
22
const PAYLOAD_SCHEMA = "html-js-gaming.audio-sfx-playground-v2";
3-
const TOOL_STATE_SCHEMA_PATH = "tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json";
3+
const TOOL_SCHEMA_PATH = "tools/schemas/tools/audio-sfx-playground-v2.schema.json";
44
const ALLOWED_WAVEFORMS = Object.freeze(new Set(["sine", "square", "triangle", "sawtooth"]));
55
const ROOT_KEYS = Object.freeze(new Set(["$schema", "schema", "version", "toolId", "payload"]));
66
const PAYLOAD_KEYS = Object.freeze(new Set(["schema", "version", "toolId", "activeSoundId", "sounds"]));
@@ -21,6 +21,10 @@ function isPlainObject(value) {
2121
return typeof value === "object" && value !== null && !Array.isArray(value);
2222
}
2323

24+
function normalizeSoundName(name) {
25+
return name.trim().toLowerCase();
26+
}
27+
2428
function serializeSound(sound) {
2529
return {
2630
attackMs: sound.attackMs,
@@ -104,6 +108,7 @@ function readSoundEntries(value) {
104108
}
105109
const soundEntries = [];
106110
const ids = new Set();
111+
const names = new Set();
107112
for (let index = 0; index < value.length; index += 1) {
108113
const item = value[index];
109114
if (!isPlainObject(item)) {
@@ -123,7 +128,12 @@ function readSoundEntries(value) {
123128
if (!sound.ok) {
124129
return sound;
125130
}
131+
const normalizedName = normalizeSoundName(sound.value.name);
132+
if (names.has(normalizedName)) {
133+
return { ok: false, message: `Duplicate sound name: ${sound.value.name}.` };
134+
}
126135
ids.add(item.id);
136+
names.add(normalizedName);
127137
soundEntries.push({
128138
id: item.id,
129139
sound: sound.value
@@ -142,7 +152,7 @@ export class ToolStateSerializer {
142152
? activeSoundId
143153
: "";
144154
return {
145-
$schema: TOOL_STATE_SCHEMA_PATH,
155+
$schema: TOOL_SCHEMA_PATH,
146156
schema: "html-js-gaming.tool-state",
147157
version: 1,
148158
toolId: TOOL_ID,
@@ -164,8 +174,8 @@ export class ToolStateSerializer {
164174
if (!rootKeys.ok) {
165175
return { valid: false, message: rootKeys.message };
166176
}
167-
if (value.$schema !== TOOL_STATE_SCHEMA_PATH) {
168-
return { valid: false, message: `Imported JSON $schema must be ${TOOL_STATE_SCHEMA_PATH}.` };
177+
if (value.$schema !== TOOL_SCHEMA_PATH) {
178+
return { valid: false, message: `Imported JSON $schema must be ${TOOL_SCHEMA_PATH}.` };
169179
}
170180
if (value.schema !== "html-js-gaming.tool-state") {
171181
return { valid: false, message: "Imported JSON schema must be html-js-gaming.tool-state." };

tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json renamed to tools/schemas/tools/audio-sfx-playground-v2.schema.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
3-
"$id": "tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json",
4-
"title": "Audio / SFX Playground V2 Tool State",
3+
"$id": "tools/schemas/tools/audio-sfx-playground-v2.schema.json",
4+
"title": "Audio / SFX Playground V2 Schema",
55
"description": "Exported toolState wrapper for Audio / SFX Playground V2. Saved sounds are the single source of truth; activeSoundId identifies the selected sound.",
66
"type": "object",
77
"required": ["$schema", "schema", "version", "toolId", "payload"],
88
"additionalProperties": false,
99
"properties": {
1010
"$schema": {
1111
"type": "string",
12-
"const": "tools/schemas/tool-states/audio-sfx-playground-v2.tool-state.schema.json"
12+
"const": "tools/schemas/tools/audio-sfx-playground-v2.schema.json"
1313
},
1414
"schema": {
1515
"type": "string",

0 commit comments

Comments
 (0)