Files
twenty/packages/twenty-eslint-rules/rules/export-component-props.spec.ts
T
Raphaël Bosi cb7d0b83a8 [FRONT COMPONENTS] Twenty UI elements generation (#17866)
## PR description

This PR:
- Creates a TypeScript-based extractor that discovers all exported
twenty-ui components by scanning barrel files, extracting
props/slots/events via ts-morph type analysis, and generating the remote
DOM bindings automatically.

- Adds a new ESLint rule which enforces all *Props types in twenty-ui
components to be exported, which is required for the extractor to
discover component prop types. Existing twenty-ui components are updated
to comply with this rule.

- Extends the remote DOM generation to support slots, per-component
events, forwardRef wrappers, and richer property types (array, object,
function)

## Edge cases to fix in another PR

- Icons cannot be rendered inside buttons
- IconButtons throw an error when mounted
- MenuItems are not displayed correctly
- MenuItemNavigate throws on click

## Video Demo


https://github.com/user-attachments/assets/c2ed67cf-6a15-4896-9fec-e83fac0e862b
2026-02-12 12:48:16 +01:00

120 lines
3.2 KiB
TypeScript

/**
* @jest-environment node
*/
import { RuleTester } from 'eslint';
import { rule, RULE_NAME } from './export-component-props';
const typescriptParser = require('@typescript-eslint/parser');
const ruleTester = new RuleTester({
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
});
ruleTester.run(RULE_NAME, rule as any, {
valid: [
{
name: 'Props type is already exported',
code: `
export type MyComponentProps = { label: string };
`,
},
{
name: 'Props interface is already exported',
code: `
export interface MyComponentProps { label: string }
`,
},
{
name: 'Type that doesn\'t end with Props is ignored',
code: `
type MyComponentOptions = { flag: boolean };
`,
},
{
name: 'Interface that doesn\'t end with Props is ignored',
code: `
interface MyComponentConfig { flag: boolean }
`,
},
{
name: 'Props re-exported via export { FooProps } is treated as exported',
code: `
type MyComponentProps = { label: string };
export { MyComponentProps };
`,
},
{
name: 'Non-Props type is not required to be exported',
code: `
type InternalState = { count: number };
`,
},
],
invalid: [
{
name: 'Unexported Props type alias',
code: `
type MyComponentProps = { label: string };
`,
errors: [{ messageId: 'mustExportProps' }],
output: `
export type MyComponentProps = { label: string };
`,
},
{
name: 'Unexported Props interface',
code: `
interface MyComponentProps { label: string }
`,
errors: [{ messageId: 'mustExportProps' }],
output: `
export interface MyComponentProps { label: string }
`,
},
{
name: 'Unexported Props type used in a component',
code: `
type MyComponentProps = { label: string };
export const MyComponent = ({ label }: MyComponentProps) => <div>{label}</div>;
`,
errors: [{ messageId: 'mustExportProps' }],
output: `
export type MyComponentProps = { label: string };
export const MyComponent = ({ label }: MyComponentProps) => <div>{label}</div>;
`,
},
{
name: 'Unexported Props type with non-exported component',
code: `
type MyComponentProps = { label: string };
const MyComponent = ({ label }: MyComponentProps) => <div>{label}</div>;
`,
errors: [{ messageId: 'mustExportProps' }],
output: `
export type MyComponentProps = { label: string };
const MyComponent = ({ label }: MyComponentProps) => <div>{label}</div>;
`,
},
{
name: 'Unexported Props type defined after the component',
code: `
export const MyComponent = ({ label }: MyComponentProps) => <div>{label}</div>;
type MyComponentProps = { label: string };
`,
errors: [{ messageId: 'mustExportProps' }],
output: `
export const MyComponent = ({ label }: MyComponentProps) => <div>{label}</div>;
export type MyComponentProps = { label: string };
`,
},
],
});