From 3b42243bdf9dc861ff335435579f0c8aaab593b1 Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Tue, 16 Jun 2026 11:58:38 -0700 Subject: [PATCH 1/5] include passing/failing scenarios for unions --- .../test/generation.test.ts | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/packages/http-server-csharp/test/generation.test.ts b/packages/http-server-csharp/test/generation.test.ts index 4e8ee720669..d00eba5bc7e 100644 --- a/packages/http-server-csharp/test/generation.test.ts +++ b/packages/http-server-csharp/test/generation.test.ts @@ -3445,3 +3445,264 @@ it("emits class for model extending another model with no additional properties" ], ); }); +// test passing +it("generates extensible enum for named union with string base and named variants", async () => { + await compileAndValidateMultiple( + tester, + ` + /** Reasoning effort level */ + union ReasoningEffort { + string, + + /** No reasoning effort. */ + none: "none", + + /** Minimal reasoning effort. */ + minimal: "minimal", + + /** Low reasoning effort - faster responses with less reasoning. */ + low: "low", + + /** Medium reasoning effort - balanced between speed and reasoning depth. */ + medium: "medium", + + /** High reasoning effort - more thorough reasoning, may take longer. */ + high: "high", + + /** Extra high reasoning effort - maximum reasoning depth. */ + xhigh: "xhigh", + } + + /** A simple test model */ + model Foo { + /** effort level */ + effort?: ReasoningEffort; + } + + @route("/foo") @get op getFoo(): Foo; + `, + [ + [ + "ReasoningEffort.cs", + [ + "public enum ReasoningEffort", + "[JsonConverter(typeof(JsonStringEnumConverter))]", + `[JsonStringEnumMemberName("none")]`, + "None,", + `[JsonStringEnumMemberName("minimal")]`, + "Minimal,", + `[JsonStringEnumMemberName("low")]`, + "Low,", + `[JsonStringEnumMemberName("medium")]`, + "Medium,", + `[JsonStringEnumMemberName("high")]`, + "High,", + `[JsonStringEnumMemberName("xhigh")]`, + "Xhigh", + ], + ], + [ + "Foo.cs", + [ + "public partial class Foo", + "ReasoningEffort", + "Effort", + ], + ], + ], + ); +}); +// test failing +it("generates non-empty model for named union of string literals with null", async () => { + await compileAndValidateMultiple( + tester, + ` + /** Reasoning effort level */ + union ReasoningEffort { + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + null, + } + + /** A simple test model */ + model Foo { + /** effort level */ + effort?: ReasoningEffort; + } + + @route("/foo") @get op getFoo(): Foo; + `, + [ + [ + "ReasoningEffort.cs", + [ + "ReasoningEffort", + ], + ], + [ + "Foo.cs", + [ + "public partial class Foo", + "Effort", + ], + ], + ], + ); +}); +// test failing +it("generates enum for named union of unnamed string literals without null", async () => { + await compileAndValidateMultiple( + tester, + ` + /** Priority level */ + union Priority { + "low", + "medium", + "high", + } + + /** A simple test model */ + model Foo { + /** priority */ + priority?: Priority; + } + + @route("/foo") @get op getFoo(): Foo; + `, + [ + [ + "Priority.cs", + [ + "Priority", + ], + ], + [ + "Foo.cs", + [ + "public partial class Foo", + "Priority", + ], + ], + ], + ); +}); +// test failing +it("generates enum for named union with named variants and null", async () => { + await compileAndValidateMultiple( + tester, + ` + /** Reasoning effort level */ + union ReasoningEffort { + /** No reasoning effort. */ + none: "none", + + /** Medium reasoning effort. */ + medium: "medium", + + /** High reasoning effort. */ + high: "high", + + /** Null value */ + null, + } + + /** A simple test model */ + model Foo { + /** effort level */ + effort?: ReasoningEffort; + } + + @route("/foo") @get op getFoo(): Foo; + `, + [ + [ + "ReasoningEffort.cs", + [ + "ReasoningEffort", + ], + ], + [ + "Foo.cs", + [ + "public partial class Foo", + "Effort", + ], + ], + ], + ); +}); +// test failing +it("generates enum for extensible union with string base, named variants, and null", async () => { + await compileAndValidateMultiple( + tester, + ` + /** Reasoning effort level */ + union ReasoningEffort { + string, + + /** No reasoning effort. */ + none: "none", + + /** Medium reasoning effort. */ + medium: "medium", + + /** High reasoning effort. */ + high: "high", + + /** Null value */ + null, + } + + /** A simple test model */ + model Foo { + /** effort level */ + effort?: ReasoningEffort; + } + + @route("/foo") @get op getFoo(): Foo; + `, + [ + [ + "ReasoningEffort.cs", + [ + "ReasoningEffort", + ], + ], + [ + "Foo.cs", + [ + "public partial class Foo", + "Effort", + ], + ], + ], + ); +}); +// test passing +it("generates nullable property for inline union of string literals with null", async () => { + await compileAndValidateMultiple( + tester, + ` + /** A simple test model */ + model Foo { + /** effort level */ + effort?: "low" | "medium" | "high" | null; + } + + @route("/foo") @get op getFoo(): Foo; + `, + [ + [ + "Foo.cs", + [ + "public partial class Foo", + "Effort", + ], + ], + ], + ); +}); From 496e67b6619b660a626574304e6f4a37267a1a13 Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Wed, 17 Jun 2026 13:56:42 -0700 Subject: [PATCH 2/5] fix union enum issue and move tests --- ...-server-union-failure-2026-5-17-14-5-13.md | 7 + .../src/components/enums/enums.test.tsx | 95 +++++++ .../src/components/enums/enums.tsx | 24 +- .../test/generation.test.ts | 261 ------------------ 4 files changed, 121 insertions(+), 266 deletions(-) create mode 100644 .chronus/changes/sramsey-csharp-server-union-failure-2026-5-17-14-5-13.md diff --git a/.chronus/changes/sramsey-csharp-server-union-failure-2026-5-17-14-5-13.md b/.chronus/changes/sramsey-csharp-server-union-failure-2026-5-17-14-5-13.md new file mode 100644 index 00000000000..9619c70081f --- /dev/null +++ b/.chronus/changes/sramsey-csharp-server-union-failure-2026-5-17-14-5-13.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-server-csharp" +--- + +Update the union definition to include unnamed string literals and null \ No newline at end of file diff --git a/packages/http-server-csharp/src/components/enums/enums.test.tsx b/packages/http-server-csharp/src/components/enums/enums.test.tsx index e0583855c93..b9f4de261fc 100644 --- a/packages/http-server-csharp/src/components/enums/enums.test.tsx +++ b/packages/http-server-csharp/src/components/enums/enums.test.tsx @@ -5,6 +5,7 @@ import { t, type TesterInstance } from "@typespec/compiler/testing"; import { Output } from "@typespec/emitter-framework"; import { EnumDeclaration } from "@typespec/emitter-framework/csharp"; import { beforeEach, describe, expect, it } from "vitest"; +import { isUnionEnum } from "./enums.jsx"; let runner: TesterInstance; @@ -97,3 +98,97 @@ describe("EnumDeclaration", () => { `); }); }); + +describe("isUnionEnum", () => { + it("returns true for extensible union with string base and named variants", async () => { + const { ReasoningEffort } = await runner.compile(t.code` + union ${t.union("ReasoningEffort")} { + string, + none: "none", + low: "low", + medium: "medium", + high: "high", + } + `); + + expect(isUnionEnum(ReasoningEffort)).toBe(true); + }); + + it("returns true for fixed union with named variants only", async () => { + const { Priority } = await runner.compile(t.code` + union ${t.union("Priority")} { + low: "low", + medium: "medium", + high: "high", + } + `); + + expect(isUnionEnum(Priority)).toBe(true); + }); + + it("returns true for union with unnamed string literals", async () => { + const { Priority } = await runner.compile(t.code` + union ${t.union("Priority")} { + "low", + "medium", + "high", + } + `); + + expect(isUnionEnum(Priority)).toBe(true); + }); + + it("returns true for union with unnamed string literals and null", async () => { + const { ReasoningEffort } = await runner.compile(t.code` + union ${t.union("ReasoningEffort")} { + "none", + "minimal", + "low", + "medium", + "high", + null, + } + `); + + expect(isUnionEnum(ReasoningEffort)).toBe(true); + }); + + it("returns true for union with named variants and null", async () => { + const { ReasoningEffort } = await runner.compile(t.code` + union ${t.union("ReasoningEffort")} { + none: "none", + medium: "medium", + high: "high", + null, + } + `); + + expect(isUnionEnum(ReasoningEffort)).toBe(true); + }); + + it("returns true for extensible union with string base, named variants, and null", async () => { + const { ReasoningEffort } = await runner.compile(t.code` + union ${t.union("ReasoningEffort")} { + string, + none: "none", + medium: "medium", + high: "high", + null, + } + `); + + expect(isUnionEnum(ReasoningEffort)).toBe(true); + }); + + it("returns false for union with non-string variant types", async () => { + const { Mixed } = await runner.compile(t.code` + model Foo { x: string; } + union ${t.union("Mixed")} { + Foo, + "bar", + } + `); + + expect(isUnionEnum(Mixed)).toBe(false); + }); +}); diff --git a/packages/http-server-csharp/src/components/enums/enums.tsx b/packages/http-server-csharp/src/components/enums/enums.tsx index 9f3be70673b..b3350b08042 100644 --- a/packages/http-server-csharp/src/components/enums/enums.tsx +++ b/packages/http-server-csharp/src/components/enums/enums.tsx @@ -136,32 +136,42 @@ export function Enums(props: EnumsProps): Children { /** * Returns true if a named union can be represented as a C# enum. * Requires: named union, every named variant has a string value, - * and optionally one unnamed scalar `string` variant (open/extensible). + * and optionally one unnamed scalar `string` variant (open/extensible) + * and/or a `null` variant. */ export function isUnionEnum(union: Union): boolean { if (!union.name) return false; const variants = Array.from(union.variants.values()); - let hasNamedStringVariant = false; + let hasStringVariant = false; for (const variant of variants) { // Allow a single open string scalar variant (extensible union) if (variant.type.kind === "Scalar" && variant.type.name === "string") { continue; } + // Allow null variant (nullable union) + if (variant.type.kind === "Intrinsic" && variant.type.name === "null") { + continue; + } // Named variant with a string literal value if (variant.type.kind === "String" && variant.name && typeof variant.name === "string") { - hasNamedStringVariant = true; + hasStringVariant = true; + continue; + } + // Unnamed variant with a string literal value (e.g., union { "low", "medium", "high" }) + if (variant.type.kind === "String" && typeof variant.name === "symbol") { + hasStringVariant = true; continue; } // Any other variant type means it's not a simple enum return false; } - return hasNamedStringVariant; + return hasStringVariant; } -/** Gets the named string variants of a union-as-enum (skipping the open `string` variant). */ +/** Gets the named string variants of a union-as-enum (skipping the open `string` and `null` variants). */ export function getUnionEnumMembers( union: Union, ): { name: string; value: string; variant: import("@typespec/compiler").UnionVariant }[] { @@ -172,7 +182,11 @@ export function getUnionEnumMembers( }[] = []; for (const variant of union.variants.values()) { if (variant.type.kind === "String" && variant.name && typeof variant.name === "string") { + // Named variant with explicit key (e.g., none: "none") members.push({ name: variant.name, value: variant.type.value, variant }); + } else if (variant.type.kind === "String" && typeof variant.name === "symbol") { + // Unnamed string literal variant (e.g., "none") — derive name from the value + members.push({ name: variant.type.value, value: variant.type.value, variant }); } } return members; diff --git a/packages/http-server-csharp/test/generation.test.ts b/packages/http-server-csharp/test/generation.test.ts index d00eba5bc7e..4e8ee720669 100644 --- a/packages/http-server-csharp/test/generation.test.ts +++ b/packages/http-server-csharp/test/generation.test.ts @@ -3445,264 +3445,3 @@ it("emits class for model extending another model with no additional properties" ], ); }); -// test passing -it("generates extensible enum for named union with string base and named variants", async () => { - await compileAndValidateMultiple( - tester, - ` - /** Reasoning effort level */ - union ReasoningEffort { - string, - - /** No reasoning effort. */ - none: "none", - - /** Minimal reasoning effort. */ - minimal: "minimal", - - /** Low reasoning effort - faster responses with less reasoning. */ - low: "low", - - /** Medium reasoning effort - balanced between speed and reasoning depth. */ - medium: "medium", - - /** High reasoning effort - more thorough reasoning, may take longer. */ - high: "high", - - /** Extra high reasoning effort - maximum reasoning depth. */ - xhigh: "xhigh", - } - - /** A simple test model */ - model Foo { - /** effort level */ - effort?: ReasoningEffort; - } - - @route("/foo") @get op getFoo(): Foo; - `, - [ - [ - "ReasoningEffort.cs", - [ - "public enum ReasoningEffort", - "[JsonConverter(typeof(JsonStringEnumConverter))]", - `[JsonStringEnumMemberName("none")]`, - "None,", - `[JsonStringEnumMemberName("minimal")]`, - "Minimal,", - `[JsonStringEnumMemberName("low")]`, - "Low,", - `[JsonStringEnumMemberName("medium")]`, - "Medium,", - `[JsonStringEnumMemberName("high")]`, - "High,", - `[JsonStringEnumMemberName("xhigh")]`, - "Xhigh", - ], - ], - [ - "Foo.cs", - [ - "public partial class Foo", - "ReasoningEffort", - "Effort", - ], - ], - ], - ); -}); -// test failing -it("generates non-empty model for named union of string literals with null", async () => { - await compileAndValidateMultiple( - tester, - ` - /** Reasoning effort level */ - union ReasoningEffort { - "none", - "minimal", - "low", - "medium", - "high", - "xhigh", - null, - } - - /** A simple test model */ - model Foo { - /** effort level */ - effort?: ReasoningEffort; - } - - @route("/foo") @get op getFoo(): Foo; - `, - [ - [ - "ReasoningEffort.cs", - [ - "ReasoningEffort", - ], - ], - [ - "Foo.cs", - [ - "public partial class Foo", - "Effort", - ], - ], - ], - ); -}); -// test failing -it("generates enum for named union of unnamed string literals without null", async () => { - await compileAndValidateMultiple( - tester, - ` - /** Priority level */ - union Priority { - "low", - "medium", - "high", - } - - /** A simple test model */ - model Foo { - /** priority */ - priority?: Priority; - } - - @route("/foo") @get op getFoo(): Foo; - `, - [ - [ - "Priority.cs", - [ - "Priority", - ], - ], - [ - "Foo.cs", - [ - "public partial class Foo", - "Priority", - ], - ], - ], - ); -}); -// test failing -it("generates enum for named union with named variants and null", async () => { - await compileAndValidateMultiple( - tester, - ` - /** Reasoning effort level */ - union ReasoningEffort { - /** No reasoning effort. */ - none: "none", - - /** Medium reasoning effort. */ - medium: "medium", - - /** High reasoning effort. */ - high: "high", - - /** Null value */ - null, - } - - /** A simple test model */ - model Foo { - /** effort level */ - effort?: ReasoningEffort; - } - - @route("/foo") @get op getFoo(): Foo; - `, - [ - [ - "ReasoningEffort.cs", - [ - "ReasoningEffort", - ], - ], - [ - "Foo.cs", - [ - "public partial class Foo", - "Effort", - ], - ], - ], - ); -}); -// test failing -it("generates enum for extensible union with string base, named variants, and null", async () => { - await compileAndValidateMultiple( - tester, - ` - /** Reasoning effort level */ - union ReasoningEffort { - string, - - /** No reasoning effort. */ - none: "none", - - /** Medium reasoning effort. */ - medium: "medium", - - /** High reasoning effort. */ - high: "high", - - /** Null value */ - null, - } - - /** A simple test model */ - model Foo { - /** effort level */ - effort?: ReasoningEffort; - } - - @route("/foo") @get op getFoo(): Foo; - `, - [ - [ - "ReasoningEffort.cs", - [ - "ReasoningEffort", - ], - ], - [ - "Foo.cs", - [ - "public partial class Foo", - "Effort", - ], - ], - ], - ); -}); -// test passing -it("generates nullable property for inline union of string literals with null", async () => { - await compileAndValidateMultiple( - tester, - ` - /** A simple test model */ - model Foo { - /** effort level */ - effort?: "low" | "medium" | "high" | null; - } - - @route("/foo") @get op getFoo(): Foo; - `, - [ - [ - "Foo.cs", - [ - "public partial class Foo", - "Effort", - ], - ], - ], - ); -}); From 11bb9075f8f859cb5e9af03ce43c0a754b866459 Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Thu, 18 Jun 2026 13:06:12 -0700 Subject: [PATCH 3/5] add support for inline union variants --- .../src/components/enums/enums.test.tsx | 11 +++++++ .../src/components/enums/enums.tsx | 30 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/http-server-csharp/src/components/enums/enums.test.tsx b/packages/http-server-csharp/src/components/enums/enums.test.tsx index b9f4de261fc..0d127877932 100644 --- a/packages/http-server-csharp/src/components/enums/enums.test.tsx +++ b/packages/http-server-csharp/src/components/enums/enums.test.tsx @@ -191,4 +191,15 @@ describe("isUnionEnum", () => { expect(isUnionEnum(Mixed)).toBe(false); }); + + it("returns true for anonymous inline union of string literals", async () => { + const { ReasoningEffort } = await runner.compile(t.code` + union ${t.union("ReasoningEffort")} { + "none" | "minimal" | "low" | "medium" | "high", + null, + } + `); + + expect(isUnionEnum(ReasoningEffort)).toBe(true); + }); }); diff --git a/packages/http-server-csharp/src/components/enums/enums.tsx b/packages/http-server-csharp/src/components/enums/enums.tsx index b3350b08042..8fdcf3d9fa7 100644 --- a/packages/http-server-csharp/src/components/enums/enums.tsx +++ b/packages/http-server-csharp/src/components/enums/enums.tsx @@ -133,11 +133,23 @@ export function Enums(props: EnumsProps): Children { ); } +/** + * Returns true if an anonymous inline union contains only string literal variants. + */ +function isInlineStringLiteralUnion(type: Type): boolean { + if (type.kind !== "Union" || type.name) return false; + for (const variant of type.variants.values()) { + if (variant.type.kind !== "String") return false; + } + return type.variants.size > 0; +} + /** * Returns true if a named union can be represented as a C# enum. * Requires: named union, every named variant has a string value, * and optionally one unnamed scalar `string` variant (open/extensible) - * and/or a `null` variant. + * and/or a `null` variant. Also supports inline anonymous unions of + * string literals (e.g., `"a" | "b" | "c"` as a single variant). */ export function isUnionEnum(union: Union): boolean { if (!union.name) return false; @@ -164,6 +176,11 @@ export function isUnionEnum(union: Union): boolean { hasStringVariant = true; continue; } + // Inline anonymous union of string literals (e.g., "a" | "b" | "c" as a single variant) + if (isInlineStringLiteralUnion(variant.type)) { + hasStringVariant = true; + continue; + } // Any other variant type means it's not a simple enum return false; } @@ -187,6 +204,17 @@ export function getUnionEnumMembers( } else if (variant.type.kind === "String" && typeof variant.name === "symbol") { // Unnamed string literal variant (e.g., "none") — derive name from the value members.push({ name: variant.type.value, value: variant.type.value, variant }); + } else if (isInlineStringLiteralUnion(variant.type)) { + // Inline anonymous union of string literals — flatten into individual members + for (const innerVariant of (variant.type as Union).variants.values()) { + if (innerVariant.type.kind === "String") { + members.push({ + name: innerVariant.type.value, + value: innerVariant.type.value, + variant: innerVariant, + }); + } + } } } return members; From a6790bd41bd631d5ab5778e36da8813295e7476b Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Fri, 19 Jun 2026 11:46:28 -0700 Subject: [PATCH 4/5] add rendering to tests --- .../src/components/enums/enums.test.tsx | 145 +++++++++++++++--- 1 file changed, 123 insertions(+), 22 deletions(-) diff --git a/packages/http-server-csharp/src/components/enums/enums.test.tsx b/packages/http-server-csharp/src/components/enums/enums.test.tsx index 0d127877932..5d4fe2651ec 100644 --- a/packages/http-server-csharp/src/components/enums/enums.test.tsx +++ b/packages/http-server-csharp/src/components/enums/enums.test.tsx @@ -1,11 +1,12 @@ import { Tester } from "#test/tester.js"; import { type Children } from "@alloy-js/core"; -import { createCSharpNamePolicy, SourceFile } from "@alloy-js/csharp"; +import { Attribute, createCSharpNamePolicy, EnumDeclaration as CsEnumDeclaration, EnumMember, SourceFile } from "@alloy-js/csharp"; +import { type Union } from "@typespec/compiler"; import { t, type TesterInstance } from "@typespec/compiler/testing"; import { Output } from "@typespec/emitter-framework"; import { EnumDeclaration } from "@typespec/emitter-framework/csharp"; import { beforeEach, describe, expect, it } from "vitest"; -import { isUnionEnum } from "./enums.jsx"; +import { getUnionEnumMembers, isUnionEnum } from "./enums.jsx"; let runner: TesterInstance; @@ -22,6 +23,26 @@ function Wrapper(props: { children: Children }) { ); } +/** + * Renders a union-as-enum using the same logic as the Enums component, + * but without the file/namespace/useTsp wrapping. + */ +function UnionEnumDecl(props: { union: Union }) { + const members = getUnionEnumMembers(props.union); + return ( + + {members.map((member, i) => ( + <> + + {"\n"} + + {i < members.length - 1 ? ",\n" : ""} + + ))} + + ); +} + describe("EnumDeclaration", () => { it("renders a simple enum", async () => { const { Color } = await runner.compile(t.code` @@ -126,7 +147,21 @@ describe("isUnionEnum", () => { expect(isUnionEnum(Priority)).toBe(true); }); - it("returns true for union with unnamed string literals", async () => { + it("returns false for union with non-string variant types", async () => { + const { Mixed } = await runner.compile(t.code` + model Foo { x: string; } + union ${t.union("Mixed")} { + Foo, + "bar", + } + `); + + expect(isUnionEnum(Mixed)).toBe(false); + }); +}); + +describe("union-as-enum rendering", () => { + it("renders union with unnamed string literals", async () => { const { Priority } = await runner.compile(t.code` union ${t.union("Priority")} { "low", @@ -135,10 +170,24 @@ describe("isUnionEnum", () => { } `); - expect(isUnionEnum(Priority)).toBe(true); + expect( + + + , + ).toRenderTo(` + public enum Priority + { + [JsonStringEnumMemberName("low")] + Low, + [JsonStringEnumMemberName("medium")] + Medium, + [JsonStringEnumMemberName("high")] + High + } + `); }); - it("returns true for union with unnamed string literals and null", async () => { + it("renders union with unnamed string literals and null (null is skipped)", async () => { const { ReasoningEffort } = await runner.compile(t.code` union ${t.union("ReasoningEffort")} { "none", @@ -150,10 +199,28 @@ describe("isUnionEnum", () => { } `); - expect(isUnionEnum(ReasoningEffort)).toBe(true); + expect( + + + , + ).toRenderTo(` + public enum ReasoningEffort + { + [JsonStringEnumMemberName("none")] + None, + [JsonStringEnumMemberName("minimal")] + Minimal, + [JsonStringEnumMemberName("low")] + Low, + [JsonStringEnumMemberName("medium")] + Medium, + [JsonStringEnumMemberName("high")] + High + } + `); }); - it("returns true for union with named variants and null", async () => { + it("renders union with named variants and null (null is skipped)", async () => { const { ReasoningEffort } = await runner.compile(t.code` union ${t.union("ReasoningEffort")} { none: "none", @@ -163,10 +230,24 @@ describe("isUnionEnum", () => { } `); - expect(isUnionEnum(ReasoningEffort)).toBe(true); + expect( + + + , + ).toRenderTo(` + public enum ReasoningEffort + { + [JsonStringEnumMemberName("none")] + None, + [JsonStringEnumMemberName("medium")] + Medium, + [JsonStringEnumMemberName("high")] + High + } + `); }); - it("returns true for extensible union with string base, named variants, and null", async () => { + it("renders extensible union with string base, named variants, and null", async () => { const { ReasoningEffort } = await runner.compile(t.code` union ${t.union("ReasoningEffort")} { string, @@ -177,22 +258,24 @@ describe("isUnionEnum", () => { } `); - expect(isUnionEnum(ReasoningEffort)).toBe(true); - }); - - it("returns false for union with non-string variant types", async () => { - const { Mixed } = await runner.compile(t.code` - model Foo { x: string; } - union ${t.union("Mixed")} { - Foo, - "bar", + expect( + + + , + ).toRenderTo(` + public enum ReasoningEffort + { + [JsonStringEnumMemberName("none")] + None, + [JsonStringEnumMemberName("medium")] + Medium, + [JsonStringEnumMemberName("high")] + High } `); - - expect(isUnionEnum(Mixed)).toBe(false); }); - it("returns true for anonymous inline union of string literals", async () => { + it("renders union with inline anonymous union of string literals and null", async () => { const { ReasoningEffort } = await runner.compile(t.code` union ${t.union("ReasoningEffort")} { "none" | "minimal" | "low" | "medium" | "high", @@ -200,6 +283,24 @@ describe("isUnionEnum", () => { } `); - expect(isUnionEnum(ReasoningEffort)).toBe(true); + expect( + + + , + ).toRenderTo(` + public enum ReasoningEffort + { + [JsonStringEnumMemberName("none")] + None, + [JsonStringEnumMemberName("minimal")] + Minimal, + [JsonStringEnumMemberName("low")] + Low, + [JsonStringEnumMemberName("medium")] + Medium, + [JsonStringEnumMemberName("high")] + High + } + `); }); }); From b47e2b5da78df7e4c2cafd42ae3edb7618e4446a Mon Sep 17 00:00:00 2001 From: sophia-ramsey Date: Fri, 19 Jun 2026 11:57:10 -0700 Subject: [PATCH 5/5] fix formatting --- .../src/components/enums/enums.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/http-server-csharp/src/components/enums/enums.test.tsx b/packages/http-server-csharp/src/components/enums/enums.test.tsx index 5d4fe2651ec..811d2cb2c2e 100644 --- a/packages/http-server-csharp/src/components/enums/enums.test.tsx +++ b/packages/http-server-csharp/src/components/enums/enums.test.tsx @@ -1,6 +1,12 @@ import { Tester } from "#test/tester.js"; import { type Children } from "@alloy-js/core"; -import { Attribute, createCSharpNamePolicy, EnumDeclaration as CsEnumDeclaration, EnumMember, SourceFile } from "@alloy-js/csharp"; +import { + Attribute, + createCSharpNamePolicy, + EnumDeclaration as CsEnumDeclaration, + EnumMember, + SourceFile, +} from "@alloy-js/csharp"; import { type Union } from "@typespec/compiler"; import { t, type TesterInstance } from "@typespec/compiler/testing"; import { Output } from "@typespec/emitter-framework";