99 * be spread across those arms, so a new type either satisfies them or fails
1010 * here.
1111 */
12+ import { Table as TableIcon } from '@sim/emcn/icons'
1213import { describe , expect , it } from 'vitest'
1314import { zonedWallClockToUtc } from '@/lib/core/utils/timezone'
1415import type { ColumnType } from '@/lib/table/column-types'
@@ -43,6 +44,17 @@ describe('registry shape', () => {
4344 expect ( isColumnType ( 'currency' ) ) . toBe ( true )
4445 } )
4546
47+ it ( 'registers reference columns as configured string-backed columns' , ( ) => {
48+ const definition = COLUMN_TYPE_REGISTRY . reference
49+
50+ expect ( definition . label ) . toBe ( 'Reference' )
51+ expect ( definition . icon ) . toBe ( TableIcon )
52+ expect ( definition . requiresConfigurationOnCreate ) . toBe ( true )
53+ expect ( definition . hasConfiguration ) . toBe ( true )
54+ expect ( definition . ownedMetadata ) . toEqual ( [ 'referenceTableId' ] )
55+ expect ( definition . jsonbCast ) . toBeNull ( )
56+ } )
57+
4658 it ( 'only casts to numeric/timestamptz for types whose storage is actually that' , ( ) => {
4759 // A wrong cast makes every filter and sort on the column fail in SQL.
4860 for ( const definition of ALL_COLUMN_TYPES ) {
@@ -317,19 +329,23 @@ describe('metadata ownership', () => {
317329 const options = [ { id : 'opt_a' , name : 'A' } ]
318330
319331 it . each `
320- label | definition | valid | needle
321- ${ 'options on select' } | ${ column ( { type : 'select' , options } ) } | ${ true } | ${ '' }
322- ${ 'options on string' } | ${ column ( { type : 'string' , options } ) } | ${ false } | ${ 'cannot define options' }
323- ${ 'options on currency' } | ${ column ( { type : 'currency' , options } ) } | ${ false } | ${ 'cannot define options' }
324- ${ 'multiple on number' } | ${ column ( { type : 'number' , multiple : true } ) } | ${ false } | ${ 'cannot be multiple' }
325- ${ 'code on currency' } | ${ column ( { type : 'currency' , currencyCode : 'USD' } ) } | ${ true } | ${ '' }
326- ${ 'code on number' } | ${ column ( { type : 'number' , currencyCode : 'USD' } ) } | ${ false } | ${ 'cannot define a currency' }
327- ${ 'code on select' } | ${ column ( { type : 'select' , currencyCode : 'USD' , options } ) } | ${ false } | ${ 'cannot define a currency' }
328- ${ 'unsupported code' } | ${ column ( { type : 'currency' , currencyCode : 'ZZZ' } ) } | ${ false } | ${ 'invalid currency code' }
329- ${ 'unique on select' } | ${ column ( { type : 'select' , unique : true , options } ) } | ${ false } | ${ 'cannot be unique' }
330- ${ 'unique on currency' } | ${ column ( { type : 'currency' , unique : true } ) } | ${ true } | ${ '' }
331- ${ 'select with no option' } | ${ column ( { type : 'select' } ) } | ${ false } | ${ 'at least one option' }
332- ${ 'unknown type' } | ${ column ( { type : 'percent' as ColumnDefinition [ 'type' ] } ) } | ${ false } | ${ 'invalid type' }
332+ label | definition | valid | needle
333+ ${ 'options on select' } | ${ column ( { type : 'select' , options } ) } | ${ true } | ${ '' }
334+ ${ 'options on string' } | ${ column ( { type : 'string' , options } ) } | ${ false } | ${ 'cannot define options' }
335+ ${ 'options on currency' } | ${ column ( { type : 'currency' , options } ) } | ${ false } | ${ 'cannot define options' }
336+ ${ 'multiple on number' } | ${ column ( { type : 'number' , multiple : true } ) } | ${ false } | ${ 'cannot be multiple' }
337+ ${ 'code on currency' } | ${ column ( { type : 'currency' , currencyCode : 'USD' } ) } | ${ true } | ${ '' }
338+ ${ 'code on number' } | ${ column ( { type : 'number' , currencyCode : 'USD' } ) } | ${ false } | ${ 'cannot define a currency' }
339+ ${ 'code on select' } | ${ column ( { type : 'select' , currencyCode : 'USD' , options } ) } | ${ false } | ${ 'cannot define a currency' }
340+ ${ 'unsupported code' } | ${ column ( { type : 'currency' , currencyCode : 'ZZZ' } ) } | ${ false } | ${ 'invalid currency code' }
341+ ${ 'target on reference' } | ${ column ( { type : 'reference' , referenceTableId : 'tbl_anything' } ) } | ${ true } | ${ '' }
342+ ${ 'missing target' } | ${ column ( { type : 'reference' } ) } | ${ false } | ${ 'reference table' }
343+ ${ 'empty target' } | ${ column ( { type : 'reference' , referenceTableId : '' } ) } | ${ false } | ${ 'reference table' }
344+ ${ 'target on string' } | ${ column ( { type : 'string' , referenceTableId : 'tbl_other' } ) } | ${ false } | ${ 'reference another table' }
345+ ${ 'unique on select' } | ${ column ( { type : 'select' , unique : true , options } ) } | ${ false } | ${ 'cannot be unique' }
346+ ${ 'unique on currency' } | ${ column ( { type : 'currency' , unique : true } ) } | ${ true } | ${ '' }
347+ ${ 'select with no option' } | ${ column ( { type : 'select' } ) } | ${ false } | ${ 'at least one option' }
348+ ${ 'unknown type' } | ${ column ( { type : 'percent' as ColumnDefinition [ 'type' ] } ) } | ${ false } | ${ 'invalid type' }
333349 ` (
334350 'rejects $label' ,
335351 ( {
@@ -346,6 +362,23 @@ describe('metadata ownership', () => {
346362 if ( ! valid ) expect ( result . errors . join ( ' ' ) . toLowerCase ( ) ) . toContain ( needle . toLowerCase ( ) )
347363 }
348364 )
365+
366+ it ( 'accepts arbitrary row-id strings without resolving them' , ( ) => {
367+ const column = {
368+ name : 'account' ,
369+ type : 'reference' ,
370+ referenceTableId : 'tbl_accounts' ,
371+ } as ColumnDefinition
372+ const definition = COLUMN_TYPE_REGISTRY . reference
373+
374+ expect ( definition . coerce ( 'not-a-real-row-id' , column ) ) . toEqual ( {
375+ ok : true ,
376+ value : 'not-a-real-row-id' ,
377+ } )
378+ expect ( definition . coerce ( 97 , column ) ) . toEqual ( { ok : true , value : '97' } )
379+ expect ( definition . coerce ( true , column ) ) . toEqual ( { ok : true , value : 'true' } )
380+ expect ( definition . validateCell ( 'not-a-real-row-id' , column ) ) . toBeNull ( )
381+ } )
349382} )
350383
351384/**
0 commit comments