|
1 | 1 | import { describe, expect, test } from "vitest"; |
2 | 2 |
|
3 | | -import { envVarNameFromKeyPath, resolveDescription } from "./KeyDrawer"; |
| 3 | +import { |
| 4 | + envVarNameFromKeyPath, |
| 5 | + resolveDescription, |
| 6 | + resolveValueAnnotation, |
| 7 | +} from "./KeyDrawer"; |
4 | 8 | import type { Row } from "@/lib/rows"; |
5 | 9 |
|
6 | 10 | function rowWithKey(keyPath: string, partial: Partial<Row> = {}): Row { |
@@ -113,78 +117,84 @@ describe("resolveDescription", () => { |
113 | 117 | expect(resolveDescription(row)).toBe("First line."); |
114 | 118 | }); |
115 | 119 |
|
116 | | - test("uses the permissions catalog mode description for permissions.defaultMode rows whose value is documented", () => { |
117 | | - // The permissions catalog has prose richer than the settings |
118 | | - // schema's mash-up of every mode in one description. When the row's |
119 | | - // effective value is a cataloged mode, surface that mode's prose. |
| 120 | + test("keeps the generic settings catalog description for permissions.defaultMode regardless of value", () => { |
| 121 | + // The mode-specific prose moved out of the header — it's value- |
| 122 | + // conditional and belongs under the EFFECTIVE block. The header |
| 123 | + // describes the knob itself, not its current value. |
120 | 124 | const row = rowWithKey("permissions.defaultMode", { |
121 | 125 | value: "acceptEdits", |
122 | 126 | catalog: { |
123 | 127 | key: "permissions.defaultMode", |
124 | 128 | description: "Default permission mode.\nGeneric multi-line prose.", |
125 | 129 | }, |
126 | 130 | }); |
127 | | - const desc = resolveDescription(row); |
128 | | - expect(desc).not.toBe("Default permission mode."); |
129 | | - expect(desc).toMatch(/edits/i); |
| 131 | + expect(resolveDescription(row)).toBe("Default permission mode."); |
130 | 132 | }); |
| 133 | +}); |
131 | 134 |
|
132 | | - test("falls back to the settings catalog description for permissions.defaultMode when the value is undocumented", () => { |
133 | | - // `delegate` is in the settings JSON Schema enum but isn't in the |
134 | | - // upstream permissions docs (experimental agent-team mode). The |
135 | | - // drawer must fall back to the settings catalog's first line. |
136 | | - const row = rowWithKey("permissions.defaultMode", { |
137 | | - value: "delegate", |
138 | | - catalog: { |
139 | | - key: "permissions.defaultMode", |
140 | | - description: "Default permission mode.\nGeneric multi-line prose.", |
141 | | - }, |
142 | | - }); |
143 | | - expect(resolveDescription(row)).toBe("Default permission mode."); |
| 135 | +describe("resolveValueAnnotation", () => { |
| 136 | + test("returns the cataloged mode description for permissions.defaultMode when the value is documented", () => { |
| 137 | + // The annotation surfaces what the current *value* does, not what |
| 138 | + // the knob does. Anchor on `acceptEdits` — its prose is stable. |
| 139 | + const row = rowWithKey("permissions.defaultMode", { value: "acceptEdits" }); |
| 140 | + const anno = resolveValueAnnotation(row); |
| 141 | + expect(anno).not.toBeNull(); |
| 142 | + expect(anno).toMatch(/edits/i); |
144 | 143 | }); |
145 | 144 |
|
146 | | - test("uses catalog default when permissions.defaultMode row is unset", () => { |
147 | | - // Unset rows carry their value from `catalog.default` (see rows.ts). |
148 | | - // The cross-reference must work for the unset case too — that's the |
149 | | - // common state for new users who haven't customized permissions. |
| 145 | + test("returns null for permissions.defaultMode when the value is undocumented", () => { |
| 146 | + // `delegate` is in the JSON Schema enum but not in the upstream |
| 147 | + // permissions docs. Don't render an annotation — the alternative is |
| 148 | + // misleading prose. |
| 149 | + const row = rowWithKey("permissions.defaultMode", { value: "delegate" }); |
| 150 | + expect(resolveValueAnnotation(row)).toBeNull(); |
| 151 | + }); |
| 152 | + |
| 153 | + test("fires for unset permissions.defaultMode rows whose value is the catalog default", () => { |
| 154 | + // Unset rows carry value from `catalog.default` (see rows.ts). The |
| 155 | + // unset case is the common state for new users; the annotation |
| 156 | + // should still help them understand what the default actually does. |
150 | 157 | const row = rowWithKey("permissions.defaultMode", { |
151 | 158 | value: "default", |
152 | 159 | state: "unset", |
153 | | - catalog: { |
154 | | - key: "permissions.defaultMode", |
155 | | - description: "Default permission mode.\nGeneric prose.", |
156 | | - default: "default", |
157 | | - }, |
| 160 | + catalog: { key: "permissions.defaultMode", default: "default" }, |
158 | 161 | }); |
159 | | - const desc = resolveDescription(row); |
160 | | - expect(desc).not.toBe("Default permission mode."); |
161 | | - expect(desc?.length ?? 0).toBeGreaterThan(0); |
| 162 | + const anno = resolveValueAnnotation(row); |
| 163 | + expect(anno).not.toBeNull(); |
| 164 | + expect(anno!.length).toBeGreaterThan(0); |
162 | 165 | }); |
163 | 166 |
|
164 | | - test("ignores non-string values on permissions.defaultMode without throwing", () => { |
165 | | - // Defensive: a malformed settings.json could put a non-string here. |
166 | | - // The lookup must not crash; fall back to the settings catalog prose. |
167 | | - const row = rowWithKey("permissions.defaultMode", { |
168 | | - value: 42 as unknown, |
169 | | - catalog: { |
170 | | - key: "permissions.defaultMode", |
171 | | - description: "Default permission mode.", |
172 | | - }, |
173 | | - }); |
174 | | - expect(resolveDescription(row)).toBe("Default permission mode."); |
| 167 | + test("returns null for non-string values without throwing", () => { |
| 168 | + // Defensive: malformed settings.json could put a non-string here. |
| 169 | + const row = rowWithKey("permissions.defaultMode", { value: 42 as unknown }); |
| 170 | + expect(resolveValueAnnotation(row)).toBeNull(); |
175 | 171 | }); |
176 | 172 |
|
177 | | - test("permissions catalog override does not bleed into other permissions.* rows", () => { |
178 | | - // Only `permissions.defaultMode` joins to `permissions.modes`. Other |
179 | | - // permissions.* rows (allow/deny/ask/...) keep the existing |
180 | | - // catalog-description behavior. |
181 | | - const row = rowWithKey("permissions.allow", { |
182 | | - value: ["Bash"], |
183 | | - catalog: { |
184 | | - key: "permissions.allow", |
185 | | - description: "Tools permitted without prompting", |
186 | | - }, |
187 | | - }); |
188 | | - expect(resolveDescription(row)).toBe("Tools permitted without prompting"); |
| 173 | + test("returns null for any other keyPath", () => { |
| 174 | + // Only permissions.defaultMode joins to permissions.modes today. |
| 175 | + // Other permissions.* rows (allow / deny / ask / ...) and unrelated |
| 176 | + // rows must not get an annotation. |
| 177 | + expect( |
| 178 | + resolveValueAnnotation(rowWithKey("permissions.allow", { value: ["Bash"] })), |
| 179 | + ).toBeNull(); |
| 180 | + expect( |
| 181 | + resolveValueAnnotation(rowWithKey("model", { value: "claude-sonnet-4-6" })), |
| 182 | + ).toBeNull(); |
| 183 | + expect( |
| 184 | + resolveValueAnnotation(rowWithKey("env.ANTHROPIC_API_KEY", { value: "sk-x" })), |
| 185 | + ).toBeNull(); |
| 186 | + }); |
| 187 | + |
| 188 | + test("preserves the full multi-line description (no first-line truncation)", () => { |
| 189 | + // The annotation lives in a block of its own under EFFECTIVE, not |
| 190 | + // a single-line header band. Keep the full prose so the user sees |
| 191 | + // qualifiers like 'Currently a research preview' that follow on |
| 192 | + // later lines if upstream ever adds them. |
| 193 | + const row = rowWithKey("permissions.defaultMode", { value: "auto" }); |
| 194 | + const anno = resolveValueAnnotation(row); |
| 195 | + // Real catalog prose for `auto` includes a second clause that the |
| 196 | + // header would have truncated; assert structurally — the prose drifts. |
| 197 | + expect(anno).not.toBeNull(); |
| 198 | + expect(anno!.length).toBeGreaterThan(40); |
189 | 199 | }); |
190 | 200 | }); |
0 commit comments