diff --git a/packages/spec-dashboard/src/apis.test.ts b/packages/spec-dashboard/src/apis.test.ts index 1c3c799ae5b..ed07f3ed7fb 100644 --- a/packages/spec-dashboard/src/apis.test.ts +++ b/packages/spec-dashboard/src/apis.test.ts @@ -286,4 +286,70 @@ describe("splitManifestByTables", () => { const dataPlaneTable = result.find((r) => r.tableName === "Data Plane Table"); expect(dataPlaneTable!.emitterNames).toEqual(["@azure-tools/typespec-csharp"]); }); + + it("should preserve order of tableDefinitions in the result", () => { + const manifest = createManifest("test-package", "Display Name", [ + "prefix1_scenario1", + "prefix2_scenario1", + "prefix3_scenario1", + ]); + const tables: TableDefinition[] = [ + { + name: "Third Table", + packageName: "test-package", + prefixes: ["prefix3_"], + }, + { + name: "First Table", + packageName: "test-package", + prefixes: ["prefix1_"], + }, + { + name: "Second Table", + packageName: "test-package", + prefixes: ["prefix2_"], + }, + ]; + + const result = splitManifestByTables(manifest, tables); + + expect(result).toHaveLength(3); + // Results should be in the same order as tableDefinitions + expect(result[0].tableName).toBe("Third Table"); + expect(result[1].tableName).toBe("First Table"); + expect(result[2].tableName).toBe("Second Table"); + }); + + it("should preserve order when mixing tables with prefixes and catch-all tables", () => { + const manifest = createManifest("test-package", "Display Name", [ + "prefix1_scenario1", + "prefix2_scenario1", + "other_scenario", + ]); + const tables: TableDefinition[] = [ + { + name: "Second Table (Prefix2)", + packageName: "test-package", + prefixes: ["prefix2_"], + }, + { + name: "First Table (Catch-All)", + packageName: "test-package", + // No prefixes - catch-all table + }, + { + name: "Third Table (Prefix1)", + packageName: "test-package", + prefixes: ["prefix1_"], + }, + ]; + + const result = splitManifestByTables(manifest, tables); + + expect(result).toHaveLength(3); + // Results should be in the same order as tableDefinitions, not grouped by type + expect(result[0].tableName).toBe("Second Table (Prefix2)"); + expect(result[1].tableName).toBe("First Table (Catch-All)"); + expect(result[2].tableName).toBe("Third Table (Prefix1)"); + }); }); diff --git a/packages/spec-dashboard/src/apis.ts b/packages/spec-dashboard/src/apis.ts index 9f8ced79cc9..d367352ef59 100644 --- a/packages/spec-dashboard/src/apis.ts +++ b/packages/spec-dashboard/src/apis.ts @@ -88,49 +88,46 @@ export function splitManifestByTables( []; const usedScenarios = new Set(); - // Separate tables with prefixes from catch-all tables (no prefixes) - const tablesWithPrefixes = applicableTables.filter((t) => t.prefixes && t.prefixes.length > 0); - const catchAllTables = applicableTables.filter((t) => !t.prefixes || t.prefixes.length === 0); - - // Process tables with prefixes first - for (const table of tablesWithPrefixes) { - // Filter scenarios by prefixes - const filteredScenarios = manifest.scenarios.filter((s: ScenarioData) => { - if (usedScenarios.has(s.name)) { - return false; // Already assigned to another table + // First, identify which scenarios would match ANY prefix table (to reserve them from catch-all tables) + const scenariosMatchingAnyPrefix = new Set(); + for (const table of applicableTables) { + if (table.prefixes && table.prefixes.length > 0) { + for (const scenario of manifest.scenarios) { + if (matchesPrefixes(scenario.name, table.prefixes)) { + scenariosMatchingAnyPrefix.add(scenario.name); + } } - return matchesPrefixes(s.name, table.prefixes!); - }); + } + } - if (filteredScenarios.length > 0) { - // Mark these scenarios as used - filteredScenarios.forEach((s) => usedScenarios.add(s.name)); + // Now process tables in the order they appear in tableDefinitions + for (const table of applicableTables) { + const isCatchAll = !table.prefixes || table.prefixes.length === 0; - result.push({ - manifest: { - ...manifest, - scenarios: filteredScenarios, - }, - tableName: table.name, - emitterNames: table.emitterNames, + let matchingScenarios: ScenarioData[]; + if (isCatchAll) { + // Catch-all table: only get scenarios not yet assigned AND not matching any prefix + matchingScenarios = manifest.scenarios.filter( + (s: ScenarioData) => !usedScenarios.has(s.name) && !scenariosMatchingAnyPrefix.has(s.name), + ); + } else { + // Table with prefixes: filter scenarios by prefixes + matchingScenarios = manifest.scenarios.filter((s: ScenarioData) => { + if (usedScenarios.has(s.name)) { + return false; // Already assigned to another table + } + return matchesPrefixes(s.name, table.prefixes!); }); } - } - - // Process catch-all tables - they only get scenarios not yet assigned - for (const table of catchAllTables) { - const remainingScenarios = manifest.scenarios.filter( - (s: ScenarioData) => !usedScenarios.has(s.name), - ); - if (remainingScenarios.length > 0) { + if (matchingScenarios.length > 0) { // Mark these scenarios as used - remainingScenarios.forEach((s) => usedScenarios.add(s.name)); + matchingScenarios.forEach((s) => usedScenarios.add(s.name)); result.push({ manifest: { ...manifest, - scenarios: remainingScenarios, + scenarios: matchingScenarios, }, tableName: table.name, emitterNames: table.emitterNames,