88
99import * as React from 'react' ;
1010import { describe , it , expect , vi , afterEach , beforeAll } from 'vitest' ;
11- import { render , screen , cleanup } from '@testing-library/react' ;
11+ import { render , screen , cleanup , fireEvent , waitFor } from '@testing-library/react' ;
1212import userEvent from '@testing-library/user-event' ;
1313import { ObjectSchema } from '@objectstack/spec/data' ;
1414import {
@@ -61,8 +61,41 @@ function renderAmount(fields: Record<string, Record<string, unknown>>) {
6161 return { onPatch, saved } ;
6262}
6363
64+ /** {@link renderAmount }, with each patch applied to the draft, so a second edit sees the first. */
65+ function renderAmountLive ( initial : Record < string , Record < string , unknown > > ) {
66+ const onPatch = vi . fn ( ) ;
67+ function Live ( ) {
68+ const [ fields , setFields ] = React . useState ( initial ) ;
69+ return (
70+ < ObjectFieldInspector
71+ type = "object"
72+ name = "invoice"
73+ draft = { { name : 'invoice' , fields } }
74+ selection = { { kind : 'field' , id : 'amount' } }
75+ onPatch = { ( patch ) => {
76+ onPatch ( patch ) ;
77+ setFields ( patch . fields as Record < string , Record < string , unknown > > ) ;
78+ } }
79+ onClearSelection = { vi . fn ( ) }
80+ onSelectionChange = { vi . fn ( ) }
81+ readOnly = { false }
82+ locale = "en-US"
83+ />
84+ ) ;
85+ }
86+ render ( < Live /> ) ;
87+ const saved = ( ) => onPatch . mock . calls . at ( - 1 ) ! [ 0 ] . fields as Record < string , Record < string , unknown > > ;
88+ return { onPatch, saved } ;
89+ }
90+
6491const CODE = { type : 'text' , label : 'Currency' , valueDomain : 'iso_4217_currency' } ;
6592
93+ async function pick ( label : RegExp | string , option : string ) {
94+ fireEvent . keyDown ( screen . getByRole ( 'combobox' , { name : label } ) , { key : 'ArrowDown' } ) ;
95+ await waitFor ( ( ) => expect ( screen . queryAllByRole ( 'option' ) . length ) . toBeGreaterThan ( 0 ) ) ;
96+ fireEvent . click ( screen . getByRole ( 'option' , { name : option } ) ) ;
97+ }
98+
6699describe ( 'Studio currency section (ADR-0144 D2)' , ( ) => {
67100 it ( 'a mode switch keeps only the keys that mode reads' , ( ) => {
68101 expect ( switchCurrencyMode ( { currencyMode : 'record' , currencyField : 'currency_code' } , 'dynamic' ) ) . toEqual ( { currencyMode : 'dynamic' } ) ;
@@ -88,6 +121,19 @@ describe('Studio currency section (ADR-0144 D2)', () => {
88121 expect ( screen . getByRole ( 'combobox' , { name : / C u r r e n c y f i e l d / } ) ) . toHaveTextContent ( 'currency_code' ) ;
89122 } ) ;
90123
124+ it ( 'switching a fixed amount to record writes no defaultCurrency, and the object parses (#27268 refuses one)' , async ( ) => {
125+ const { saved } = renderAmountLive ( {
126+ amount : { type : 'currency' , label : 'Amount' , currencyConfig : { currencyMode : 'fixed' , defaultCurrency : 'EUR' } } ,
127+ currency_code : CODE ,
128+ } ) ;
129+ await pick ( 'Currency mode' , 'Record — each record names its currency' ) ;
130+ expect ( saved ( ) . amount . currencyConfig ) . toEqual ( { currencyMode : 'record' } ) ;
131+ await pick ( / C u r r e n c y f i e l d / , 'currency_code' ) ;
132+ const fields = saved ( ) ;
133+ expect ( fields . amount . currencyConfig ) . toEqual ( { currencyMode : 'record' , currencyField : 'currency_code' } ) ;
134+ expect ( ObjectSchema . safeParse ( { name : 'invoice' , label : 'Invoice' , fields } ) . success ) . toBe ( true ) ;
135+ } ) ;
136+
91137 it ( '"Create currency field" writes an explicit code field and the pointer, and the object parses' , async ( ) => {
92138 const { saved } = renderAmount ( { amount : { type : 'currency' , label : 'Amount' , currencyConfig : { currencyMode : 'record' } } } ) ;
93139 await userEvent . click ( screen . getByTestId ( 'currency-config-create-code-field' ) ) ;
0 commit comments