diff --git a/examples/react/src/basic-ui/components/Containers/FormField.tsx b/examples/react/src/basic-ui/components/Containers/FormField.tsx
index 24e5975..07f551b 100644
--- a/examples/react/src/basic-ui/components/Containers/FormField.tsx
+++ b/examples/react/src/basic-ui/components/Containers/FormField.tsx
@@ -1,5 +1,5 @@
import React from 'react';
export const FormField = ({ children, ...props }: any) => {
- return
{children}
;
+ return {children}
;
};
\ No newline at end of file
diff --git a/examples/react/src/basic-ui/components/Containers/FormSectionContainer.tsx b/examples/react/src/basic-ui/components/Containers/FormSectionContainer.tsx
index f5f125f..4da09ab 100644
--- a/examples/react/src/basic-ui/components/Containers/FormSectionContainer.tsx
+++ b/examples/react/src/basic-ui/components/Containers/FormSectionContainer.tsx
@@ -1,5 +1,5 @@
import React from 'react';
export const FormSectionContainer = ({ children, ...props }: any) => {
- return {children}
;
+ return {children}
;
};
\ No newline at end of file
diff --git a/examples/react/src/basic-ui/components/Containers/FormSectionGroup.tsx b/examples/react/src/basic-ui/components/Containers/FormSectionGroup.tsx
index 59a9d65..e32fc5b 100644
--- a/examples/react/src/basic-ui/components/Containers/FormSectionGroup.tsx
+++ b/examples/react/src/basic-ui/components/Containers/FormSectionGroup.tsx
@@ -2,5 +2,5 @@ import React from 'react';
export const FormSectionGroup = ({ children, columns, ...props }: any) => {
const gridColumns = columns || 'repeat(auto-fit, minmax(200px, 1fr))';
- return {children}
;
+ return {children}
;
};
\ No newline at end of file
diff --git a/examples/react/src/basic-ui/components/Containers/FormSectionGroupContainer.tsx b/examples/react/src/basic-ui/components/Containers/FormSectionGroupContainer.tsx
index 3306a08..3d7140e 100644
--- a/examples/react/src/basic-ui/components/Containers/FormSectionGroupContainer.tsx
+++ b/examples/react/src/basic-ui/components/Containers/FormSectionGroupContainer.tsx
@@ -1,5 +1,5 @@
import React from 'react';
export const FormSectionGroupContainer = ({ children, ...props }: any) => {
- return {children}
;
+ return {children}
;
};
\ No newline at end of file
diff --git a/examples/react/src/basic-ui/components/Containers/FormSectionTitle.tsx b/examples/react/src/basic-ui/components/Containers/FormSectionTitle.tsx
index 4fed989..b0db5aa 100644
--- a/examples/react/src/basic-ui/components/Containers/FormSectionTitle.tsx
+++ b/examples/react/src/basic-ui/components/Containers/FormSectionTitle.tsx
@@ -1,5 +1,5 @@
import React from 'react';
export const FormSectionTitle = ({ 'x-content': content, children, ...props }: any) => {
- return {content || children}
;
+ return {content || children}
;
};
\ No newline at end of file
diff --git a/examples/react/src/basic-ui/pages/BasicFormPage.tsx b/examples/react/src/basic-ui/pages/BasicFormPage.tsx
index 9152a21..d3bd562 100644
--- a/examples/react/src/basic-ui/pages/BasicFormPage.tsx
+++ b/examples/react/src/basic-ui/pages/BasicFormPage.tsx
@@ -22,12 +22,12 @@ export function BasicFormPage() {
<>
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/examples/react/src/chakra-ui/components/Containers/FormField.tsx b/examples/react/src/chakra-ui/components/Containers/FormField.tsx
index a4c7270..c38912e 100644
--- a/examples/react/src/chakra-ui/components/Containers/FormField.tsx
+++ b/examples/react/src/chakra-ui/components/Containers/FormField.tsx
@@ -2,5 +2,5 @@ import React from "react";
import { Box } from "@chakra-ui/react";
export const FormField = ({ children, ...props }: any) => {
- return {children};
+ return {children};
};
\ No newline at end of file
diff --git a/examples/react/src/chakra-ui/components/Containers/FormSectionGroup.tsx b/examples/react/src/chakra-ui/components/Containers/FormSectionGroup.tsx
index 7bb2079..f365584 100644
--- a/examples/react/src/chakra-ui/components/Containers/FormSectionGroup.tsx
+++ b/examples/react/src/chakra-ui/components/Containers/FormSectionGroup.tsx
@@ -1,15 +1,13 @@
import React from "react";
-import { Grid, GridItem } from "@chakra-ui/react";
+import { Grid } from "@chakra-ui/react";
export const FormSectionGroup = ({ children, 'x-component-props': xComponentProps, ...props }: any) => {
const columns = xComponentProps?.columns || '1fr 1fr';
const gridColumns = columns === '1fr' ? 1 : 2;
return (
-
- {React.Children.map(children, (child, index) => (
- {child}
- ))}
+
+ {children}
);
};
\ No newline at end of file
diff --git a/examples/react/src/chakra-ui/components/Containers/FormSectionGroupContainer.tsx b/examples/react/src/chakra-ui/components/Containers/FormSectionGroupContainer.tsx
index 45194be..d6ac7bf 100644
--- a/examples/react/src/chakra-ui/components/Containers/FormSectionGroupContainer.tsx
+++ b/examples/react/src/chakra-ui/components/Containers/FormSectionGroupContainer.tsx
@@ -2,5 +2,5 @@ import React from "react";
import { Box } from "@chakra-ui/react";
export const FormSectionGroupContainer = ({ children, ...props }: any) => {
- return {children};
+ return {children};
};
\ No newline at end of file
diff --git a/examples/react/src/chakra-ui/components/Inputs/InputCheckbox.tsx b/examples/react/src/chakra-ui/components/Inputs/InputCheckbox.tsx
index cc70e2b..fdf0341 100644
--- a/examples/react/src/chakra-ui/components/Inputs/InputCheckbox.tsx
+++ b/examples/react/src/chakra-ui/components/Inputs/InputCheckbox.tsx
@@ -2,12 +2,12 @@ import React from "react";
import { Checkbox } from "@chakra-ui/react";
export const InputCheckbox = React.forwardRef((props, ref) => {
- const { label, name, checked, onChange, ...rest } = props;
+ const { label, name, value, onChange, ...rest } = props;
return (
onChange?.(e.target.checked)}
data-test-id={name}
{...rest}
diff --git a/examples/react/src/index.css b/examples/react/src/index.css
index ad5c070..83bfa71 100644
--- a/examples/react/src/index.css
+++ b/examples/react/src/index.css
@@ -12,3 +12,7 @@ code {
monospace;
}
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+
diff --git a/examples/react/src/material-ui/components/Containers/FormField.tsx b/examples/react/src/material-ui/components/Containers/FormField.tsx
index 32c3ef5..aba9d70 100644
--- a/examples/react/src/material-ui/components/Containers/FormField.tsx
+++ b/examples/react/src/material-ui/components/Containers/FormField.tsx
@@ -2,5 +2,5 @@ import React from "react";
import { Box } from "@mui/material";
export const FormField = ({ children, ...props }: any) => {
- return {children};
+ return {children};
};
diff --git a/examples/react/src/material-ui/components/Containers/FormSectionGroup.tsx b/examples/react/src/material-ui/components/Containers/FormSectionGroup.tsx
index bb357dc..2017d52 100644
--- a/examples/react/src/material-ui/components/Containers/FormSectionGroup.tsx
+++ b/examples/react/src/material-ui/components/Containers/FormSectionGroup.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { Grid } from "@mui/material";
+import { Box, Grid } from "@mui/material";
export const FormSectionGroup = ({
children,
@@ -7,23 +7,17 @@ export const FormSectionGroup = ({
...props
}: any) => {
const columns = xComponentProps?.columns || "1fr 1fr";
- const gridColumns = columns === "1fr" ? 12 : 6;
+ const gridColumns = columns === "1fr" ? 1 : 2;
return (
-
- {React.Children.map(children, (child) => (
-
- {child}
-
- ))}
-
+ {children}
+
);
};
diff --git a/examples/react/src/material-ui/components/Containers/FormSectionGroupContainer.tsx b/examples/react/src/material-ui/components/Containers/FormSectionGroupContainer.tsx
index f02690b..397bb78 100644
--- a/examples/react/src/material-ui/components/Containers/FormSectionGroupContainer.tsx
+++ b/examples/react/src/material-ui/components/Containers/FormSectionGroupContainer.tsx
@@ -2,5 +2,5 @@ import React from "react";
import { Box } from "@mui/material";
export const FormSectionGroupContainer = ({ children, ...props }: any) => {
- return {children};
+ return {children};
};
diff --git a/examples/react/src/material-ui/components/Inputs/InputCheckbox.tsx b/examples/react/src/material-ui/components/Inputs/InputCheckbox.tsx
index cb83cc7..e152c6a 100644
--- a/examples/react/src/material-ui/components/Inputs/InputCheckbox.tsx
+++ b/examples/react/src/material-ui/components/Inputs/InputCheckbox.tsx
@@ -4,14 +4,14 @@ import { Checkbox } from "@mui/material";
export const InputCheckbox = React.forwardRef(
(props, ref) => {
- const { label, name, checked, onChange, ...rest } = props;
+ const { label, name, value, onChange, ...rest } = props;
return (
onChange?.(e.target.checked)}
data-test-id={name}
{...rest}
diff --git a/instances/form/complex-form.json b/instances/form/complex-form.json
index 70d7102..bde79a7 100644
--- a/instances/form/complex-form.json
+++ b/instances/form/complex-form.json
@@ -23,15 +23,12 @@
"basicInfo": {
"type": "object",
"x-component": "FormSectionGroup",
- "x-component-props": {
- "columns": "1fr 1fr"
- },
"properties": {
"email": {
"type": "object",
"x-component": "FormField",
"x-ui": {
- "order": 1
+ "order": 3
},
"properties": {
"email": {
@@ -40,11 +37,10 @@
"x-component-props": {
"type": "email",
"label": "Email Address",
- "placeholder": "example@email.com"
- },
- "x-rules": {
+ "placeholder": "example@email.com",
"required": true
}
+
}
}
},
@@ -52,7 +48,7 @@
"type": "object",
"x-component": "FormField",
"x-ui": {
- "order": 2
+ "order": 4
},
"properties": {
"phone": {
@@ -60,7 +56,8 @@
"x-component": "InputPhone",
"x-component-props": {
"label": "Phone Number",
- "placeholder": "(123) 456-7890"
+ "placeholder": "(123) 456-7890",
+ "required": true
}
}
}
@@ -90,10 +87,43 @@
"basicInfo": {
"type": "object",
"x-component": "FormSectionGroup",
- "x-component-props": {
- "columns": "1fr 1fr"
- },
"properties": {
+ "firstName": {
+ "type": "object",
+ "x-component": "FormField",
+ "x-ui": {
+ "order": 1
+ },
+ "properties": {
+ "firstName": {
+ "type": "string",
+ "x-component": "InputText",
+ "x-component-props": {
+ "label": "First Name",
+ "placeholder": "Enter your first name",
+ "required": true
+ }
+ }
+ }
+ },
+ "lastName": {
+ "type": "object",
+ "x-component": "FormField",
+ "x-ui": {
+ "order": 2
+ },
+ "properties": {
+ "lastName": {
+ "type": "string",
+ "x-component": "InputText",
+ "x-component-props": {
+ "label": "Last Name",
+ "placeholder": "Enter your last name",
+ "required": true
+ }
+ }
+ }
+ },
"userType": {
"type": "object",
"x-component": "FormField",
@@ -136,9 +166,6 @@
"additionalInfo": {
"type": "object",
"x-component": "FormSectionGroup",
- "x-component-props": {
- "columns": "1fr"
- },
"properties": {
"bio": {
"type": "object",
@@ -157,28 +184,15 @@
}
}
},
- "age": {
- "type": "object",
- "x-component": "FormField",
- "x-ui": {
- "order": 6
- },
- "properties": {
- "age": {
- "type": "number",
- "x-component": "InputNumber",
- "x-component-props": {
- "label": "Age",
- "placeholder": "Enter your age",
- "min": 0,
- "max": 120
- }
- }
- }
- },
"acceptTerms": {
"type": "object",
"x-component": "FormField",
+ "x-component-props": {
+ "style": {
+ "display": "flex",
+ "alignItems": "center"
+ }
+ },
"x-ui": {
"order": 7
},
diff --git a/packages/core/src/registry/renderer-registry.ts b/packages/core/src/registry/renderer-registry.ts
index 041eefb..5526302 100644
--- a/packages/core/src/registry/renderer-registry.ts
+++ b/packages/core/src/registry/renderer-registry.ts
@@ -37,7 +37,9 @@ export const defaultTypeRenderers: Record = {
return runtime.create(spec, propsWithChildren);
},
'container': (spec, props, runtime, children) => {
- const sanitized = sanitizePropsForDOM(props);
+ const xComponentProps = props['x-component-props'] || {};
+ const mergedProps = { ...props, ...xComponentProps };
+ const sanitized = sanitizePropsForDOM(mergedProps);
const propsWithChildren = children && children.length > 0
? { ...sanitized, children }
: sanitized;
diff --git a/packages/core/src/schema/schema-types.ts b/packages/core/src/schema/schema-types.ts
index 4ab8b35..801d8ca 100644
--- a/packages/core/src/schema/schema-types.ts
+++ b/packages/core/src/schema/schema-types.ts
@@ -1,201 +1,221 @@
/**
- * Schema Types
- *
- * Type definitions for form and menu schemas.
- * Compatible with JSON Schema format.
- */
-
-/**
- * Base schema property
- */
-export interface BaseSchemaProperty {
- type: string;
- 'x-component'?: string;
- 'x-component-props'?: Record;
- 'x-rules'?: Record;
- 'x-reactions'?: Record;
- 'x-ui'?: {
- order?: number;
- [key: string]: any;
- };
- 'x-content'?: any;
- 'x-slots'?: Record;
-}
-
-/**
- * Form schema structure
- */
-export interface FormSchema extends BaseSchemaProperty {
- type: 'object';
- $id?: string;
- $schema?: string;
- properties?: Record;
- required?: string[];
-}
-
-/**
- * Form section container
- */
-export interface FormSectionContainer extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'FormSectionContainer';
- properties?: Record;
-}
-
-/**
- * Form section title
- */
-export interface FormSectionTitle extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'FormSectionTitle';
- 'x-content': string;
-}
-
-/**
- * Form section group container
- */
-export interface FormSectionGroupContainer extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'FormSectionGroupContainer';
- properties?: Record;
-}
-
-/**
- * Form section group
- */
-export interface FormSectionGroup extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'FormSectionGroup';
- properties?: Record;
-}
-
-/**
- * Form field
- */
-export interface FormField extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'FormField';
- properties?: Record;
-}
-
-/**
- * Field component types
- */
-export type FieldComponent =
- | InputText
- | InputSelect
- | InputCheckbox
- | InputDate
- | InputCpf
- | InputPhone
- | InputTextarea
- | InputNumber
- | InputAutocomplete;
-
-/**
- * Input text
- */
-export interface InputText extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputText';
-}
-
-/**
- * Input select
- */
-export interface InputSelect extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputSelect';
-}
-
-/**
- * Input checkbox
- */
-export interface InputCheckbox extends BaseSchemaProperty {
- type: 'boolean';
- 'x-component': 'InputCheckbox';
-}
-
-/**
- * Date picker
- */
-export interface InputDate extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputDate';
-}
-
-/**
- * Input CPF
- */
-export interface InputCpf extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputCpf';
-}
-
-/**
- * Input phone
- */
-export interface InputPhone extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputPhone';
-}
-
-/**
- * Input autocomplete
- */
-export interface InputAutocomplete extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputAutocomplete';
-}
-
-/**
- * Input textarea
- */
-export interface InputTextarea extends BaseSchemaProperty {
- type: 'string';
- 'x-component': 'InputTextarea';
-}
-
-/**
- * Input number
- */
-export interface InputNumber extends BaseSchemaProperty {
- type: 'number';
- 'x-component': 'InputNumber';
-}
-
-/**
- * Menu schema structure
- */
-export interface MenuSchema extends BaseSchemaProperty {
- type: 'object';
- properties?: Record;
-}
-
-/**
- * Menu container
- */
-export interface MenuContainer extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'MenuContainer';
- 'x-content'?: {
- items?: MenuItem[];
- };
-}
-
-/**
- * Menu item
- */
-export interface MenuItem extends BaseSchemaProperty {
- type: 'object';
- 'x-component': 'MenuLink' | 'MenuButton';
- 'x-component-props'?: {
- href?: string;
- onClick?: string;
- [key: string]: any;
- };
- 'x-content'?: {
- text?: string;
- };
+ * Schema de formulário
+ */
+export interface FormSchema {
+ $id: string
+ $schema?: string
+ type: "object"
+ properties: {
+ [k: string]: FormSectionContainer
+ }
+ "x-component": "FormContainer"
+}
+/**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.*$".
+ */
+export interface FormSectionContainer {
+ type: "object"
+ "x-ui": {
+ /**
+ * Order of the section in the form (lower values appear first)
+ */
+ order: number
+ }
+ properties: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.*$".
+ */
+ [k: string]: FormSectionTitle | FormSectionGroupContainer
+ }
+ "x-component": "FormSectionContainer"
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
+}
+export interface FormSectionTitle {
+ type: "object"
+ "x-slots"?: {
+ [k: string]: unknown
+ }
+ "x-content": string
+ "x-component": "FormSectionTitle"
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
+}
+export interface FormSectionGroupContainer {
+ type: "object"
+ properties: {
+ [k: string]: FormSectionGroup
+ }
+ "x-component": "FormSectionGroupContainer"
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
+}
+/**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.*$".
+ */
+export interface FormSectionGroup {
+ type: "object"
+ properties: {
+ [k: string]: FormField
+ }
+ "x-component": "FormSectionGroup"
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
+}
+/**
+ * FormField wrapper (Grid) reutilizável para qualquer tipo de input
+ *
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.*$".
+ */
+export interface FormField {
+ type: "object"
+ "x-component": "FormField"
+ "x-ui": {
+ /**
+ * Order of the field in the form (lower values appear first)
+ */
+ order: number
+ }
+ properties: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z_][a-zA-Z0-9_]*$".
+ */
+ [k: string]:
+ | InputText
+ | InputSelect
+ | InputCheckbox
+ | InputDate
+ | InputPhone
+ | InputAutocomplete
+ | InputTextarea
+ | InputNumber
+ }
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
+}
+export interface InputText {
+ type: "string"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-component": "InputText"
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ label?: string
+ placeholder?: string
+ [k: string]: unknown
+ }
+}
+export interface InputSelect {
+ type: "string"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-component": "InputSelect"
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ label?: string
+ placeholder?: string
+ [k: string]: unknown
+ }
+}
+export interface InputCheckbox {
+ type: "boolean"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-component": "InputCheckbox"
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ label?: string
+ placeholder?: string
+ [k: string]: unknown
+ }
+}
+export interface InputDate {
+ type: "string"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-component": "InputDate"
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ label?: string
+ placeholder?: string
+ [k: string]: unknown
+ }
+}
+export interface InputPhone {
+ type: "string"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-component": "InputPhone"
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ label?: string
+ placeholder?: string
+ [k: string]: unknown
+ }
+}
+export interface InputAutocomplete {
+ type: "string"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-component": "InputAutocomplete"
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ label?: string
+ placeholder?: string
+ [k: string]: unknown
+ }
+}
+export interface InputTextarea {
+ type: "string"
+ "x-component": "InputTextarea"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
+}
+export interface InputNumber {
+ type: "number"
+ "x-component": "InputNumber"
+ "x-rules"?: {
+ [k: string]: unknown
+ }
+ "x-reactions"?: {
+ [k: string]: unknown
+ }
+ "x-component-props"?: {
+ [k: string]: unknown
+ }
}
-
diff --git a/tests/e2e/chakra-ui.spec.ts b/tests/e2e/chakra-ui.spec.ts
deleted file mode 100644
index e568318..0000000
--- a/tests/e2e/chakra-ui.spec.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { test, expect } from '@playwright/test';
-import simpleFormSchema from '../../instances/form/simple-form.json';
-import complexFormSchema from '../../instances/form/complex-form.json';
-
-test.describe('Chakra UI Form Factory', () => {
- test.beforeEach(async ({ page, baseURL }) => {
- await page.goto(`${baseURL || 'http://localhost:3002'}/`);
- });
-
- test('should render simple form with Chakra UI components', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- const lastNameField = page.locator('[data-test-id*="lastName"]').first();
-
- await expect(firstNameField).toBeVisible();
- await expect(lastNameField).toBeVisible();
- });
-
- test('should render complex form with all field types', async ({ page, baseURL }) => {
- await page.goto(`${baseURL || 'http://localhost:3002'}/complex`);
-
- await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 });
-
- await expect(page.locator('[data-test-id*="email"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="phone"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="userType"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="acceptTerms"]').first()).toBeVisible();
- });
-
- test('should handle form input', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- // Chakra UI Input component renders directly as an INPUT element
- // Get the input element directly by its data-test-id
- const firstNameInput = page.locator('input[data-test-id*="firstName"]').first();
-
- // Wait for the element to be visible and enabled
- await expect(firstNameInput).toBeVisible();
- await expect(firstNameInput).toBeEnabled();
-
- // Get initial value
- const initialValue = await firstNameInput.inputValue();
- console.log('Initial value:', initialValue);
-
- // Try to fill the input
- try {
- await firstNameInput.fill('John');
- console.log('Fill completed');
- } catch (error) {
- console.log('Fill error:', error);
- throw error;
- }
-
- // Wait a bit for the value to be set
- await page.waitForTimeout(500);
-
- // Check the value after fill
- const valueAfterFill = await firstNameInput.inputValue();
- console.log('Value after fill:', valueAfterFill);
-
- // This will fail if the input is not filled
- expect(valueAfterFill).toBe('John');
- });
-});
-
diff --git a/tests/e2e/material-ui.spec.ts b/tests/e2e/material-ui.spec.ts
deleted file mode 100644
index 4ba7081..0000000
--- a/tests/e2e/material-ui.spec.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { test, expect } from '@playwright/test';
-import simpleFormSchema from '../../instances/form/simple-form.json';
-import complexFormSchema from '../../instances/form/complex-form.json';
-
-test.describe('Material UI Form Factory', () => {
- test.beforeEach(async ({ page, baseURL }) => {
- await page.goto(`${baseURL || 'http://localhost:3001'}/`);
- });
-
- test('should render simple form with Material UI components', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- const lastNameField = page.locator('[data-test-id*="lastName"]').first();
-
- await expect(firstNameField).toBeVisible();
- await expect(lastNameField).toBeVisible();
-
- // Check if Material UI styling is applied
- const input = firstNameField.locator('input');
- await expect(input).toBeVisible();
- });
-
- test('should render complex form with all field types', async ({ page, baseURL }) => {
- await page.goto(`${baseURL || 'http://localhost:3001'}/complex`);
-
- await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 });
-
- await expect(page.locator('[data-test-id*="email"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="phone"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="userType"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="acceptTerms"]').first()).toBeVisible();
- });
-
- test('should handle form input', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- const firstNameField = page.locator('[data-test-id*="firstName"] input').first();
- await firstNameField.fill('John');
-
- await expect(firstNameField).toHaveValue('John');
- });
-});
-
diff --git a/tests/e2e/provider.spec.ts b/tests/e2e/provider.spec.ts
deleted file mode 100644
index 709029f..0000000
--- a/tests/e2e/provider.spec.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import { test, expect } from '@playwright/test';
-
-// Only run provider tests for React (provider example is only in React until we have a provider example for other frameworks)
-// TODO: atualizar test pois nao temos mais um exemplo direto de provider, agora ele circunda tudo
-test.describe('Provider E2E Tests', () => {
- test.use({
- // @ts-ignore - project is a valid option in Playwright config
- project: 'react'
- });
-
- test.beforeEach(async ({ page, baseURL }) => {
- await page.goto(`${baseURL || 'http://localhost:3000'}/provider`);
- });
-
- test('should render form using components from provider', async ({ page }) => {
- // Wait for form to be rendered
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- // Verify form container exists (from provider)
- const formContainer = page.locator('[data-test-id="FormContainer"]');
- await expect(formContainer).toBeVisible();
-
- // Verify form fields are present (components from provider)
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- const lastNameField = page.locator('[data-test-id*="lastName"]').first();
-
- await expect(firstNameField).toBeVisible();
- await expect(lastNameField).toBeVisible();
- });
-
- test('should apply middleware from provider to form fields', async ({ page }) => {
- // Wait for form to be rendered
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- // The middleware adds "[Provider]" prefix to labels
- // Verify that labels contain the prefix
- const firstNameLabel = page.locator('label[for*="firstName"]');
- await expect(firstNameLabel).toBeVisible();
-
- const labelText = await firstNameLabel.textContent();
- expect(labelText).toContain('[Provider]');
- });
-
- test('should use externalContext from provider', async ({ page }) => {
- // Wait for form to be rendered
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- // The schema has: "label": "{{ $externalContext.user.name }}"
- // Which should be replaced with "Provider User" from the provider's externalContext
- // Note: The middleware adds "[Provider]" prefix, so the label will be "[Provider] Provider User"
- const firstNameLabel = page.locator('label[for*="firstName"]');
- await expect(firstNameLabel).toBeVisible();
-
- const labelText = await firstNameLabel.textContent();
- // Verify that the template expression was resolved to "Provider User"
- expect(labelText).toContain('Provider User');
- // Verify that the middleware prefix is also applied
- expect(labelText).toContain('[Provider]');
- });
-
- test('should handle form input with provider components', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- await firstNameField.fill('Provider Test');
-
- await expect(firstNameField).toHaveValue('Provider Test');
- });
-
- test('should submit form and display submitted values', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- // Fill form fields
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- await firstNameField.fill('Jane');
-
- const lastNameField = page.locator('[data-test-id*="lastName"]').first();
- await lastNameField.fill('Smith');
-
- // Submit form
- const submitButton = page.locator('[data-test-id="submit-button"]');
- await submitButton.click();
-
- // Wait for submitted values section
- await page.waitForSelector('text=Valores Submetidos', { timeout: 5000 });
-
- // Verify submitted values are displayed
- const submittedSection = page.locator('text=Valores Submetidos');
- await expect(submittedSection).toBeVisible();
-
- // Verify values in the JSON output
- const preElement = page.locator('pre');
- const jsonContent = await preElement.textContent();
- expect(jsonContent).toContain('Jane');
- expect(jsonContent).toContain('Smith');
- });
-
-});
-
diff --git a/tests/e2e/react.spec.ts b/tests/e2e/react.spec.ts
index ee786d2..55c77a2 100644
--- a/tests/e2e/react.spec.ts
+++ b/tests/e2e/react.spec.ts
@@ -1,55 +1,87 @@
import { test, expect } from '@playwright/test';
import simpleFormSchema from '../../instances/form/simple-form.json';
import complexFormSchema from '../../instances/form/complex-form.json';
+import { extractFormFields, extractRequiredFields } from 'tests/utils/extractJsonFields';
+
+
test.describe('React Form Factory', () => {
test.beforeEach(async ({ page, baseURL }) => {
- await page.goto(baseURL || 'http://localhost:3000/');
+ await page.goto(`${baseURL || 'http://localhost:3000'}/basic`);
});
test('should render simple form', async ({ page }) => {
+ const fields = extractFormFields(simpleFormSchema);
+
// Wait for form to be rendered
await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
- // Check if form fields are present
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- const lastNameField = page.locator('[data-test-id*="lastName"]').first();
-
- await expect(firstNameField).toBeVisible();
- await expect(lastNameField).toBeVisible();
+ // Check if all form fields from schema are present
+ for (const field of fields) {
+ await expect(page.locator(`[data-test-id*="${field}"]`)).toBeVisible();
+ }
});
test('should render complex form with all field types', async ({ page, baseURL }) => {
- await page.goto(`${baseURL || 'http://localhost:3000'}/complex`);
+ await page.click('[data-test-id*="complex-form-tab"]');
+
+ const fields = extractFormFields(complexFormSchema);
+ console.log('Extracted fields from complex schema:', fields);
// Wait for form to be rendered
await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 });
- // Check different field types
- await expect(page.locator('[data-test-id*="email"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="phone"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="userType"]').first()).toBeVisible();
- await expect(page.locator('[data-test-id*="acceptTerms"]').first()).toBeVisible();
+ // Check if all form fields from schema are present
+ for (const field of fields) {
+ await expect(page.locator(`[data-test-id*="${field}"]`).first()).toBeVisible();
+ }
});
- test('should handle form input', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- await firstNameField.fill('John');
-
- await expect(firstNameField).toHaveValue('John');
+ test('should fill form fields', async ({ page }) => {
+ const fields = extractFormFields(complexFormSchema);
+ const firstNameField = fields.find(f => f === 'firstName');
+ const lastNameField = fields.find(f => f === 'lastName');
+ const emailField = fields.find(f => f === 'email');
+ const phoneField = fields.find(f => f === 'phone');
+ const birthDateField = fields.find(f => f === 'birthDate');
+ const userTypeField = fields.find(f => f === 'userType');
+ const bioField = fields.find(f => f === 'bio');
+ const acceptTermsField = fields.find(f => f === 'acceptTerms');
+
+ await page.click('[data-test-id*="complex-form-tab"]');
+ await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 });
+
+ await page.locator(`[data-test-id*="${emailField}"]`).first().fill('john.doe@example.com');
+ await page.locator(`[data-test-id*="${phoneField}"]`).first().fill('(123) 456-7890');
+ await page.locator(`[data-test-id*="${firstNameField}"]`).first().fill('John');
+ await page.locator(`[data-test-id*="${lastNameField}"]`).first().fill('Doe');
+ await page.locator(`[data-test-id*="${userTypeField}"]`).first().selectOption('individual');
+ await page.locator(`[data-test-id*="${birthDateField}"]`).first().fill('1990-01-01');
+ await page.locator(`[data-test-id*="${bioField}"]`).first().fill('I am a software engineer');
+ await page.locator(`[data-test-id*="${acceptTermsField}"]`).first().check();
+
+ await expect(page.locator(`[data-test-id*="${emailField}"]`).first()).toHaveValue('john.doe@example.com');
+ await expect(page.locator(`[data-test-id*="${phoneField}"]`).first()).toHaveValue('(123) 456-7890');
+ await expect(page.locator(`[data-test-id*="${firstNameField}"]`).first()).toHaveValue('John');
+ await expect(page.locator(`[data-test-id*="${lastNameField}"]`).first()).toHaveValue('Doe');
+ await expect(page.locator(`[data-test-id*="${userTypeField}"]`).first()).toHaveValue('individual');
+ await expect(page.locator(`[data-test-id*="${birthDateField}"]`).first()).toHaveValue('1990-01-01');
+ await expect(page.locator(`[data-test-id*="${bioField}"]`).first()).toHaveValue('I am a software engineer');
+ await expect(page.locator(`[data-test-id*="${acceptTermsField}"]`).first()).toBeChecked();
+
});
test('should validate required fields', async ({ page }) => {
- await page.waitForSelector('[data-test-id*="firstName"]', { timeout: 10000 });
-
- const firstNameField = page.locator('[data-test-id*="firstName"]').first();
- await firstNameField.focus();
- await firstNameField.blur();
-
- // Check if validation error appears (implementation dependent)
- // This is a placeholder - adjust based on actual validation UI
+ const requiredFields = extractRequiredFields(complexFormSchema);
+
+ await page.click('[data-test-id*="complex-form-tab"]');
+ await page.waitForSelector('[data-test-id*="email"]', { timeout: 10000 });
+
+ for (const field of requiredFields) {
+ const fieldLocator = page.locator(`[data-test-id*="${field}"]`).first();
+ await expect(fieldLocator).toHaveAttribute('required', '');
+ }
+
});
});
diff --git a/tests/playwright.config.ts b/tests/playwright.config.ts
index f35f186..8025c2c 100644
--- a/tests/playwright.config.ts
+++ b/tests/playwright.config.ts
@@ -20,35 +20,7 @@ export default defineConfig({
...devices['Desktop Chrome'],
baseURL: 'http://localhost:3000',
},
- },
- {
- name: 'material-ui',
- use: {
- ...devices['Desktop Chrome'],
- baseURL: 'http://localhost:3001',
- },
- },
- {
- name: 'chakra-ui',
- use: {
- ...devices['Desktop Chrome'],
- baseURL: 'http://localhost:3002',
- },
- },
- {
- name: 'vue',
- use: {
- ...devices['Desktop Chrome'],
- baseURL: 'http://localhost:3010',
- },
- },
- {
- name: 'vue-vuetify',
- use: {
- ...devices['Desktop Chrome'],
- baseURL: 'http://localhost:3011',
- },
- },
+ }
],
webServer: [
{
@@ -58,39 +30,7 @@ export default defineConfig({
timeout: 120 * 1000,
stdout: 'ignore',
stderr: 'pipe',
- },
- {
- command: 'pnpm --filter examples-react-material-ui dev',
- url: 'http://localhost:3001',
- reuseExistingServer: !process.env.CI,
- timeout: 120 * 1000,
- stdout: 'ignore',
- stderr: 'pipe',
- },
- {
- command: 'pnpm --filter examples-react-chakra-ui dev',
- url: 'http://localhost:3002',
- reuseExistingServer: !process.env.CI,
- timeout: 120 * 1000,
- stdout: 'ignore',
- stderr: 'pipe',
- },
- {
- command: 'pnpm --filter examples-vue dev',
- url: 'http://localhost:3010',
- reuseExistingServer: !process.env.CI,
- timeout: 120 * 1000,
- stdout: 'ignore',
- stderr: 'pipe',
- },
- {
- command: 'pnpm --filter examples-vue-vuetify dev',
- url: 'http://localhost:3011',
- reuseExistingServer: !process.env.CI,
- timeout: 120 * 1000,
- stdout: 'ignore',
- stderr: 'pipe',
- },
+ }
],
});
diff --git a/tests/tsconfig.json b/tests/tsconfig.json
new file mode 100644
index 0000000..ec16afb
--- /dev/null
+++ b/tests/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "jsx": "preserve",
+ "baseUrl": "..",
+ "paths": {
+ "@schepta/core": ["./packages/core/src"],
+ "@schepta/adapter-vanilla": ["./packages/adapters/vanilla/src"],
+ "@schepta/adapter-react": ["./packages/adapters/react/src"],
+ "@schepta/adapter-vue": ["./packages/adapters/vue/src"],
+ "@schepta/factory-vanilla": ["./packages/factories/vanilla/src"],
+ "@schepta/factory-react": ["./packages/factories/react/src"],
+ "@schepta/factory-vue": ["./packages/factories/vue/src"]
+ }
+ },
+ "include": ["**/*"],
+ "exclude": ["node_modules", "dist"]
+ }
\ No newline at end of file
diff --git a/tests/utils/extractJsonFields.ts b/tests/utils/extractJsonFields.ts
new file mode 100644
index 0000000..95631f0
--- /dev/null
+++ b/tests/utils/extractJsonFields.ts
@@ -0,0 +1,93 @@
+import type { FormSchema } from '@schepta/core';
+
+function isInputComponent(component: string): boolean {
+ return component.startsWith('Input') ||
+ component === 'InputText' ||
+ component === 'InputSelect' ||
+ component === 'InputPhone' ||
+ component === 'InputDate' ||
+ component === 'InputTextarea' ||
+ component === 'InputNumber' ||
+ component === 'InputCheckbox';
+}
+
+/**
+ * Recursively extracts all form field names from a schema JSON
+ * A field is identified by having x-component: "FormField" and containing
+ * a property with an input component (InputText, InputSelect, etc.)
+ */
+export function extractFormFields(schema: FormSchema | any): string[] {
+ const fields: string[] = [];
+
+ function traverse(obj: any, path: string[] = []) {
+ if (!obj || typeof obj !== 'object') {
+ return;
+ }
+
+ // Check if this is a FormField component
+ if (obj['x-component'] === 'FormField' && obj.properties) {
+ // Look for the actual input field inside the FormField
+ for (const [key, value] of Object.entries(obj.properties)) {
+ if (value && typeof value === 'object' && 'x-component' in value) {
+ const component = (value as any)['x-component'];
+ // Check if it's an input component
+ if (component && isInputComponent(component)) {
+ fields.push(key);
+ }
+ }
+ }
+ }
+
+ // Recursively traverse properties
+ if (obj.properties) {
+ for (const [key, value] of Object.entries(obj.properties)) {
+ if (value && typeof value === 'object') {
+ traverse(value, [...path, key]);
+ }
+ }
+ }
+ }
+
+ traverse(schema);
+ return fields;
+}
+
+/**
+ * Extracts required fields from schema based on x-rules.required or required prop
+ */
+export function extractRequiredFields(schema: FormSchema | any): string[] {
+ const requiredFields: string[] = [];
+
+ function traverse(obj: any, currentFieldName: string | null = null) {
+ if (!obj || typeof obj !== 'object') {
+ return;
+ }
+
+ // Check if this is an input component with required validation
+ if (obj['x-component'] && (
+ isInputComponent(obj['x-component'])
+ )) {
+ // Check for required in x-rules or x-component-props
+ const isRequired =
+ obj['x-component-props']?.required === true;
+
+ if (isRequired && currentFieldName) {
+ requiredFields.push(currentFieldName);
+ }
+ }
+
+ // Recursively traverse properties
+ if (obj.properties) {
+ for (const [key, value] of Object.entries(obj.properties)) {
+ if (value && typeof value === 'object') {
+ // Use the key as field name if we're inside a FormField
+ const fieldName = obj['x-component'] === 'FormField' ? key : currentFieldName;
+ traverse(value, fieldName);
+ }
+ }
+ }
+ }
+
+ traverse(schema);
+ return requiredFields;
+}
\ No newline at end of file