From 24a25e0d1a329b6cf0351812a7fa231c79e00ad5 Mon Sep 17 00:00:00 2001 From: Grishma Dhakal Date: Sun, 26 Jul 2026 10:27:39 +0545 Subject: [PATCH] feat(component-templates): make components dynamic via $faker types & migrate React templates to inline CSS --- src/lib/component-templates/codegen-utils.ts | 47 ++++- src/lib/component-templates/comment-item.ts | 91 +++++++--- src/lib/component-templates/dynamic-grid.ts | 38 ++-- src/lib/component-templates/index.ts | 12 +- src/lib/component-templates/post-card.ts | 113 ++++++++---- src/lib/component-templates/product-card.ts | 164 ++++++++++++------ src/lib/component-templates/todo-list.ts | 137 +++++++++++---- .../component-templates/transaction-row.ts | 134 ++++++++------ src/lib/component-templates/user-card.ts | 125 +++++++++---- tests/component-templates.test.ts | 154 +++++++++++++++- 10 files changed, 752 insertions(+), 263 deletions(-) diff --git a/src/lib/component-templates/codegen-utils.ts b/src/lib/component-templates/codegen-utils.ts index bd12632..0e0b5eb 100644 --- a/src/lib/component-templates/codegen-utils.ts +++ b/src/lib/component-templates/codegen-utils.ts @@ -5,7 +5,52 @@ function normalize(s: string): string { } export function safeGet(k: string | undefined): string { - return k ? `(item[${JSON.stringify(k)}] ?? '')` : "''"; + if (!k) return "''"; + if (k.includes(".")) { + const parts = k.split("."); + return `(${parts.map((p) => `item?.[${JSON.stringify(p)}]`).join(".") ?? "''"})`; + } + return `(item[${JSON.stringify(k)}] ?? '')`; +} + +export function mapTsType(dt: string): string { + const d = dt.toLowerCase(); + if (d.includes("boolean")) return "boolean"; + if ( + d.includes("number") || + d.includes("int") || + d.includes("float") || + d.includes("price") || + d.includes("amount") + ) + return "number"; + return "string"; +} + +/** + * Finds a field based on matching its faker dataType string. + */ +export function findFieldByDataType( + fields: SchemaField[], + dataTypePatterns: string[], +): string | undefined { + return fields.find((f) => { + const dt = f.dataType.toLowerCase(); + return dataTypePatterns.some((pattern) => dt.includes(pattern.toLowerCase())); + })?.fieldName; +} + +/** + * Finds all fields matching any of the given faker dataType patterns. + */ +export function filterFieldsByDataType( + fields: SchemaField[], + dataTypePatterns: string[], +): SchemaField[] { + return fields.filter((f) => { + const dt = f.dataType.toLowerCase(); + return dataTypePatterns.some((pattern) => dt.includes(pattern.toLowerCase())); + }); } /** diff --git a/src/lib/component-templates/comment-item.ts b/src/lib/component-templates/comment-item.ts index 0853879..90527d1 100644 --- a/src/lib/component-templates/comment-item.ts +++ b/src/lib/component-templates/comment-item.ts @@ -1,6 +1,8 @@ import type { ComponentTemplate } from "./types"; import { findField, + findFieldByDataType, + mapTsType, buildFetchHook, buildInterface, buildHtmlFetchScript, @@ -12,24 +14,47 @@ export const commentItemTemplate: ComponentTemplate = { id: "comment-item", name: "Comment Item", description: "Avatar + author + comment text list. Handles single or list response.", - requiredFields: ["comment", "author"], - optionalFields: ["avatar", "createdAt", "id"], + requiredFields: [ + "$faker.lorem.paragraph", + "$faker.lorem.sentence", + "$faker.person.fullName", + "$faker.internet.username", + ], + optionalFields: [ + "$faker.image.avatar", + "$faker.date.recent", + "$faker.date.past", + "$faker.string.uuid", + ], code: (fields, endpointUrl) => { - const idField = findField(fields, ["id", "commentid", "uuid"]) ?? "id"; + const idField = + findFieldByDataType(fields, ["string.uuid", "string.nanoid", "number.int"]) ?? + findField(fields, ["id", "commentid", "uuid"]) ?? + fields[0]?.fieldName ?? + "id"; const authorField = - findField(fields, ["author", "fullname", "username", "name"]) ?? "author"; + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "internet.username", + ]) ?? + findField(fields, ["author", "fullname", "username", "name"]) ?? + "author"; const commentField = - findField(fields, ["comment", "message", "content", "text"]) ?? "comment"; - const avatarField = findField(fields, ["avatar", "image", "photo"]); - const createdAtField = findField(fields, ["createdat", "date", "timestamp"]); + findFieldByDataType(fields, ["lorem.paragraph", "lorem.sentence", "lorem.sentences"]) ?? + findField(fields, ["comment", "message", "content", "text"]) ?? + "comment"; + const avatarField = + findFieldByDataType(fields, ["image.avatar", "image.url", "image.datauri"]) ?? + findField(fields, ["avatar", "image", "photo"]); + const createdAtField = + findFieldByDataType(fields, ["date.recent", "date.past", "date.anytime"]) ?? + findField(fields, ["createdat", "date", "timestamp"]); - const interfaceLines = [ - `${idField}: string;`, - `${authorField}: string;`, - `${commentField}: string;`, - ]; - if (avatarField) interfaceLines.push(`${avatarField}: string;`); - if (createdAtField) interfaceLines.push(`${createdAtField}: string;`); + const interfaceLines = fields.map((f) => { + const name = f.fieldName.includes(".") ? `"${f.fieldName}"?` : f.fieldName; + return `${name}: ${mapTsType(f.dataType)};`; + }); return `import { useEffect, useState } from "react"; @@ -40,21 +65,21 @@ ${buildFetchHook("CommentItem", endpointUrl, "Comment")} export function CommentList() { const { data, loading, error } = useCommentItemData(); - if (loading) return
Loading...
; - if (error) return
Error: {error}
; + if (loading) return
Loading...
; + if (error) return
Error: {error}
; if (!data) return null; const comments = Array.isArray(data) ? data : [data]; return ( -
+
{comments.map((comment) => ( -
-${avatarField ? ` {comment.${authorField}}\n` : ""}
-
- {comment.${authorField}} -${createdAtField ? ` {comment.${createdAtField}}\n` : ""}
-

{comment.${commentField}}

+
+${avatarField ? ` {comment.${authorField}}\n` : ""}
+
+ {comment.${authorField}} +${createdAtField ? ` {comment.${createdAtField}}\n` : ""}
+

{comment.${commentField}}

))} @@ -65,11 +90,23 @@ ${createdAtField ? ` {comme }, htmlCode: (fields, endpointUrl) => { const authorField = - findField(fields, ["author", "fullname", "username", "name"]) ?? "author"; + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "internet.username", + ]) ?? + findField(fields, ["author", "fullname", "username", "name"]) ?? + "author"; const commentField = - findField(fields, ["comment", "message", "content", "text"]) ?? "comment"; - const avatarField = findField(fields, ["avatar", "image", "photo"]); - const createdAtField = findField(fields, ["createdat", "date", "timestamp"]); + findFieldByDataType(fields, ["lorem.paragraph", "lorem.sentence", "lorem.sentences"]) ?? + findField(fields, ["comment", "message", "content", "text"]) ?? + "comment"; + const avatarField = + findFieldByDataType(fields, ["image.avatar", "image.url", "image.datauri"]) ?? + findField(fields, ["avatar", "image", "photo"]); + const createdAtField = + findFieldByDataType(fields, ["date.recent", "date.past", "date.anytime"]) ?? + findField(fields, ["createdat", "date", "timestamp"]); const avatarHtml = avatarField ? `\${${safeGet(avatarField)} ? \`avatar\` : \`
\${String(${safeGet(authorField)}).charAt(0).toUpperCase() || 'U'}
\`}` diff --git a/src/lib/component-templates/dynamic-grid.ts b/src/lib/component-templates/dynamic-grid.ts index 6424170..420f7c6 100644 --- a/src/lib/component-templates/dynamic-grid.ts +++ b/src/lib/component-templates/dynamic-grid.ts @@ -170,58 +170,58 @@ ${buildFetchHook("DataItem", endpointUrl, "DataItem")} export function DataCardList() { const { data, loading, error } = useDataItemData(); - if (loading) return
Loading...
; - if (error) return
Error: {error}
; + if (loading) return
Loading...
; + if (error) return
Error: {error}
; if (!data) return null; const items = Array.isArray(data) ? data : [data]; return ( -
+
{items.map((item, idx) => (
-
-
+
+
${ avatarField ? ` {${renderPropAccess(avatarField)} ? ( Avatar ) : ( -
+
{${titleField ? `String(${renderPropAccess(titleField)} ?? "").charAt(0).toUpperCase() || ` : ""} "#"}
)}` - : `
+ : `
{${titleField ? `String(${renderPropAccess(titleField)} ?? "").charAt(0).toUpperCase() || ` : ""} "D"}
` } -
+
${ titleField - ? `

{${renderPropAccess(titleField)}}

\n` - : `

Item #{idx + 1}

\n` -}${subtitleField ? `

{${renderPropAccess(subtitleField)}}

\n` : ""}
+ ? `

{${renderPropAccess(titleField)}}

\n` + : `

Item #{idx + 1}

\n` +}${subtitleField ? `

{${renderPropAccess(subtitleField)}}

\n` : ""}
-${descriptionField ? `

{${renderPropAccess(descriptionField)}}

\n` : ""}
+${descriptionField ? `

{${renderPropAccess(descriptionField)}}

\n` : ""}
${ detailFields.length > 0 - ? `
+ ? `
${detailFields .map((f) => { const propAccess = renderPropAccess(f.fieldName); return ` {${propAccess} !== undefined && ( -
- ${f.fieldName}: - {String(${propAccess})} +
+ ${f.fieldName}: + {String(${propAccess})}
)}`; }) @@ -338,7 +338,7 @@ ${detailFields : ""; const detailsHtml = detailFields.length > 0 - ? `
\${[${detailFields.map((f) => `[${JSON.stringify(f.fieldName)}, item[${JSON.stringify(f.fieldName)}]]`).join(", ")}].filter(([,v]) => v !== undefined).map(([k,v]) => \`
\${k}:\${v}
\`).join('')}
` + ? `
\${[${detailFields.map((f) => `[${JSON.stringify(f.fieldName)}, ${safeGet(f.fieldName)}]`).join(", ")}].filter(([,v]) => v !== undefined && v !== '').map(([k,v]) => \`
\${k}:\${v}
\`).join('')}
` : ""; const renderFn = `item => \`
diff --git a/src/lib/component-templates/index.ts b/src/lib/component-templates/index.ts index 6307ba5..f0e827e 100644 --- a/src/lib/component-templates/index.ts +++ b/src/lib/component-templates/index.ts @@ -32,12 +32,14 @@ function fieldMatches(fieldNameNormalized: string, candidateNormalized: string): /** * Scores every template against the given schema fields and endpoint URL path. + * Matches against field $faker dataTypes (and fieldNames as fallback). * requiredFields matches are weighted heavily (10x); * path segment matches add a strong boost (8x); * optionalFields matches add a smaller boost. */ export function matchTemplates(fields: SchemaField[], endpointPath: string = ""): TemplateMatch[] { const normalizedFieldNames = fields.map((f) => normalize(f.fieldName)); + const normalizedDataTypes = fields.map((f) => normalize(f.dataType)); const normalizedPath = normalize(endpointPath); const results: TemplateMatch[] = COMPONENT_TEMPLATES.map((template) => { @@ -58,13 +60,19 @@ export function matchTemplates(fields: SchemaField[], endpointPath: string = "") for (const required of template.requiredFields) { const normalizedRequired = normalize(required); - if (normalizedFieldNames.some((fn) => fieldMatches(fn, normalizedRequired))) { + if ( + normalizedDataTypes.some((dt) => fieldMatches(dt, normalizedRequired)) || + normalizedFieldNames.some((fn) => fieldMatches(fn, normalizedRequired)) + ) { matchedFields.push(required); } } for (const optional of template.optionalFields) { const normalizedOptional = normalize(optional); - if (normalizedFieldNames.some((fn) => fieldMatches(fn, normalizedOptional))) { + if ( + normalizedDataTypes.some((dt) => fieldMatches(dt, normalizedOptional)) || + normalizedFieldNames.some((fn) => fieldMatches(fn, normalizedOptional)) + ) { matchedFields.push(optional); } } diff --git a/src/lib/component-templates/post-card.ts b/src/lib/component-templates/post-card.ts index f2f2f60..6126d38 100644 --- a/src/lib/component-templates/post-card.ts +++ b/src/lib/component-templates/post-card.ts @@ -1,6 +1,8 @@ import type { ComponentTemplate } from "./types"; import { findField, + findFieldByDataType, + mapTsType, buildFetchHook, buildInterface, buildHtmlFetchScript, @@ -12,24 +14,57 @@ export const postCardTemplate: ComponentTemplate = { id: "post-card", name: "Post Card", description: "Cover image + title + excerpt list. Handles single or list response.", - requiredFields: ["title", "content"], - optionalFields: ["author", "coverImage", "publishedAt", "tags", "id"], + requiredFields: [ + "$faker.lorem.sentence", + "$faker.lorem.paragraph", + "$faker.lorem.paragraphs", + "$faker.book.title", + ], + optionalFields: [ + "$faker.person.fullName", + "$faker.internet.username", + "$faker.image.url", + "$faker.date.anytime", + "$faker.date.past", + "$faker.date.recent", + "$faker.string.uuid", + ], code: (fields, endpointUrl) => { - const idField = findField(fields, ["id", "postid", "uuid"]) ?? "id"; - const titleField = findField(fields, ["title"]) ?? "title"; - const contentField = findField(fields, ["content", "body", "paragraph"]) ?? "content"; - const authorField = findField(fields, ["author", "fullname", "username"]); - const coverImageField = findField(fields, ["coverimage", "image", "thumbnail"]); - const publishedAtField = findField(fields, ["publishedat", "createdat", "date"]); + const idField = + findFieldByDataType(fields, ["string.uuid", "string.nanoid", "number.int"]) ?? + findField(fields, ["id", "postid", "uuid"]) ?? + fields[0]?.fieldName ?? + "id"; + const titleField = + findFieldByDataType(fields, ["book.title", "lorem.sentence", "company.catchPhrase"]) ?? + findField(fields, ["title"]) ?? + "title"; + const contentField = + findFieldByDataType(fields, ["lorem.paragraph", "lorem.paragraphs", "person.bio"]) ?? + findField(fields, ["content", "body", "paragraph"]) ?? + "content"; + const authorField = + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "internet.username", + "book.author", + ]) ?? findField(fields, ["author", "fullname", "username"]); + const coverImageField = + findFieldByDataType(fields, ["image.url", "image.datauri"]) ?? + findField(fields, ["coverimage", "image", "thumbnail"]); + const publishedAtField = + findFieldByDataType(fields, [ + "date.anytime", + "date.past", + "date.future", + "date.recent", + ]) ?? findField(fields, ["publishedat", "createdat", "date"]); - const interfaceLines = [ - `${idField}: string;`, - `${titleField}: string;`, - `${contentField}: string;`, - ]; - if (authorField) interfaceLines.push(`${authorField}: string;`); - if (coverImageField) interfaceLines.push(`${coverImageField}: string;`); - if (publishedAtField) interfaceLines.push(`${publishedAtField}: string;`); + const interfaceLines = fields.map((f) => { + const name = f.fieldName.includes(".") ? `"${f.fieldName}"?` : f.fieldName; + return `${name}: ${mapTsType(f.dataType)};`; + }); return `import { useEffect, useState } from "react"; @@ -40,20 +75,20 @@ ${buildFetchHook("PostCard", endpointUrl, "Post")} export function PostCard() { const { data, loading, error } = usePostCardData(); - if (loading) return
Loading...
; - if (error) return
Error: {error}
; + if (loading) return
Loading...
; + if (error) return
Error: {error}
; if (!data) return null; const posts = Array.isArray(data) ? data : [data]; return ( -
+
{posts.map((post) => ( -
-${coverImageField ? ` {post.${titleField}}\n` : ""}
-

{post.${titleField}}

-

{post.${contentField}}

-
+
+${coverImageField ? ` {post.${titleField}}\n` : ""}
+

{post.${titleField}}

+

{post.${contentField}}

+
${authorField ? ` {post.${authorField}}\n` : ""}${publishedAtField ? ` {post.${publishedAtField}}\n` : ""}
@@ -64,11 +99,31 @@ ${authorField ? ` {post.${authorField}}\n` : ""}${publ `; }, htmlCode: (fields, endpointUrl) => { - const titleField = findField(fields, ["title"]) ?? "title"; - const contentField = findField(fields, ["content", "body", "paragraph"]) ?? "content"; - const authorField = findField(fields, ["author", "fullname", "username"]); - const coverImageField = findField(fields, ["coverimage", "image", "thumbnail"]); - const publishedAtField = findField(fields, ["publishedat", "createdat", "date"]); + const titleField = + findFieldByDataType(fields, ["book.title", "lorem.sentence", "company.catchPhrase"]) ?? + findField(fields, ["title"]) ?? + "title"; + const contentField = + findFieldByDataType(fields, ["lorem.paragraph", "lorem.paragraphs", "person.bio"]) ?? + findField(fields, ["content", "body", "paragraph"]) ?? + "content"; + const authorField = + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "internet.username", + "book.author", + ]) ?? findField(fields, ["author", "fullname", "username"]); + const coverImageField = + findFieldByDataType(fields, ["image.url", "image.datauri"]) ?? + findField(fields, ["coverimage", "image", "thumbnail"]); + const publishedAtField = + findFieldByDataType(fields, [ + "date.anytime", + "date.past", + "date.future", + "date.recent", + ]) ?? findField(fields, ["publishedat", "createdat", "date"]); const coverHtml = coverImageField ? `\${${safeGet(coverImageField)} ? \`cover\` : ''}` diff --git a/src/lib/component-templates/product-card.ts b/src/lib/component-templates/product-card.ts index 11cbba2..a4462db 100644 --- a/src/lib/component-templates/product-card.ts +++ b/src/lib/component-templates/product-card.ts @@ -1,6 +1,8 @@ import type { ComponentTemplate } from "./types"; import { findField, + findFieldByDataType, + mapTsType, buildFetchHook, buildInterface, buildHtmlFetchScript, @@ -12,34 +14,66 @@ export const productCardTemplate: ComponentTemplate = { id: "product-card", name: "Product Card", description: "Image + product info card. Gracefully handles present/absent images & tags.", - requiredFields: ["name", "price", "productname", "amount"], - optionalFields: ["description", "category", "image", "inStock", "rating", "brand", "id"], + requiredFields: [ + "$faker.commerce.productName", + "$faker.commerce.product", + "$faker.commerce.price", + "$faker.finance.amount", + ], + optionalFields: [ + "$faker.commerce.productDescription", + "$faker.commerce.department", + "$faker.company.name", + "$faker.image.url", + "$faker.datatype.boolean", + "$faker.number.float", + "$faker.string.uuid", + ], code: (fields, endpointUrl) => { - const idField = findField(fields, ["id", "productid", "uuid"]) ?? "id"; - const nameField = findField(fields, ["name", "productname", "title"]) ?? "name"; - const priceField = findField(fields, ["price", "amount", "cost"]) ?? "price"; - const imageField = findField(fields, ["image", "photo", "picture", "thumbnail", "cover"]); - const descriptionField = findField(fields, [ - "description", - "productdescription", - "summary", - ]); - const categoryField = findField(fields, ["category", "department", "genre"]); - const brandField = findField(fields, ["brand", "company", "manufacturer"]); - const inStockField = findField(fields, ["instock", "available", "isinstock"]); - const ratingField = findField(fields, ["rating", "score", "stars"]); + const idField = + findFieldByDataType(fields, ["string.uuid", "string.nanoid", "number.int"]) ?? + findField(fields, ["id", "productid", "uuid"]) ?? + fields[0]?.fieldName ?? + "id"; + const nameField = + findFieldByDataType(fields, [ + "commerce.productName", + "commerce.product", + "food.dish", + "vehicle.vehicle", + ]) ?? + findField(fields, ["name", "productname", "title"]) ?? + "name"; + const priceField = + findFieldByDataType(fields, ["commerce.price", "finance.amount"]) ?? + findField(fields, ["price", "amount", "cost"]) ?? + "price"; + const imageField = + findFieldByDataType(fields, ["image.url", "image.datauri"]) ?? + findField(fields, ["image", "photo", "picture", "thumbnail", "cover"]); + const descriptionField = + findFieldByDataType(fields, [ + "commerce.productDescription", + "lorem.paragraph", + "company.catchPhrase", + ]) ?? findField(fields, ["description", "productdescription", "summary"]); + const categoryField = + findFieldByDataType(fields, ["commerce.department", "book.genre"]) ?? + findField(fields, ["category", "department", "genre"]); + const brandField = + findFieldByDataType(fields, ["company.name", "vehicle.manufacturer"]) ?? + findField(fields, ["brand", "company", "manufacturer"]); + const inStockField = + findFieldByDataType(fields, ["datatype.boolean"]) ?? + findField(fields, ["instock", "available", "isinstock"]); + const ratingField = + findFieldByDataType(fields, ["number.float", "number.int"]) ?? + findField(fields, ["rating", "score", "stars"]); - const interfaceLines = [ - `${idField}: string;`, - `${nameField}: string;`, - `${priceField}: string | number;`, - ]; - if (imageField) interfaceLines.push(`${imageField}: string;`); - if (descriptionField) interfaceLines.push(`${descriptionField}: string;`); - if (categoryField) interfaceLines.push(`${categoryField}: string;`); - if (brandField) interfaceLines.push(`${brandField}: string;`); - if (inStockField) interfaceLines.push(`${inStockField}: boolean;`); - if (ratingField) interfaceLines.push(`${ratingField}: string | number;`); + const interfaceLines = fields.map((f) => { + const name = f.fieldName.includes(".") ? `"${f.fieldName}"?` : f.fieldName; + return `${name}: ${mapTsType(f.dataType)};`; + }); return `import { useEffect, useState } from "react"; @@ -50,46 +84,46 @@ ${buildFetchHook("ProductCard", endpointUrl, "Product")} export function ProductCard() { const { data, loading, error } = useProductCardData(); - if (loading) return
Loading...
; - if (error) return
Error: {error}
; + if (loading) return
Loading...
; + if (error) return
Error: {error}
; if (!data) return null; const products = Array.isArray(data) ? data : [data]; return ( -
+
{products.map((product) => ( -
+
${ imageField ? ` {product.${imageField} ? ( - {product.${nameField}} + {product.${nameField}} ) : ( -
+
📦
)}` - : `
+ : `
📦
` } -
-
-${categoryField ? ` {product.${categoryField} && {product.${categoryField}}}\n` : ""}${brandField ? ` {product.${brandField} && {product.${brandField}}}\n` : ""}
-

{product.${nameField}}

-${descriptionField ? ` {product.${descriptionField} &&

{product.${descriptionField}}

}\n` : ""}
+
+
+${categoryField ? ` {product.${categoryField} && {product.${categoryField}}}\n` : ""}${brandField ? ` {product.${brandField} && {product.${brandField}}}\n` : ""}
+

{product.${nameField}}

+${descriptionField ? ` {product.${descriptionField} &&

{product.${descriptionField}}

}\n` : ""}
-
+
- \${product.${priceField}} -${ratingField ? ` {product.${ratingField} && Rating: {product.${ratingField}}}\n` : ""}
+ \${product.${priceField}} +${ratingField ? ` {product.${ratingField} && Rating: {product.${ratingField}}}\n` : ""}
${ inStockField ? ` {product.${inStockField} !== undefined && ( - + {product.${inStockField} ? "In stock" : "Out of stock"} )}\n` @@ -103,18 +137,40 @@ ${ `; }, htmlCode: (fields, endpointUrl) => { - const nameField = findField(fields, ["name", "productname", "title"]) ?? "name"; - const priceField = findField(fields, ["price", "amount", "cost"]) ?? "price"; - const imageField = findField(fields, ["image", "photo", "picture", "thumbnail", "cover"]); - const descriptionField = findField(fields, [ - "description", - "productdescription", - "summary", - ]); - const categoryField = findField(fields, ["category", "department", "genre"]); - const brandField = findField(fields, ["brand", "company", "manufacturer"]); - const inStockField = findField(fields, ["instock", "available", "isinstock"]); - const ratingField = findField(fields, ["rating", "score", "stars"]); + const nameField = + findFieldByDataType(fields, [ + "commerce.productName", + "commerce.product", + "food.dish", + "vehicle.vehicle", + ]) ?? + findField(fields, ["name", "productname", "title"]) ?? + "name"; + const priceField = + findFieldByDataType(fields, ["commerce.price", "finance.amount"]) ?? + findField(fields, ["price", "amount", "cost"]) ?? + "price"; + const imageField = + findFieldByDataType(fields, ["image.url", "image.datauri"]) ?? + findField(fields, ["image", "photo", "picture", "thumbnail", "cover"]); + const descriptionField = + findFieldByDataType(fields, [ + "commerce.productDescription", + "lorem.paragraph", + "company.catchPhrase", + ]) ?? findField(fields, ["description", "productdescription", "summary"]); + const categoryField = + findFieldByDataType(fields, ["commerce.department", "book.genre"]) ?? + findField(fields, ["category", "department", "genre"]); + const brandField = + findFieldByDataType(fields, ["company.name", "vehicle.manufacturer"]) ?? + findField(fields, ["brand", "company", "manufacturer"]); + const inStockField = + findFieldByDataType(fields, ["datatype.boolean"]) ?? + findField(fields, ["instock", "available", "isinstock"]); + const ratingField = + findFieldByDataType(fields, ["number.float", "number.int"]) ?? + findField(fields, ["rating", "score", "stars"]); const imgHtml = imageField ? `\${${safeGet(imageField)} ? \`product\` : '
📦
'}` diff --git a/src/lib/component-templates/todo-list.ts b/src/lib/component-templates/todo-list.ts index 0f9180b..ab21dcf 100644 --- a/src/lib/component-templates/todo-list.ts +++ b/src/lib/component-templates/todo-list.ts @@ -1,6 +1,8 @@ import type { ComponentTemplate } from "./types"; import { findField, + findFieldByDataType, + mapTsType, buildFetchHook, buildInterface, buildHtmlFetchScript, @@ -12,29 +14,61 @@ export const todoListTemplate: ComponentTemplate = { id: "todo-list", name: "Todo List", description: "Checkbox list adaptively rendering title, status, priority, dates & tags.", - requiredFields: ["title", "completed", "task", "done"], - optionalFields: ["dueDate", "priority", "category", "description", "assignee", "id"], + requiredFields: ["$faker.lorem.words", "$faker.lorem.sentence", "$faker.datatype.boolean"], + optionalFields: [ + "$faker.date.future", + "$faker.date.recent", + "$faker.person.firstName", + "$faker.person.fullName", + "$faker.lorem.paragraph", + "$faker.string.uuid", + ], code: (fields, endpointUrl) => { - const idField = findField(fields, ["id", "todoid", "uuid"]) ?? "id"; - const titleField = findField(fields, ["title", "task", "name", "summary"]) ?? "title"; + const idField = + findFieldByDataType(fields, ["string.uuid", "string.nanoid", "number.int"]) ?? + findField(fields, ["id", "todoid", "uuid"]) ?? + fields[0]?.fieldName ?? + "id"; + const titleField = + findFieldByDataType(fields, [ + "lorem.words", + "lorem.word", + "lorem.sentence", + "system.fileName", + ]) ?? + findField(fields, ["title", "task", "name", "summary"]) ?? + "title"; const completedField = - findField(fields, ["completed", "done", "isdone", "iscompleted"]) ?? "completed"; - const dueDateField = findField(fields, ["duedate", "deadline", "date", "due"]); - const priorityField = findField(fields, ["priority", "level", "urgency"]); - const categoryField = findField(fields, ["category", "tag", "project", "label"]); - const assigneeField = findField(fields, ["assignee", "assignedto", "user", "owner"]); - const descriptionField = findField(fields, ["description", "details", "notes"]); + findFieldByDataType(fields, ["datatype.boolean"]) ?? + findField(fields, ["completed", "done", "isdone", "iscompleted"]) ?? + "completed"; + const dueDateField = + findFieldByDataType(fields, [ + "date.future", + "date.recent", + "date.anytime", + "date.soon", + ]) ?? findField(fields, ["duedate", "deadline", "date", "due"]); + const priorityField = + findFieldByDataType(fields, ["color.human", "lorem.word"]) ?? + findField(fields, ["priority", "level", "urgency"]); + const categoryField = + findFieldByDataType(fields, ["commerce.department", "book.genre"]) ?? + findField(fields, ["category", "tag", "project", "label"]); + const assigneeField = + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "internet.username", + ]) ?? findField(fields, ["assignee", "assignedto", "user", "owner"]); + const descriptionField = + findFieldByDataType(fields, ["lorem.paragraph", "lorem.sentence"]) ?? + findField(fields, ["description", "details", "notes"]); - const interfaceLines = [ - `${idField}: string;`, - `${titleField}: string;`, - `${completedField}: boolean;`, - ]; - if (dueDateField) interfaceLines.push(`${dueDateField}: string;`); - if (priorityField) interfaceLines.push(`${priorityField}: string;`); - if (categoryField) interfaceLines.push(`${categoryField}: string;`); - if (assigneeField) interfaceLines.push(`${assigneeField}: string;`); - if (descriptionField) interfaceLines.push(`${descriptionField}: string;`); + const interfaceLines = fields.map((f) => { + const name = f.fieldName.includes(".") ? `"${f.fieldName}"?` : f.fieldName; + return `${name}: ${mapTsType(f.dataType)};`; + }); return `import { useEffect, useState } from "react"; @@ -45,37 +79,37 @@ ${buildFetchHook("TodoList", endpointUrl, "Todo")} export function TodoList() { const { data, loading, error } = useTodoListData(); - if (loading) return
Loading...
; - if (error) return
Error: {error}
; + if (loading) return
Loading...
; + if (error) return
Error: {error}
; if (!data) return null; const todos = Array.isArray(data) ? data : [data]; return ( -
    - {todos.map((todo) => ( -
  • -
    +
      + {todos.map((todo, idx) => ( +
    • +
      - + {todo.${titleField}} -${priorityField ? ` {todo.${priorityField} && {todo.${priorityField}}}\n` : ""}${categoryField ? ` {todo.${categoryField} && {todo.${categoryField}}}\n` : ""}
      +${priorityField ? ` {todo.${priorityField} && {todo.${priorityField}}}\n` : ""}${categoryField ? ` {todo.${categoryField} && {todo.${categoryField}}}\n` : ""}
    ${ descriptionField ? ` {todo.${descriptionField} && ( -

    {todo.${descriptionField}}

    +

    {todo.${descriptionField}}

    )}\n` : "" }${ dueDateField || assigneeField - ? `
    + ? `
    ${dueDateField ? ` {todo.${dueDateField} && Due: {todo.${dueDateField}}}\n` : ""}${assigneeField ? ` {todo.${assigneeField} && Assignee: {todo.${assigneeField}}}\n` : ""}
    \n` : "" }
  • @@ -86,14 +120,41 @@ ${dueDateField ? ` {todo.${dueDateField} && Due: {todo.${dueDat `; }, htmlCode: (fields, endpointUrl) => { - const titleField = findField(fields, ["title", "task", "name", "summary"]) ?? "title"; + const titleField = + findFieldByDataType(fields, [ + "lorem.words", + "lorem.word", + "lorem.sentence", + "system.fileName", + ]) ?? + findField(fields, ["title", "task", "name", "summary"]) ?? + "title"; const completedField = - findField(fields, ["completed", "done", "isdone", "iscompleted"]) ?? "completed"; - const dueDateField = findField(fields, ["duedate", "deadline", "date", "due"]); - const priorityField = findField(fields, ["priority", "level", "urgency"]); - const categoryField = findField(fields, ["category", "tag", "project", "label"]); - const descriptionField = findField(fields, ["description", "details", "notes"]); - const assigneeField = findField(fields, ["assignee", "assignedto", "user", "owner"]); + findFieldByDataType(fields, ["datatype.boolean"]) ?? + findField(fields, ["completed", "done", "isdone", "iscompleted"]) ?? + "completed"; + const dueDateField = + findFieldByDataType(fields, [ + "date.future", + "date.recent", + "date.anytime", + "date.soon", + ]) ?? findField(fields, ["duedate", "deadline", "date", "due"]); + const priorityField = + findFieldByDataType(fields, ["color.human", "lorem.word"]) ?? + findField(fields, ["priority", "level", "urgency"]); + const categoryField = + findFieldByDataType(fields, ["commerce.department", "book.genre"]) ?? + findField(fields, ["category", "tag", "project", "label"]); + const descriptionField = + findFieldByDataType(fields, ["lorem.paragraph", "lorem.sentence"]) ?? + findField(fields, ["description", "details", "notes"]); + const assigneeField = + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "internet.username", + ]) ?? findField(fields, ["assignee", "assignedto", "user", "owner"]); const priorityBadge = priorityField ? `\${${safeGet(priorityField)} ? \`\${${safeGet(priorityField)}}\` : ''}` diff --git a/src/lib/component-templates/transaction-row.ts b/src/lib/component-templates/transaction-row.ts index ebe9bac..f263d12 100644 --- a/src/lib/component-templates/transaction-row.ts +++ b/src/lib/component-templates/transaction-row.ts @@ -1,6 +1,8 @@ import type { ComponentTemplate } from "./types"; import { findField, + findFieldByDataType, + mapTsType, buildFetchHook, buildInterface, buildHtmlFetchScript, @@ -12,36 +14,54 @@ export const transactionRowTemplate: ComponentTemplate = { id: "transaction-row", name: "Transaction Statement Table", description: "Account statement / financial log table row list adapting to present columns.", - requiredFields: ["amount", "date", "price"], + requiredFields: [ + "$faker.finance.amount", + "$faker.commerce.price", + "$faker.date.anytime", + "$faker.date.past", + "$faker.date.recent", + ], optionalFields: [ - "transactionId", - "currency", - "status", - "description", - "category", - "merchant", - "id", + "$faker.finance.accountNumber", + "$faker.finance.currencyCode", + "$faker.company.name", + "$faker.commerce.department", + "$faker.string.uuid", ], code: (fields, endpointUrl) => { - const idField = findField(fields, ["id", "transactionid", "uuid"]) ?? "id"; - const amountField = findField(fields, ["amount", "price", "value", "cost"]) ?? "amount"; - const dateField = findField(fields, ["date", "createdat", "timestamp", "time"]) ?? "date"; - const currencyField = findField(fields, ["currency", "currencycode", "symbol"]); - const statusField = findField(fields, ["status", "state"]); - const descriptionField = findField(fields, ["description", "note", "title", "details"]); - const merchantField = findField(fields, ["merchant", "payee", "company", "vendor"]); - const categoryField = findField(fields, ["category", "type", "department"]); + const idField = + findFieldByDataType(fields, ["string.uuid", "string.nanoid", "number.int"]) ?? + findField(fields, ["id", "transactionid", "uuid"]) ?? + fields[0]?.fieldName ?? + "id"; + const amountField = + findFieldByDataType(fields, ["finance.amount", "commerce.price"]) ?? + findField(fields, ["amount", "price", "value", "cost"]) ?? + "amount"; + const dateField = + findFieldByDataType(fields, ["date.anytime", "date.past", "date.recent"]) ?? + findField(fields, ["date", "createdat", "timestamp", "time"]) ?? + "date"; + const currencyField = + findFieldByDataType(fields, ["finance.currencyCode", "finance.currencySymbol"]) ?? + findField(fields, ["currency", "currencycode", "symbol"]); + const statusField = + findFieldByDataType(fields, ["datatype.boolean"]) ?? + findField(fields, ["status", "state"]); + const descriptionField = + findFieldByDataType(fields, ["lorem.sentence", "company.catchPhrase"]) ?? + findField(fields, ["description", "note", "title", "details"]); + const merchantField = + findFieldByDataType(fields, ["company.name", "person.fullName"]) ?? + findField(fields, ["merchant", "payee", "company", "vendor"]); + const categoryField = + findFieldByDataType(fields, ["commerce.department", "book.genre"]) ?? + findField(fields, ["category", "type", "department"]); - const interfaceLines = [ - `${idField}: string;`, - `${amountField}: string | number;`, - `${dateField}: string;`, - ]; - if (currencyField) interfaceLines.push(`${currencyField}: string;`); - if (statusField) interfaceLines.push(`${statusField}: string;`); - if (descriptionField) interfaceLines.push(`${descriptionField}: string;`); - if (merchantField) interfaceLines.push(`${merchantField}: string;`); - if (categoryField) interfaceLines.push(`${categoryField}: string;`); + const interfaceLines = fields.map((f) => { + const name = f.fieldName.includes(".") ? `"${f.fieldName}"?` : f.fieldName; + return `${name}: ${mapTsType(f.dataType)};`; + }); return `import { useEffect, useState } from "react"; @@ -52,36 +72,36 @@ ${buildFetchHook("TransactionRow", endpointUrl, "Transaction")} export function TransactionTable() { const { data, loading, error } = useTransactionRowData(); - if (loading) return
    Loading...
    ; - if (error) return
    Error: {error}
    ; + if (loading) return
    Loading...
    ; + if (error) return
    Error: {error}
    ; if (!data) return null; const transactions = Array.isArray(data) ? data : [data]; return ( -
    - +
    +
    - - -${merchantField ? ` \n` : ""}${descriptionField ? ` \n` : ""}${categoryField ? ` \n` : ""}${statusField ? ` \n` : ""} + + +${merchantField ? ` \n` : ""}${descriptionField ? ` \n` : ""}${categoryField ? ` \n` : ""}${statusField ? ` \n` : ""} - - {transactions.map((tx) => ( - - -${merchantField ? ` \n` : ""}${descriptionField ? ` \n` : ""}${categoryField ? ` \n` : ""}${ + + {transactions.map((tx, idx) => ( + + +${merchantField ? ` \n` : ""}${descriptionField ? ` \n` : ""}${categoryField ? ` \n` : ""}${ statusField - ? ` \n` : "" - } @@ -94,13 +114,29 @@ ${merchantField ? ` ` : ""; diff --git a/src/lib/component-templates/user-card.ts b/src/lib/component-templates/user-card.ts index 66f44fa..2831854 100644 --- a/src/lib/component-templates/user-card.ts +++ b/src/lib/component-templates/user-card.ts @@ -1,6 +1,8 @@ import type { ComponentTemplate } from "./types"; import { findField, + findFieldByDataType, + mapTsType, buildFetchHook, buildInterface, buildHtmlFetchScript, @@ -12,25 +14,56 @@ export const userCardTemplate: ComponentTemplate = { id: "user-card", name: "User Card", description: "Avatar + profile details grid. Adapts to present fields.", - requiredFields: ["username", "fullName", "name"], - optionalFields: ["email", "jobTitle", "phone", "bio", "avatar", "id"], + requiredFields: [ + "$faker.person.fullName", + "$faker.person.firstName", + "$faker.person.lastName", + "$faker.internet.username", + ], + optionalFields: [ + "$faker.internet.email", + "$faker.person.jobTitle", + "$faker.phone.number", + "$faker.person.bio", + "$faker.image.avatar", + "$faker.string.uuid", + ], code: (fields, endpointUrl) => { - const idField = findField(fields, ["id", "userid", "uuid"]) ?? "id"; - const usernameField = findField(fields, ["username", "handle"]); - const fullNameField = findField(fields, ["fullname", "name", "displayname"]) ?? "name"; - const avatarField = findField(fields, ["avatar", "image", "photo", "picture"]); - const emailField = findField(fields, ["email"]); - const jobTitleField = findField(fields, ["jobtitle", "role", "title"]); - const phoneField = findField(fields, ["phone", "phonenumber", "mobile", "cell"]); - const bioField = findField(fields, ["bio", "about", "description"]); + const idField = + findFieldByDataType(fields, ["string.uuid", "string.nanoid", "number.int"]) ?? + findField(fields, ["id", "userid", "uuid"]) ?? + fields[0]?.fieldName ?? + "id"; + const fullNameField = + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "person.lastName", + ]) ?? + findField(fields, ["fullname", "name", "displayname"]) ?? + "name"; + const usernameField = + findFieldByDataType(fields, ["internet.username"]) ?? + findField(fields, ["username", "handle"]); + const avatarField = + findFieldByDataType(fields, ["image.avatar", "image.url", "image.datauri"]) ?? + findField(fields, ["avatar", "image", "photo", "picture"]); + const emailField = + findFieldByDataType(fields, ["internet.email"]) ?? findField(fields, ["email"]); + const jobTitleField = + findFieldByDataType(fields, ["person.jobTitle"]) ?? + findField(fields, ["jobtitle", "role", "title"]); + const phoneField = + findFieldByDataType(fields, ["phone.number", "phone.imei"]) ?? + findField(fields, ["phone", "phonenumber", "mobile", "cell"]); + const bioField = + findFieldByDataType(fields, ["person.bio", "lorem.paragraph", "lorem.sentence"]) ?? + findField(fields, ["bio", "about", "description"]); - const interfaceLines = [`${idField}: string;`, `${fullNameField}: string;`]; - if (usernameField) interfaceLines.push(`${usernameField}: string;`); - if (avatarField) interfaceLines.push(`${avatarField}: string;`); - if (emailField) interfaceLines.push(`${emailField}: string;`); - if (jobTitleField) interfaceLines.push(`${jobTitleField}: string;`); - if (phoneField) interfaceLines.push(`${phoneField}: string;`); - if (bioField) interfaceLines.push(`${bioField}: string;`); + const interfaceLines = fields.map((f) => { + const name = f.fieldName.includes(".") ? `"${f.fieldName}"?` : f.fieldName; + return `${name}: ${mapTsType(f.dataType)};`; + }); return `import { useEffect, useState } from "react"; @@ -41,36 +74,36 @@ ${buildFetchHook("UserCard", endpointUrl, "User")} export function UserCard() { const { data, loading, error } = useUserCardData(); - if (loading) return
    Loading...
    ; - if (error) return
    Error: {error}
    ; + if (loading) return
    Loading...
    ; + if (error) return
    Error: {error}
    ; if (!data) return null; const users = Array.isArray(data) ? data : [data]; return ( -
    +
    {users.map((user) => ( -
    -
    +
    +
    ${ avatarField ? ` {user.${fullNameField}}` - : `
    + : `
    {user.${fullNameField}?.charAt(0) || "U"}
    ` } -
    -

    {user.${fullNameField}}

    -${usernameField ? `

    @{user.${usernameField}}

    \n` : ""}${jobTitleField ? `

    {user.${jobTitleField}}

    \n` : ""}
    +
    +

    {user.${fullNameField}}

    +${usernameField ? `

    @{user.${usernameField}}

    \n` : ""}${jobTitleField ? `

    {user.${jobTitleField}}

    \n` : ""}
    -${bioField ? `

    {user.${bioField}}

    \n` : ""}${ +${bioField ? `

    {user.${bioField}}

    \n` : ""}${ emailField || phoneField - ? `
    -${emailField ? ` {user.${emailField} &&

    Email: {user.${emailField}}

    }\n` : ""}${phoneField ? ` {user.${phoneField} &&

    Phone: {user.${phoneField}}

    }\n` : ""}
    \n` + ? `
    +${emailField ? ` {user.${emailField} &&

    Email: {user.${emailField}}

    }\n` : ""}${phoneField ? ` {user.${phoneField} &&

    Phone: {user.${phoneField}}

    }\n` : ""}
    \n` : "" }
    ))} @@ -80,13 +113,31 @@ ${emailField ? ` {user.${emailField} &&

    Email `; }, htmlCode: (fields, endpointUrl) => { - const fullNameField = findField(fields, ["fullname", "name", "displayname"]) ?? "name"; - const usernameField = findField(fields, ["username", "handle"]); - const avatarField = findField(fields, ["avatar", "image", "photo", "picture"]); - const emailField = findField(fields, ["email"]); - const jobTitleField = findField(fields, ["jobtitle", "role", "title"]); - const phoneField = findField(fields, ["phone", "phonenumber", "mobile", "cell"]); - const bioField = findField(fields, ["bio", "about", "description"]); + const fullNameField = + findFieldByDataType(fields, [ + "person.fullName", + "person.firstName", + "person.lastName", + ]) ?? + findField(fields, ["fullname", "name", "displayname"]) ?? + "name"; + const usernameField = + findFieldByDataType(fields, ["internet.username"]) ?? + findField(fields, ["username", "handle"]); + const avatarField = + findFieldByDataType(fields, ["image.avatar", "image.url", "image.datauri"]) ?? + findField(fields, ["avatar", "image", "photo", "picture"]); + const emailField = + findFieldByDataType(fields, ["internet.email"]) ?? findField(fields, ["email"]); + const jobTitleField = + findFieldByDataType(fields, ["person.jobTitle"]) ?? + findField(fields, ["jobtitle", "role", "title"]); + const phoneField = + findFieldByDataType(fields, ["phone.number", "phone.imei"]) ?? + findField(fields, ["phone", "phonenumber", "mobile", "cell"]); + const bioField = + findFieldByDataType(fields, ["person.bio", "lorem.paragraph", "lorem.sentence"]) ?? + findField(fields, ["bio", "about", "description"]); const avatarHtml = avatarField ? `\${${safeGet(avatarField)} ? \`\${${safeGet(fullNameField)}}\` : \`

    \${String(${safeGet(fullNameField)}).charAt(0).toUpperCase() || 'U'}
    \`}` diff --git a/tests/component-templates.test.ts b/tests/component-templates.test.ts index 85d3712..1678d1c 100644 --- a/tests/component-templates.test.ts +++ b/tests/component-templates.test.ts @@ -14,25 +14,41 @@ import { productCardTemplate } from "~/lib/component-templates/product-card"; import { todoListTemplate } from "~/lib/component-templates/todo-list"; import { transactionRowTemplate } from "~/lib/component-templates/transaction-row"; import { commentItemTemplate } from "~/lib/component-templates/comment-item"; -import { findField, buildFetchHook, buildInterface } from "~/lib/component-templates/codegen-utils"; +import { + findField, + findFieldByDataType, + buildFetchHook, + buildInterface, +} from "~/lib/component-templates/codegen-utils"; describe("Component Templates Library", () => { describe("matchTemplates & getTopMatches", () => { - it("should rank specialized templates (e.g. user-card) when fields match user schema", () => { + it("should rank specialized templates (e.g. user-card) when $faker data types match", () => { const fields = [ - { id: "1", fieldName: "username", dataType: "$faker.internet.username" }, - { id: "2", fieldName: "email", dataType: "$faker.internet.email" }, + { id: "1", fieldName: "val_a", dataType: "$faker.person.fullName" }, + { id: "2", fieldName: "val_b", dataType: "$faker.internet.username" }, ]; - const matches = getTopMatches(fields, "/api/users"); + const matches = getTopMatches(fields, "/api/custom-users"); expect(matches.length).toBeGreaterThan(0); expect(matches[0]!.template.id).toBe("user-card"); }); + it("should rank post-card when $faker data types for title and content match", () => { + const fields = [ + { id: "1", fieldName: "f_title", dataType: "$faker.book.title" }, + { id: "2", fieldName: "f_body", dataType: "$faker.lorem.paragraph" }, + ]; + const matches = getTopMatches(fields, "/api/articles"); + + expect(matches.length).toBeGreaterThan(0); + expect(matches[0]!.template.id).toBe("post-card"); + }); + it("should rank dynamicGridTemplate when path keyword matches", () => { const fields = fieldsFromSchema({ - id: "uuid", - name: "fullName", + id: "$faker.string.uuid", + name: "$faker.person.fullName", }); const matches = matchTemplates(fields, "/users"); expect(matches.some((m) => m.template.id === "dynamic-grid")).toBe(true); @@ -40,6 +56,17 @@ describe("Component Templates Library", () => { }); describe("codegen-utils", () => { + it("findFieldByDataType matches fields by $faker dataType pattern regardless of field key", () => { + const fields = [ + { id: "1", fieldName: "prop_1", dataType: "$faker.person.fullName" }, + { id: "2", fieldName: "prop_2", dataType: "$faker.internet.email" }, + ]; + + expect(findFieldByDataType(fields, ["person.fullName"])).toBe("prop_1"); + expect(findFieldByDataType(fields, ["internet.email"])).toBe("prop_2"); + expect(findFieldByDataType(fields, ["nonexistent"])).toBeUndefined(); + }); + it("findField case and separator insensitively matches schema field names", () => { const fields = [ { id: "1", fieldName: "user_name", dataType: "$faker.internet.username" }, @@ -67,6 +94,119 @@ describe("Component Templates Library", () => { }); }); + describe("React Component Templates (No Tailwind CSS & Fully Dynamic)", () => { + it("userCardTemplate generates React code without Tailwind CSS classes and works with arbitrary field keys", () => { + const fields = [ + { id: "1", fieldName: "col_id", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "col_name", dataType: "$faker.person.fullName" }, + { id: "3", fieldName: "col_user", dataType: "$faker.internet.username" }, + { id: "4", fieldName: "col_img", dataType: "$faker.image.avatar" }, + { id: "5", fieldName: "col_bio", dataType: "$faker.person.bio" }, + ]; + + const code = generateCode(userCardTemplate, fields, "https://mock.api/v1/users"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("col_id"); + expect(code).toContain("col_name"); + expect(code).toContain("col_user"); + expect(code).toContain("col_img"); + expect(code).toContain("col_bio"); + }); + + it("postCardTemplate generates React code without Tailwind CSS classes", () => { + const fields = [ + { id: "1", fieldName: "x_id", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "x_title", dataType: "$faker.book.title" }, + { id: "3", fieldName: "x_content", dataType: "$faker.lorem.paragraph" }, + { id: "4", fieldName: "x_author", dataType: "$faker.person.fullName" }, + ]; + + const code = generateCode(postCardTemplate, fields, "https://mock.api/v1/posts"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("x_title"); + expect(code).toContain("x_content"); + }); + + it("productCardTemplate generates React code without Tailwind CSS classes", () => { + const fields = [ + { id: "1", fieldName: "p_id", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "p_name", dataType: "$faker.commerce.productName" }, + { id: "3", fieldName: "p_price", dataType: "$faker.commerce.price" }, + { id: "4", fieldName: "p_stock", dataType: "$faker.datatype.boolean" }, + ]; + + const code = generateCode(productCardTemplate, fields, "https://mock.api/v1/products"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("p_name"); + expect(code).toContain("p_price"); + }); + + it("todoListTemplate generates React code without Tailwind CSS classes", () => { + const fields = [ + { id: "1", fieldName: "t_id", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "t_words", dataType: "$faker.lorem.words" }, + { id: "3", fieldName: "t_done", dataType: "$faker.datatype.boolean" }, + ]; + + const code = generateCode(todoListTemplate, fields, "https://mock.api/v1/todos"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("t_words"); + expect(code).toContain("t_done"); + }); + + it("transactionRowTemplate generates React code without Tailwind CSS classes", () => { + const fields = [ + { id: "1", fieldName: "tx_id", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "tx_val", dataType: "$faker.finance.amount" }, + { id: "3", fieldName: "tx_date", dataType: "$faker.date.recent" }, + ]; + + const code = generateCode(transactionRowTemplate, fields, "https://mock.api/v1/tx"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("tx_val"); + expect(code).toContain("tx_date"); + }); + + it("commentItemTemplate generates React code without Tailwind CSS classes", () => { + const fields = [ + { id: "1", fieldName: "c_id", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "c_author", dataType: "$faker.person.fullName" }, + { id: "3", fieldName: "c_text", dataType: "$faker.lorem.paragraph" }, + ]; + + const code = generateCode(commentItemTemplate, fields, "https://mock.api/v1/comments"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("c_author"); + expect(code).toContain("c_text"); + }); + + it("dynamicGridTemplate generates React code without Tailwind CSS classes", () => { + const fields = [ + { id: "1", fieldName: "field_a", dataType: "$faker.string.uuid" }, + { id: "2", fieldName: "field_b", dataType: "$faker.person.fullName" }, + ]; + + const code = generateCode(dynamicGridTemplate, fields, "https://mock.api/v1/grid"); + + expect(code).not.toContain('className="'); + expect(code).toContain("style={{"); + expect(code).toContain("field_a"); + expect(code).toContain("field_b"); + }); + }); + describe("dynamicGridTemplate Code Generation", () => { it("generates TSX component code from schema fields with snake_case normalization", () => { const fields = fieldsFromSchema({
    DateMerchant / PayeeDescriptionCategoryStatusAmount
    DateMerchant / PayeeDescriptionCategoryStatusAmount
    {tx.${dateField}}{tx.${merchantField}}{tx.${descriptionField}}{tx.${categoryField}}
    {tx.${dateField}}{tx.${merchantField}}{tx.${descriptionField}}{tx.${categoryField}} + ? ` {tx.${statusField} && ( - - {tx.${statusField}} + + {String(tx.${statusField})} )} + } {tx.${amountField}}${currencyField ? ` {tx.${currencyField}}` : ""}
    Merchant` : ""; const descTh = descriptionField ? `Description