|
1 | 1 | import { describe, it, expect } from 'vitest'; |
| 2 | +import { z } from 'zod'; |
2 | 3 | import { |
3 | 4 | FieldSchema, |
4 | 5 | FieldType, |
@@ -239,6 +240,75 @@ describe('FieldSchema', () => { |
239 | 240 | }); |
240 | 241 | }); |
241 | 242 |
|
| 243 | + describe('useGrouping — number-field digit-grouping presentation hint (#7768)', () => { |
| 244 | + it('accepts an explicit `false` (author opts out of grouping — e.g. a year)', () => { |
| 245 | + const yearField: Field = { |
| 246 | + name: 'founded_year', |
| 247 | + label: 'Founded Year', |
| 248 | + type: 'number', |
| 249 | + scale: 0, |
| 250 | + min: 1900, |
| 251 | + useGrouping: false, |
| 252 | + }; |
| 253 | + const result = FieldSchema.parse(yearField); |
| 254 | + expect(result.useGrouping).toBe(false); |
| 255 | + }); |
| 256 | + |
| 257 | + it('accepts an explicit `true` (author pins grouping on)', () => { |
| 258 | + const result = FieldSchema.parse({ type: 'number', useGrouping: true }); |
| 259 | + expect(result.useGrouping).toBe(true); |
| 260 | + }); |
| 261 | + |
| 262 | + it('is optional — absent stays absent, no default materializes', () => { |
| 263 | + const result = FieldSchema.parse({ type: 'number' }) as Record<string, unknown>; |
| 264 | + expect(result.useGrouping).toBeUndefined(); |
| 265 | + expect('useGrouping' in result).toBe(false); |
| 266 | + }); |
| 267 | + |
| 268 | + it('rejects a non-boolean value', () => { |
| 269 | + expect(() => FieldSchema.parse({ type: 'number', useGrouping: 'true' })).toThrow(); |
| 270 | + expect(() => FieldSchema.parse({ type: 'number', useGrouping: 1 })).toThrow(); |
| 271 | + expect(() => FieldSchema.parse({ type: 'number', useGrouping: null })).toThrow(); |
| 272 | + }); |
| 273 | + |
| 274 | + it('is not type-restricted at the schema level (flat on FieldSchema, like scale/min)', () => { |
| 275 | + // FieldSchema does not discriminate its constraint keys by `type` — same |
| 276 | + // posture as `scale`/`min`, which parse on any field type too. A type-aware |
| 277 | + // "only meaningful on number/currency/percent" restriction is a renderer/lint |
| 278 | + // concern, not a parse-time one. |
| 279 | + expect(() => FieldSchema.parse({ type: 'text', useGrouping: false })).not.toThrow(); |
| 280 | + }); |
| 281 | + |
| 282 | + it('does not disturb FieldSchema unknown-key strictness (#4001)', () => { |
| 283 | + expect(() => FieldSchema.parse({ |
| 284 | + type: 'number', |
| 285 | + useGrouping: false, |
| 286 | + totallyBogusKey: true, |
| 287 | + } as unknown as Field)).toThrow(/Unrecognized key/); |
| 288 | + }); |
| 289 | + |
| 290 | + it('Field.number(...) threads useGrouping through like scale/min (no special-casing needed)', () => { |
| 291 | + const f = Field.number({ label: 'Founded Year', scale: 0, min: 1900, useGrouping: false }); |
| 292 | + expect(f).toEqual({ type: 'number', label: 'Founded Year', scale: 0, min: 1900, useGrouping: false }); |
| 293 | + expect(() => FieldSchema.parse(f)).not.toThrow(); |
| 294 | + }); |
| 295 | + |
| 296 | + it('declares a boolean JSON-Schema slot with NO default — absence defers to the renderer', () => { |
| 297 | + const js = z.toJSONSchema(FieldSchema as unknown as z.ZodType, { |
| 298 | + unrepresentable: 'any', |
| 299 | + io: 'input', |
| 300 | + }) as any; |
| 301 | + const prop = js.properties?.useGrouping; |
| 302 | + expect(prop).toBeDefined(); |
| 303 | + expect(prop.type).toBe('boolean'); |
| 304 | + // Unlike `autonumberFormat`, this key carries no JSON-Schema `default` |
| 305 | + // annotation — there is no renderer-agnostic grouping behavior to declare |
| 306 | + // until the objectui half (#4033) retires the interim heuristic. |
| 307 | + expect(prop.default).toBeUndefined(); |
| 308 | + expect(js.required ?? []).not.toContain('useGrouping'); |
| 309 | + }); |
| 310 | + }); |
| 311 | + |
242 | 312 | describe('Select Field', () => { |
243 | 313 | it('should accept select field with options', () => { |
244 | 314 | const selectField: Field = { |
|
0 commit comments