Skip to content

Commit 250a6d4

Browse files
committed
update
1 parent 176b35b commit 250a6d4

1 file changed

Lines changed: 118 additions & 10 deletions

File tree

packages/ctablex-core/src/user.test.tsx

Lines changed: 118 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)