@@ -840,6 +840,70 @@ describe('user test - exploring @ctablex/core as a new user', () => {
840840
841841 // Perfect! This pattern is super clean for optional fields.
842842 } ) ;
843+
844+ it ( 'should use ContentValue with accessor=undefined for flexible prop/context pattern' , ( ) => {
845+ // Wait, I just realized something from that review comment!
846+ // ContentValue with value prop and accessor=undefined can make components
847+ // accept data from EITHER props OR context. That's brilliant!
848+
849+ type User = {
850+ name : string ;
851+ email ?: string ;
852+ } ;
853+
854+ // Let me rewrite EmailDisplay to be more flexible
855+ const FlexibleEmailDisplay = ( { user } : { user ?: User } ) => (
856+ // Using ContentValue instead of ContentProvider
857+ // If user prop is passed, it uses that. Otherwise, falls back to context!
858+ < ContentValue value = { user } accessor = { undefined } >
859+ < FieldContent field = "email" >
860+ < NullableContent nullContent = "No email provided" >
861+ < DefaultContent />
862+ </ NullableContent >
863+ </ FieldContent >
864+ </ ContentValue >
865+ ) ;
866+
867+ const userWithEmail : User = {
868+ name : 'Alice' ,
869+ email : 'alice@example.com' ,
870+ } ;
871+
872+ const userWithoutEmail : User = {
873+ name : 'Bob' ,
874+ } ;
875+
876+ // Test 1: Using with prop directly (no ContentProvider needed!)
877+ const { container : c1 } = render ( < FlexibleEmailDisplay user = { userWithEmail } /> ) ;
878+ expect ( c1 . textContent ) . toBe ( 'alice@example.com' ) ;
879+
880+ const { container : c2 } = render ( < FlexibleEmailDisplay user = { userWithoutEmail } /> ) ;
881+ expect ( c2 . textContent ) . toBe ( 'No email provided' ) ;
882+
883+ // Test 2: Using with context (prop is optional)
884+ const { container : c3 } = render (
885+ < ContentProvider value = { userWithEmail } >
886+ < FlexibleEmailDisplay />
887+ </ ContentProvider >
888+ ) ;
889+ expect ( c3 . textContent ) . toBe ( 'alice@example.com' ) ;
890+
891+ // Test 3: Prop overrides context!
892+ const { container : c4 } = render (
893+ < ContentProvider value = { userWithEmail } >
894+ < FlexibleEmailDisplay user = { userWithoutEmail } />
895+ </ ContentProvider >
896+ ) ;
897+ expect ( c4 . textContent ) . toBe ( 'No email provided' ) ;
898+ // Gets value from prop, not context!
899+
900+ // This is amazing! The component is now:
901+ // 1. Usable standalone with props (traditional React)
902+ // 2. Usable with context (micro-context pattern)
903+ // 3. Props override context when both present
904+ // This makes components incredibly flexible and reusable!
905+ // Why didn't I think of this pattern earlier?
906+ } ) ;
843907 } ) ;
844908
845909 describe ( 'NullContent - conditional rendering for nulls' , ( ) => {
@@ -1204,8 +1268,9 @@ describe('user test - exploring @ctablex/core as a new user', () => {
12041268 // no address
12051269 } ;
12061270
1207- const UserDisplay = ( { user } : { user : User } ) => (
1208- < ContentProvider value = { user } >
1271+ // Now using the flexible pattern! Making user optional and using ContentValue
1272+ const UserDisplay = ( { user } : { user ?: User } ) => (
1273+ < ContentValue value = { user } accessor = { undefined } >
12091274 < div >
12101275 < FieldContent field = "name" />
12111276 < FieldContent field = "address" >
@@ -1220,9 +1285,10 @@ describe('user test - exploring @ctablex/core as a new user', () => {
12201285 </ NullableContent >
12211286 </ FieldContent >
12221287 </ div >
1223- </ ContentProvider >
1288+ </ ContentValue >
12241289 ) ;
12251290
1291+ // Works with direct props
12261292 const { container : c1 } = render ( < UserDisplay user = { user1 } /> ) ;
12271293 expect ( c1 . textContent ) . toBe ( 'Alice - NYC, USA' ) ;
12281294
@@ -1232,17 +1298,29 @@ describe('user test - exploring @ctablex/core as a new user', () => {
12321298 const { container : c3 } = render ( < UserDisplay user = { user3 } /> ) ;
12331299 expect ( c3 . textContent ) . toBe ( 'Charlie - No address' ) ;
12341300
1301+ // Also works with context!
1302+ const { container : c4 } = render (
1303+ < ContentProvider value = { user1 } >
1304+ < UserDisplay />
1305+ </ ContentProvider >
1306+ ) ;
1307+ expect ( c4 . textContent ) . toBe ( 'Alice - NYC, USA' ) ;
1308+
12351309 // Wow! NullableContent handles all the edge cases elegantly.
1310+ // AND now UserDisplay works with both props and context!
12361311 // This pattern is really powerful for real-world data.
1312+ // I'm starting to see this as a best practice for reusable components.
12371313 } ) ;
12381314
12391315 it ( 'should create reusable renderer components' , ( ) => {
12401316 // One of the key features mentioned: reusable components
12411317 // Let me create a generic formatter that works with any data
12421318
1243- const CurrencyDisplay = ( ) => {
1244- const amount = useContent < number > ( ) ;
1245- return < span > ${ amount . toFixed ( 2 ) } </ span > ;
1319+ // Using the flexible pattern here too!
1320+ const CurrencyDisplay = ( { amount } : { amount ?: number } ) => {
1321+ // useContent with optional override - works with prop or context!
1322+ const value = useContent < number > ( amount ) ;
1323+ return < span > ${ value . toFixed ( 2 ) } </ span > ;
12461324 } ;
12471325
12481326 type Product = {
@@ -1255,18 +1333,39 @@ describe('user test - exploring @ctablex/core as a new user', () => {
12551333 price : 99.99 ,
12561334 } ;
12571335
1258- const { container } = render (
1336+ // Test 1: Traditional context usage
1337+ const { container : c1 } = render (
12591338 < ContentProvider value = { product } >
12601339 < FieldContent field = "name" /> : < FieldContent field = "price" >
12611340 < CurrencyDisplay />
12621341 </ FieldContent >
12631342 </ ContentProvider >
12641343 ) ;
1344+ expect ( c1 . textContent ) . toBe ( 'Widget: $99.99' ) ;
12651345
1266- expect ( container . textContent ) . toBe ( 'Widget: $99.99' ) ;
1346+ // Test 2: Direct prop usage (no context needed!)
1347+ const { container : c2 } = render (
1348+ < div >
1349+ Total: < CurrencyDisplay amount = { 1234.56 } />
1350+ </ div >
1351+ ) ;
1352+ expect ( c2 . textContent ) . toBe ( 'Total: $1,234.56' ) ;
12671353
1268- // This is powerful! CurrencyDisplay is reusable anywhere there's a number in context.
1269- // It doesn't need to know about Product or price - just formats the current context value.
1354+ // Test 3: Prop overrides context
1355+ const { container : c3 } = render (
1356+ < ContentProvider value = { 99.99 } >
1357+ < CurrencyDisplay amount = { 123.45 } />
1358+ </ ContentProvider >
1359+ ) ;
1360+ expect ( c3 . textContent ) . toBe ( '$123.45' ) ;
1361+
1362+ // This is powerful! CurrencyDisplay now works:
1363+ // 1. With context (micro-context pattern)
1364+ // 2. With props (traditional React)
1365+ // 3. Anywhere in either mode!
1366+ // The useContent(amount) pattern is the key - it's like useContent but with optional override.
1367+ // Actually, I realize now that useContent ALWAYS supported this - it's in the docs!
1368+ // I just didn't fully appreciate how useful it is until now.
12701369 } ) ;
12711370
12721371 it ( 'should compose multiple transformations' , ( ) => {
@@ -1565,23 +1664,32 @@ describe('user test - exploring @ctablex/core as a new user', () => {
15651664 // ✓ Reusable renderers are powerful - write once, use anywhere
15661665 // ✓ NullableContent, EmptyContent patterns are elegant for real-world data
15671666 // ✓ ContentValue with path strings avoids deep nesting
1667+ // ✓ The flexible prop/context pattern (ContentValue with value prop + accessor=undefined,
1668+ // or useContent with optional value param) is BRILLIANT! Makes components work in both modes.
15681669 //
15691670 // OBSERVATIONS:
15701671 // - Need to remember DefaultContent only works with primitives
15711672 // - Must provide explicit children for ArrayContent with object arrays
15721673 // - The nesting can get deep, but that's the nature of the pattern
15731674 // - ContentValue with paths is better for deep access than nested FieldContent
1675+ // - The value prop + accessor=undefined pattern should probably be a best practice!
1676+ // It makes components maximally flexible without any downside.
15741677 //
15751678 // QUESTIONS/WISHES:
15761679 // - The docs are good, but some examples could show more complex real-world scenarios
15771680 // - Would be nice to have more examples of custom isEmpty functions
15781681 // - The difference between ContentValue and FieldContent could be clearer up front
15791682 // (I figured out ContentValue is more powerful, but took some exploration)
15801683 // - AccessorContent vs ContentValue naming - docs mention it's an alias, good to know
1684+ // - The flexible prop/context pattern deserves MORE emphasis in docs! It's so powerful
1685+ // but I almost missed it. Maybe a dedicated section on "Building Flexible Components"?
15811686 //
15821687 // OVERALL: This is a really well-designed library! The micro-context pattern
15831688 // is a fresh take on component composition. Once you understand the core concept
15841689 // of "each component creates its own scoped context", everything else makes sense.
1690+ // The ability to make components work with BOTH props and context (via the value
1691+ // override pattern) is the cherry on top - it means you never have to choose
1692+ // between context-based and prop-based components. You can have both!
15851693 // Would definitely use this for complex data rendering scenarios.
15861694} ) ;
15871695
0 commit comments