@@ -26,122 +26,146 @@ describe('TableWidget', () => {
2626 } ) ;
2727 } ) ;
2828
29- describe ( 'transformToViewModel' , ( ) => {
29+ describe ( 'transformToViewModel (array context) ' , ( ) => {
3030 const data = [
3131 { 'unique-id' : '1' , name : 'Alice' } ,
3232 { 'unique-id' : '2' , name : 'Bob' , extra : undefined } ,
3333 { 'unique-id' : '' , name : 'Empty ID' } ,
3434 { name : 'No ID' } ,
35- { 'unique-id' : null }
35+ { 'unique-id' : null } ,
3636 ] ;
3737
3838 it ( 'transforms array with default options' , ( ) => {
3939 const vm = TableWidget . transformToViewModel ! ( data , { } ) ;
40- expect ( vm . rows . length ) . toBe ( 5 ) ; // Now processes all valid objects
4140 expect ( vm . headers ) . toBe ( true ) ;
42- expect ( vm . rows [ 0 ] ) . toEqual ( {
43- id : '1' , // Uses the unique-id value
44- data : { 'unique-id' : '1' , name : 'Alice' }
45- } ) ;
46- expect ( vm . rows [ 1 ] ) . toEqual ( {
47- id : '2' ,
48- data : { 'unique-id' : '2' , name : 'Bob' }
49- } ) ;
50- expect ( vm . rows [ 2 ] ) . toEqual ( {
51- id : '2' , // Uses array index as fallback for empty unique-id
52- data : { 'unique-id' : '' , name : 'Empty ID' }
53- } ) ;
54- expect ( vm . rows [ 3 ] ) . toEqual ( {
55- id : '3' , // Uses array index as fallback for missing unique-id
56- data : { name : 'No ID' }
57- } ) ;
58- expect ( vm . rows [ 4 ] ) . toEqual ( {
59- id : '4' , // Uses array index as fallback for null unique-id
60- data : { 'unique-id' : null }
61- } ) ;
62- } ) ;
63-
64- it ( 'transforms object into entries' , ( ) => {
65- const input = {
66- foo : { name : 'Foo' } ,
67- bar : { name : 'Bar' }
68- } ;
69- const vm = TableWidget . transformToViewModel ! ( input , { } ) ;
70- expect ( vm . rows . length ) . toBe ( 2 ) ;
71- expect ( vm . rows [ 0 ] . id ) . toBe ( 'foo' ) ;
72- expect ( vm . rows [ 0 ] . data ) . toEqual ( { name : 'Foo' , 'unique-id' : 'foo' } ) ;
41+ expect ( vm . flatTable ) . toBe ( false ) ;
42+ expect ( vm . rows . length ) . toBe ( 5 ) ;
43+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '1' , data : { 'unique-id' : '1' , name : 'Alice' } } ) ;
44+ expect ( vm . rows [ 1 ] ) . toEqual ( { id : '2' , data : { 'unique-id' : '2' , name : 'Bob' } } ) ;
45+ expect ( vm . rows [ 2 ] ) . toEqual ( { id : '2' , data : { 'unique-id' : '' , name : 'Empty ID' } } ) ;
46+ expect ( vm . rows [ 3 ] ) . toEqual ( { id : '3' , data : { name : 'No ID' } } ) ;
47+ expect ( vm . rows [ 4 ] ) . toEqual ( { id : '4' , data : { 'unique-id' : null } } ) ;
48+ } ) ;
49+
50+ it ( 'trims whitespace id and falls back to index' , ( ) => {
51+ const vm = TableWidget . transformToViewModel ! ( [ { 'unique-id' : ' ' , name : 'X' } ] , { } ) ;
52+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '0' , data : { 'unique-id' : ' ' , name : 'X' } } ) ;
53+ } ) ;
54+
55+ it ( 'respects headers option = false' , ( ) => {
56+ const vm = TableWidget . transformToViewModel ! ( data , { headers : false } ) ;
57+ expect ( vm . headers ) . toBe ( false ) ;
58+ } ) ;
59+
60+ it ( 'filters columns correctly and sets flatTable' , ( ) => {
61+ const vm = TableWidget . transformToViewModel ! ( data , { columns : 'name' } ) ;
62+ expect ( vm . flatTable ) . toBe ( true ) ;
63+ expect ( vm . columnNames ) . toEqual ( [ 'name' ] ) ;
64+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '1' , data : { name : 'Alice' } } ) ;
65+ } ) ;
66+
67+ it ( 'filters columns and keeps key out of data' , ( ) => {
68+ const vm = TableWidget . transformToViewModel ! ( data , { columns : 'name' , key : 'unique-id' } ) ;
69+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '1' , data : { name : 'Alice' } } ) ;
70+ } ) ;
71+
72+ it ( 'injects unique-id into data when requested in columns' , ( ) => {
73+ const vm = TableWidget . transformToViewModel ! ( [ { name : 'Z' } ] , { columns : 'unique-id, name' } ) ;
74+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '0' , data : { 'unique-id' : '0' , name : 'Z' } } ) ;
75+ } ) ;
76+
77+ it ( 'injects id into data when requested in columns' , ( ) => {
78+ const vm = TableWidget . transformToViewModel ! ( [ { name : 'Y' } ] , { columns : 'id, name' } ) ;
79+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '0' , data : { id : '0' , name : 'Y' } } ) ;
7380 } ) ;
7481
7582 it ( 'uses custom key' , ( ) => {
76- const custom = [ { key : 'abc' , name : 'Test' } ] ;
77- const vm = TableWidget . transformToViewModel ! ( custom , {
78- key : 'key'
79- } ) ;
83+ const vm = TableWidget . transformToViewModel ! ( [ { key : 'abc' , name : 'Test' } ] , { key : 'key' } ) ;
8084 expect ( vm . rows [ 0 ] . id ) . toBe ( 'abc' ) ;
85+ expect ( vm . rows [ 0 ] . data ) . toEqual ( { key : 'abc' , name : 'Test' } ) ;
86+ } ) ;
87+
88+ it ( 'handles missing/non-string key by falling back to index' , ( ) => {
89+ const vm = TableWidget . transformToViewModel ! ( [ { id : 123 } , { id : null } , { } ] , { key : 'id' } ) ;
90+ expect ( vm . rows . length ) . toBe ( 3 ) ;
91+ expect ( vm . rows [ 0 ] . id ) . toBe ( '123' ) ; // numeric id is stringified, not dropped
92+ expect ( vm . rows [ 1 ] . id ) . toBe ( '1' ) ; // null -> fallback
93+ expect ( vm . rows [ 2 ] . id ) . toBe ( '2' ) ; // missing -> fallback
8194 } ) ;
95+ } ) ;
8296
83- it ( 'skips records with missing or non-string key' , ( ) => {
84- const invalid = [ { id : 123 } , { id : null } , { } ] ;
85- const vm = TableWidget . transformToViewModel ! ( invalid , {
86- key : 'id'
87- } ) ;
88- expect ( vm . rows . length ) . toBe ( 3 ) ; // All objects processed with fallback indices
89- expect ( vm . rows [ 0 ] . id ) . toBe ( '0' ) ; // Uses array index
90- expect ( vm . rows [ 1 ] . id ) . toBe ( '1' ) ;
91- expect ( vm . rows [ 2 ] . id ) . toBe ( '2' ) ;
97+ describe ( 'transformToViewModel (object context)' , ( ) => {
98+ it ( 'expands object entries where values are objects and attaches key' , ( ) => {
99+ const input = { foo : { name : 'Foo' } , bar : { name : 'Bar' } } ;
100+ const vm = TableWidget . transformToViewModel ! ( input , { } ) ;
101+ expect ( vm . flatTable ) . toBe ( false ) ;
102+ expect ( vm . rows . length ) . toBe ( 2 ) ;
103+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : 'foo' , data : { name : 'Foo' , 'unique-id' : 'foo' } } ) ;
104+ expect ( vm . rows [ 1 ] ) . toEqual ( { id : 'bar' , data : { name : 'Bar' , 'unique-id' : 'bar' } } ) ;
92105 } ) ;
93106
94- it ( 'respects headers option = false' , ( ) => {
95- const vm = TableWidget . transformToViewModel ! ( data , {
96- headers : false
97- } ) ;
98- expect ( vm . headers ) . toBe ( false ) ;
107+ it ( 'expands primitive values into { value, key } records' , ( ) => {
108+ const input = { a : 1 , b : 'x' , c : { y : 2 } } ;
109+ const vm = TableWidget . transformToViewModel ! ( input , { } ) ;
110+ const asMap = Object . fromEntries ( vm . rows . map ( r => [ r . id , r . data ] ) ) ;
111+ expect ( asMap [ 'a' ] ) . toEqual ( { value : 1 , 'unique-id' : 'a' } ) ;
112+ expect ( asMap [ 'b' ] ) . toEqual ( { value : 'x' , 'unique-id' : 'b' } ) ;
113+ expect ( asMap [ 'c' ] ) . toEqual ( { y : 2 , 'unique-id' : 'c' } ) ;
99114 } ) ;
100115
101- it ( 'filters columns correctly' , ( ) => {
102- const vm = TableWidget . transformToViewModel ! ( data , {
103- columns : 'name'
104- } ) ;
105- expect ( vm . rows [ 0 ] . data ) . toEqual ( { name : 'Alice' } ) ;
116+ it ( 'single-row with columns when object itself has those columns' , ( ) => {
117+ const input = { a : 1 , b : 2 , 'unique-id' : 'row1' , extra : undefined } ;
118+ const vm = TableWidget . transformToViewModel ! ( input , { columns : 'a, b' } ) ;
119+ expect ( vm . flatTable ) . toBe ( true ) ;
120+ expect ( vm . rows . length ) . toBe ( 1 ) ;
121+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '0' , data : { a : 1 , b : 2 } } ) ;
106122 } ) ;
107123
108- it ( 'filters columns and keeps key out of data' , ( ) => {
109- const vm = TableWidget . transformToViewModel ! ( data , {
110- columns : 'name' , key : 'unique-id'
111- } ) ;
112- expect ( vm . rows [ 0 ] ) . toEqual ( {
113- id : '1' ,
114- data : { name : 'Alice' }
115- } ) ;
116- } ) ;
117-
118- it ( 'works with object and columns' , ( ) => {
119- const input = {
120- foo : { a : 1 , b : 2 } ,
121- bar : { a : 3 , b : 4 }
122- } ;
123- const vm = TableWidget . transformToViewModel ! ( input , {
124- columns : 'a'
125- } ) ;
126- expect ( vm . rows [ 0 ] . data ) . toEqual ( { a : 1 } ) ;
124+
125+ it ( 'object with nested values and columns does not flatten (flatTable=false)' , ( ) => {
126+ const input = { foo : { a : 1 , b : 2 } , bar : { a : 3 , b : 4 } } ;
127+ const vm = TableWidget . transformToViewModel ! ( input , { columns : 'a' } ) ;
128+ expect ( vm . flatTable ) . toBe ( false ) ;
129+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : 'foo' , data : { a : 1 , b : 2 , 'unique-id' : 'foo' } } ) ;
130+ expect ( vm . rows [ 1 ] ) . toEqual ( { id : 'bar' , data : { a : 3 , b : 4 , 'unique-id' : 'bar' } } ) ;
131+ } ) ;
132+
133+ it ( 'orientation="vertical" keeps single row; with columns uses only those fields' , ( ) => {
134+ const input = { a : 1 , b : 2 , c : 3 } ;
135+ const vm = TableWidget . transformToViewModel ! ( input , { orientation : 'vertical' , columns : 'a, c' } ) ;
136+ expect ( vm . isVertical ) . toBe ( true ) ;
137+ expect ( vm . flatTable ) . toBe ( true ) ;
138+ expect ( vm . rows . length ) . toBe ( 1 ) ;
139+ expect ( vm . rows [ 0 ] ) . toEqual ( { id : '0' , data : { a : 1 , c : 3 } } ) ;
127140 } ) ;
128141 } ) ;
129142
130- describe ( 'registerHelpers' , ( ) => {
131- const helpers = TableWidget . registerHelpers ?.( ) ;
132- const fn = helpers ?. objectEntries ;
143+ describe ( 'error handling' , ( ) => {
144+ it ( 'throws on unsupported context' , ( ) => {
145+ // @ts -expect-error – intentionally passing wrong type to validate error branch
146+ expect ( ( ) => TableWidget . transformToViewModel ! ( 'nope' , { } ) ) . toThrow ( ) ;
147+ } ) ;
148+ } ) ;
133149
134- it ( 'objectEntries returns id/data pairs' , ( ) => {
135- const result = fn ?.( { a : 1 , b : 2 } ) ;
136- expect ( result ) . toEqual ( [
150+ describe ( 'registerHelpers' , ( ) => {
151+ it ( 'exposes objectEntries and and, and they behave' , ( ) => {
152+ expect ( TableWidget . registerHelpers ) . toBeDefined ( ) ;
153+ const helpers = TableWidget . registerHelpers ! ( ) ;
154+ const { objectEntries, and } = helpers ;
155+ expect ( typeof objectEntries ) . toBe ( 'function' ) ;
156+ expect ( typeof and ) . toBe ( 'function' ) ;
157+ expect ( objectEntries ( { a : 1 , b : 2 } ) ) . toEqual ( [
137158 { id : 'a' , data : 1 } ,
138- { id : 'b' , data : 2 }
159+ { id : 'b' , data : 2 } ,
139160 ] ) ;
140- } ) ;
141-
142- it ( 'objectEntries returns empty for non-object or array' , ( ) => {
143- expect ( fn ?.( null ) ) . toEqual ( [ ] ) ;
144- expect ( fn ?.( [ 1 , 2 , 3 ] ) ) . toEqual ( [ ] ) ;
161+ expect ( objectEntries ( null ) ) . toEqual ( [ ] ) ;
162+ expect ( objectEntries ( [ 1 , 2 , 3 ] ) ) . toEqual ( [ ] ) ;
163+ const opts : Record < string , never > = { } ;
164+ expect ( and ( true , true , opts ) ) . toBe ( true ) ;
165+ expect ( and ( true , false , opts ) ) . toBe ( false ) ;
166+ expect ( and ( 1 , 'x' , opts ) ) . toBe ( true ) ;
167+ expect ( and ( 0 , 'x' , opts ) ) . toBe ( false ) ;
145168 } ) ;
146169 } ) ;
170+
147171} ) ;
0 commit comments