Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ballerine Validator(WIP) #2874

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open

Ballerine Validator(WIP) #2874

wants to merge 7 commits into from

Conversation

chesterkmr
Copy link
Collaborator

@chesterkmr chesterkmr commented Dec 2, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced the ValidatorProvider component for enhanced form validation capabilities.
    • Added useAsyncValidate and useManualValidate hooks for asynchronous and manual validation.
    • Implemented various validators, including required, maxLength, and pattern.
    • Added a JSON editor component for editing validation data.
  • Bug Fixes

    • Improved error handling and messaging for validation errors.
  • Documentation

    • Added Storybook stories for the ValidatorProvider and associated components.
  • Tests

    • Comprehensive unit tests for validation hooks and utility functions to ensure reliability.

Copy link

changeset-bot bot commented Dec 2, 2024

⚠️ No Changeset found

Latest commit: ce18a0c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

coderabbitai bot commented Dec 2, 2024

Walkthrough

The pull request introduces several enhancements and new features to the @ballerine/ui package. Key changes include updates to the package.json file to add new dependencies and devDependencies. The ValidatorProvider component has been enhanced to support new validation props, while new hooks and utility functions have been created to facilitate synchronous and asynchronous validation. Additionally, multiple validator functions have been implemented, along with their corresponding unit tests, to ensure robust validation capabilities across various scenarios.

Changes

File Change Summary
packages/ui/package.json Added new dependencies: email-validator and json-logic-js; added new devDependencies: @testing-library/dom, @testing-library/react, @types/json-logic-js, @types/jsoneditor, and jsoneditor.
packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx Updated ValidatorProvider to include new props: validationDelay and abortEarly. Updated IValidatorProviderProps interface.
packages/ui/src/components/organisms/Form/Validator/context/index.ts Exported all entities from types and validator-context modules.
packages/ui/src/components/organisms/Form/Validator/context/types.ts Introduced new interface IValidatorContext<TValues> for validation context management.
packages/ui/src/components/organisms/Form/Validator/context/validator-context.ts Created ValidatorContext using createContext for managing validation state.
packages/ui/src/components/organisms/Form/Validator/hooks/external/useValidator/index.ts Re-exported all entities from useValidator module.
packages/ui/src/components/organisms/Form/Validator/hooks/external/useValidator/useValidator.ts Introduced useValidator hook for accessing ValidatorContext.
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/index.ts Re-exported all entities from types and useValidatorRef modules.
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/types.ts Added new interface IValidatorRef for validator references.
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/useValidatorRef.ts Created useValidatorRef hook for exposing validator methods to parent components.
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/useValidatorRef.unit.test.ts Introduced unit tests for useValidatorRef functionality.
packages/ui/src/components/organisms/Form/Validator/index.ts Exported components and utilities related to form validation from various modules.
packages/ui/src/components/organisms/Form/Validator/types/index.ts Defined several TypeScript types and interfaces for validation rules and schemas.
packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/format-error-message.ts Added formatErrorMessage utility function for dynamic error message formatting.
packages/ui/src/components/organisms/Form/Validator/utils/get-validator/get-validator.ts Introduced getValidator function for retrieving validation functions based on type.
packages/ui/src/components/organisms/Form/Validator/validators/format/format-validator.ts Added formatValidator function for validating email formats.
packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length-validator.ts Introduced maxLengthValidator function for validating string lengths.
packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length-validator.ts Added minLengthValidator function for enforcing minimum string lengths.
packages/ui/src/components/organisms/Form/Validator/validators/required/required-validator.ts Created requiredValueValidator function for checking required fields.
packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts Developed validate function for validating data against schemas with support for abortEarly.
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useValidate.ts Introduced useValidate hook for managing validation logic with support for different validation types.
packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/schema.ts Defined initial validation schema for the form with various fields and validation rules.

Possibly related PRs

Suggested reviewers

  • Tomer Shvadron
  • Matan Yadaev

Poem

🐰 In the meadow, changes bloom,
Validators dance, dispelling gloom.
With hooks and tests, our forms take flight,
Ensuring data shines so bright!
A hop, a skip, let errors flee,
In our code, all will be free! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 51

🧹 Outside diff range and nitpick comments (61)
packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/validate.ts (1)

82-82: Remove unnecessary optional chaining operator

Since value is confirmed to be an array, the optional chaining operator ?. in value?.forEach is unnecessary and can be removed to simplify the code.

Apply this diff to remove the optional chaining:

-          value?.forEach((_, index) => {
+          value.forEach((_, index) => {
packages/ui/src/components/organisms/Form/Validator/validators/format/types.ts (1)

1-3: Consider using a union type for format to allow for future extensions

Currently, the format property is limited to 'email'. To facilitate future enhancements, consider defining format as a union type of possible formats.

Apply this diff to extend the format type:

-   format: 'email';
+   format: 'email' | 'url' | 'date'; // Add additional formats as needed

This change will make it easier to support additional validation formats in the future.

packages/ui/src/components/organisms/Form/Validator/validators/minimum/types.ts (1)

1-3: Add JSDoc documentation to improve developer experience.

Consider adding JSDoc documentation to describe the interface's purpose and provide usage examples:

+/**
+ * Parameters for minimum value validation
+ * @example
+ * ```typescript
+ * const params: IMinimumValueValidatorParams = { minimum: 18 };
+ * // Used to validate age restrictions, minimum amounts, etc.
+ * ```
+ */
 export interface IMinimumValueValidatorParams {
+  /** The minimum allowed value (inclusive) */
   minimum: number;
 }
packages/ui/src/components/organisms/Form/Validator/validators/min-length/types.ts (2)

1-3: Add documentation and type constraints, matching max-length implementation.

Similar improvements as suggested for max-length:

+/**
+ * Parameters for minimum length validation
+ * @example
+ * ```typescript
+ * const params: IMinLengthValueValidatorParams = { minLength: 8 };
+ * // Used to validate minimum password length, minimum input length, etc.
+ * ```
+ */
 export interface IMinLengthValueValidatorParams {
+  /** The minimum allowed length (inclusive) */
   minLength: number;
 }

1-3: Consider creating a shared validation utilities module.

Since all three validator types share similar patterns, consider creating a shared utilities module:

// shared/validation-types.ts
export type ValidatorNumber = number & { readonly brand: unique symbol };

export interface IValidatorRange {
  createValue(value: number): ValidatorNumber;
  validate(value: number): boolean;
}

export function createRangeValidator(
  options: { min?: number; max?: number; integer?: boolean }
): IValidatorRange {
  return {
    createValue(value: number) {
      if (options.integer && !Number.isInteger(value)) {
        throw new Error('Value must be an integer');
      }
      if (options.min !== undefined && value < options.min) {
        throw new Error(`Value must be >= ${options.min}`);
      }
      if (options.max !== undefined && value > options.max) {
        throw new Error(`Value must be <= ${options.max}`);
      }
      return value as ValidatorNumber;
    },
    validate(value: number) {
      try {
        this.createValue(value);
        return true;
      } catch {
        return false;
      }
    }
  };
}

This would provide consistent validation behavior across all numeric validators and reduce code duplication.

packages/ui/src/components/organisms/Form/Validator/context/types.ts (1)

1-1: Consider adding validation rule types

Consider adding types for validation rules in this file:

export interface IValidationRule<T = any> {
  validate: (value: T) => boolean | Promise<boolean>;
  message: string;
  errorCode?: string;
}

export interface IValidationSchema<T> {
  [K in keyof T]?: IValidationRule[];
}
packages/ui/src/components/organisms/Form/Validator/hooks/external/useValidator/useValidator.ts (2)

4-12: Consider adding memoization and improving error message

The implementation follows React hooks best practices but could be enhanced:

Consider these improvements:

+import { useMemo } from 'react';
+
 export const useValidator = () => {
   const context = useContext(ValidatorContext);
 
   if (!context) {
-    throw new Error('Validator context is not provided.');
+    throw new Error(
+      'Validator context is not provided. Ensure this hook is used within a ValidatorProvider.'
+    );
   }
 
-  return context;
+  // Memoize the context value to prevent unnecessary re-renders
+  return useMemo(() => context, [context]);
 };

1-2: Consider adding type exports

For better reusability, consider exporting the return type of the hook.

Add type export:

 import { useContext } from 'react';
 import { ValidatorContext } from '../../../context';
+
+export type ValidatorContextValue = NonNullable<ReturnType<typeof useValidator>>;
packages/ui/src/components/organisms/Form/_Validator/hooks/useValidatedInput/useValidatedInput.ts (2)

1-2: Standardize import paths

The imports use inconsistent path styles (@/ vs relative). Consider standardizing to either absolute imports (@/) or relative imports throughout the codebase.


4-8: Add error handling and optimize performance

The hook could benefit from some improvements:

  1. Add error handling for undefined element IDs
  2. Memoize the return value to prevent unnecessary re-renders
 export const useValidatedInput = (element: UIElement) => {
   const { errors } = useValidator();
+  const elementId = element.getId();
+  
+  if (!elementId) {
+    console.warn('Element ID is undefined in useValidatedInput');
+    return [];
+  }
 
-  return errors[element.getId()];
+  return useMemo(() => errors[elementId], [errors, elementId]);
 };
packages/ui/src/components/organisms/Form/_Validator/validator.context.ts (2)

4-4: Fix typo in type name

The type name contains a typo: TFielName should be TFieldName.

-type TFielName = string;
+type TFieldName = string;

6-10: Add JSDoc documentation for public exports

Consider adding JSDoc documentation for the exported types and interfaces to improve maintainability.

+/** Record of validation errors keyed by field name */
 export type TValidationErrors = Record<TFielName, string[]>;
 
+/** Context interface for the validator provider */
 export interface IValidatorContext {
   validate: () => TIsValid;
   errors: TValidationErrors;
 }
packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern-validator.ts (1)

1-4: Consider adding JSDoc documentation

Add JSDoc documentation to describe the validator's purpose, parameters, and potential errors.

+/**
+ * Validates if a string matches the specified regex pattern.
+ * @param value - The string to validate
+ * @param params - Validation parameters containing the pattern and optional error message
+ * @throws {Error} If the value doesn't match the pattern or if the pattern is invalid/unsafe
+ */
 export const patternValueValidator: TValidator<string, IPatternValidatorParams> = (
packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/useValidate.ts (1)

10-16: Consider adding validation state to IValidationError

The IValidationError interface could benefit from additional fields to track validation state and severity.

Consider extending the interface:

 export interface IValidationError {
   id: string;
   message: string;
   element: UIElementV2;
   valueDestination: string;
   stack: number[];
+  severity: 'error' | 'warning' | 'info';
+  timestamp: number;
+  validated: boolean;
 }
packages/ui/src/components/organisms/Form/Validator/validators/format/format-validator.ts (1)

22-22: Enhance error message for unsupported formats

The error message for unsupported formats could be more helpful.

Consider listing supported formats in the error message:

-  throw new Error(`Format validator ${params.value.format} is not supported.`);
+  throw new Error(
+    `Format '${params.value.format}' is not supported. Supported formats: email`
+  );
packages/ui/src/components/organisms/Form/_Validator/value-validators/required.value-validator.ts (1)

17-21: Improve error message handling

The error message could be more specific about the validation failure.

Consider enhancing the error message:

 private getErrorMessage() {
-  if (!this.params.message) return `Value is required.`;
+  if (!this.params.message) {
+    const valueType = Array.isArray(this.value) ? 'array' : typeof this.value;
+    return `Value is required. Received: ${valueType}`;
+  }

   return this.params.message;
 }
packages/ui/src/components/organisms/Form/_Validator/value-validators/pattern.value.validator.ts (1)

17-22: Consider caching the error message

The error message is reconstructed on every validation failure. Consider caching it in the constructor for better performance.

+private readonly errorMessage: string;
+
+constructor(params: IPatternValidatorParams) {
+  super(params);
+  this.errorMessage = this.buildErrorMessage();
+}
+
 private getErrorMessage() {
+  return this.errorMessage;
+}
+
+private buildErrorMessage() {
   if (!this.params.message)
     return `Value must match {pattern}`.replace('{pattern}', this.params.pattern);
   return this.params.message.replace('{pattern}', this.params.pattern);
 }
packages/ui/src/components/organisms/Form/_Validator/value-validators/maximum.value.validator.ts (1)

17-22: Consider enhancing error messages with actual value

The error message could be more helpful by including the actual value that failed validation.

  private getErrorMessage() {
    if (!this.params.message)
-     return `Maximum value is {maximum}.`.replace('{maximum}', this.params.maximum.toString());
+     return `Value exceeds maximum of {maximum}.`.replace('{maximum}', this.params.maximum.toString());

    return this.params.message.replace('{maximum}', this.params.maximum.toString());
  }
packages/ui/src/components/organisms/Form/_Validator/value-validators/min-length.value.validator.ts (1)

1-26: Consider extracting shared validation patterns

All three validators share similar patterns for message templating and parameter validation. Consider creating:

  1. A shared message template utility
  2. A base parameter validation method
  3. Common type definitions for validation parameters

Example shared utility:

// packages/ui/src/components/providers/Validator/utils/message-template.ts
export const replaceTemplateVariables = (
  message: string,
  variables: Record<string, string | number>
): string => {
  return Object.entries(variables).reduce(
    (msg, [key, value]) => msg.replace(`{${key}}`, String(value)),
    message
  );
};
packages/ui/src/components/organisms/Form/_Validator/value-validators/max-length.value.validator.ts (1)

17-25: Consider adding i18n support for error messages

The error message handling could benefit from internationalization support for better maintainability and localization capabilities.

Consider integrating with an i18n library and using translation keys instead of hardcoded messages.

packages/ui/src/components/organisms/Form/Validator/types/index.ts (1)

8-14: Consider extending base validators with common formats

The TBaseValidators type could be extended to include common format validations (email, url, etc.) for better type safety and autocomplete support.

 export type TBaseValidators =
   | 'required'
   | 'minLength'
   | 'maxLength'
   | 'pattern'
   | 'minimum'
-  | 'maximum';
+  | 'maximum'
+  | 'email'
+  | 'url'
+  | 'date';
packages/ui/src/components/organisms/Form/_Validator/value-validators/format.value.validator.ts (1)

5-7: Document supported formats

The IFormatValueValidatorParams interface should include documentation about supported formats to improve developer experience.

Add JSDoc comments to document the supported formats and their validation rules.

packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum.validator.unit.test.ts (1)

5-34: Improve test organization with nested describes

The test structure could be more organized to group related test cases.

Consider this structure:

describe('minimumValueValidator', () => {
  const params = {
    value: { minimum: 5 },
  };

  describe('valid inputs', () => {
    it('should not throw error when value is equal to minimum');
    it('should not throw error when value is greater than minimum');
  });

  describe('invalid inputs', () => {
    it('should throw error when value is less than minimum');
    it('should handle null/undefined values');
    it('should handle non-numeric values');
  });

  describe('error messages', () => {
    it('should use default error message');
    it('should handle custom error message');
  });
});
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/useValidatorRef.unit.test.ts (1)

17-19: Enhance test cleanup

The beforeEach cleanup should also reset the mockRef.

 beforeEach(() => {
   vi.clearAllMocks();
+  mockRef.current = null;
 });
packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/format-error-message.unit.test.ts (1)

1-3: Consider adding test helper utilities

For better test maintainability, consider extracting common test data and helper functions.

// Add at the top of the file
const createMessage = (template: string, key: string, value: string) => ({
  template,
  key,
  value,
  expected: template.replace(new RegExp(`{${key}}`, 'g'), value),
});

const testCases = [
  createMessage('This is a {test} message', 'test', 'sample'),
  createMessage('The {value} is equal to {value}', 'value', '42'),
  // ... more test cases
];
packages/ui/src/components/organisms/Form/Validator/utils/get-validator/get-validator.unit.test.ts (2)

22-22: Consider improving type safety in tests

The use of unknown type casting could be replaced with proper type definitions to maintain type safety:

- const result = getValidator({ type: validatorType } as unknown as ICommonValidator);
+ const result = getValidator({ type: validatorType as TBaseValidators } satisfies ICommonValidator);

Also applies to: 30-30, 37-37


11-40: Enhance test coverage with additional scenarios

Consider adding these test cases:

  1. Verify priority when a validator exists in both maps
  2. Test validation of the validator type parameter
  3. Test error cases with invalid validator configurations
packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern.validator.unit.test.ts (2)

6-9: Consider testing more complex pattern scenarios

The current test only validates a simple uppercase pattern. Consider adding tests for:

  1. Complex regex patterns (email, URL, etc.)
  2. Invalid regex patterns
  3. Special characters in patterns
  4. Multi-line patterns

31-41: Enhance edge case coverage

While empty string and undefined are tested, consider adding tests for:

  1. null values
  2. Non-string inputs (numbers, objects, etc.)
  3. Very long strings
  4. Unicode characters
packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum.validator.unit.test.ts (2)

5-41: Enhance numeric validation test coverage

Consider adding these test cases:

  1. Edge values: NaN, Infinity, -Infinity
  2. Different number types: integers, floats, BigInt
  3. Invalid maximum values (negative, zero, non-numeric)
  4. String numbers ("10", "10.5")
  5. Very large numbers close to Number.MAX_SAFE_INTEGER

35-40: Consider splitting decimal value tests

The decimal value test combines two assertions in one test. Consider splitting them into separate test cases for better clarity and failure isolation:

it('should throw error for values slightly above maximum', () => {
  expect(() => maximumValueValidator(10.1, params as ICommonValidator<any>))
    .toThrow('Maximum value is 10.');
});

it('should allow values slightly below maximum', () => {
  expect(() => maximumValueValidator(9.9, params as ICommonValidator<any>))
    .not.toThrow();
});
packages/ui/src/components/organisms/Form/Validator/validators/required/required.validator.unit.test.ts (1)

5-46: Add test cases for additional edge cases

The test suite should include additional cases:

  1. Whitespace-only strings (e.g., " ")
  2. Empty arrays and objects
  3. Boolean false value

Consider adding these test cases:

it('should throw error when value is whitespace-only string', () => {
  expect(() => requiredValueValidator('   ', params)).toThrow('Required value.');
});

it('should throw error when value is empty array', () => {
  expect(() => requiredValueValidator([], params)).toThrow('Required value.');
});

it('should throw error when value is empty object', () => {
  expect(() => requiredValueValidator({}, params)).toThrow('Required value.');
});

it('should not throw error when value is false', () => {
  expect(() => requiredValueValidator(false, params)).not.toThrow();
});
packages/ui/src/components/organisms/Form/_Validator/Validator.tsx (3)

19-19: Remove console.log statement

Remove debugging console.log statement before production deployment.

-console.log({ validationErrors });

17-17: Fix typo in setState function name

There's a typo in the setState function name.

-const [validationErrors, setValiationErrors] = useState<TValidationErrors>({});
+const [validationErrors, setValidationErrors] = useState<TValidationErrors>({});

9-13: Add prop types validation

Consider adding prop-types validation for better runtime type checking.

import PropTypes from 'prop-types';

// After component definition
Validator.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.node,
    PropTypes.arrayOf(PropTypes.node)
  ]).isRequired,
  context: PropTypes.any.isRequired,
  elements: PropTypes.arrayOf(PropTypes.shape({
    // Add specific UIElementV2 shape validation
  })).isRequired
};
packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length.validator.unit.test.ts (1)

5-44: Add test cases for edge cases and input validation

The test suite should include additional cases:

  1. Non-string inputs (numbers, objects)
  2. Unicode characters and emojis
  3. Input validation for negative maxLength

Consider adding these test cases:

it('should handle unicode characters correctly', () => {
  expect(() => maxLengthValidator('🌟🌟🌟', { value: { maxLength: 3 } })).not.toThrow();
  expect(() => maxLengthValidator('🌟🌟🌟🌟', { value: { maxLength: 3 } })).toThrow();
});

it('should throw error for invalid maxLength value', () => {
  expect(() => 
    maxLengthValidator('test', { value: { maxLength: -1 } })
  ).toThrow('Invalid maxLength value');
});

it('should handle non-string inputs', () => {
  expect(() => 
    maxLengthValidator(123, { value: { maxLength: 5 } })
  ).toThrow('Input must be a string');
});
packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length.validator.unit.test.ts (1)

5-46: Enhance test coverage with additional edge cases

While the current test suite covers basic scenarios well, consider adding these test cases for more robust coverage:

  • Test with special characters/unicode strings
  • Test with non-string types (arrays, objects)
  • Test with null values
  • Test with very large strings near system limits

Example additions:

it('should handle special characters correctly', () => {
  expect(() => minLengthValidator('テスト', params)).not.toThrow();
});

it('should handle null value', () => {
  expect(() => minLengthValidator(null, params)).toThrow();
});

it('should handle array value', () => {
  expect(() => minLengthValidator([], params)).toThrow();
});
packages/ui/src/components/organisms/Form/Validator/validators/format/format.validator.unit.test.ts (1)

40-51: Consider expanding supported formats

The test for unsupported formats suggests that phone validation might be needed. Consider implementing phone number validation using a library like libphonenumber-js.

packages/ui/src/components/organisms/Form/_Validator/value-validator-manager.ts (1)

11-20: Consider implementing validator registration mechanism

The current implementation hardcodes validators in a map. Consider implementing a registration mechanism for better extensibility.

Example:

export class ValueValidatorManager {
  private validators = new Map<string, typeof BaseValidator>();

  registerValidator(key: string, validator: typeof BaseValidator) {
    this.validators.set(key, validator);
  }

  // Register default validators in constructor
  constructor() {
    this.registerValidator('required', RequiredValueValidator);
    this.registerValidator('minLength', MinLengthValueValidator);
    // ... register other validators
  }
}
packages/ui/src/components/organisms/Form/_Validator/value-validators/pattern.value.validator.unit.test.ts (2)

4-16: Add edge cases to validation failure tests

While the basic failure cases are covered, consider adding tests for:

  • Empty string input
  • Special characters that might need escaping
  • Multi-line input

48-49: Fix typo in test description

"interlopation" should be "interpolation"

-    test('error message should stay same if interlopation tag is not present', () => {
+    test('error message should stay same if interpolation tag is not present', () => {
packages/ui/src/components/organisms/Form/_Validator/value-validators/required.value-validator.unit.test.ts (2)

3-42: Enhance test coverage for edge cases

While the current tests cover the basic scenarios, consider adding tests for:

  1. When required: false to ensure validation is skipped
  2. Edge cases that could be falsy but valid (e.g., empty arrays, empty objects)

Example test cases to add:

test('should skip validation when required is false', () => {
  const validator = new RequiredValueValidator({ message: 'error', required: false });
  expect(() => validator.validate('')).not.toThrow();
});

test('should validate empty array as invalid', () => {
  const validator = new RequiredValueValidator({ message: 'error', required: true });
  expect(() => validator.validate([])).toThrowError('error');
});

6-8: Consider using test.each for similar test cases

The validation failure tests follow a similar pattern. Consider using test.each to make the tests more maintainable.

test.each([
  ['undefined', undefined],
  ['empty string', ''],
  ['null', null],
])('should fail when value is %s', (_, value) => {
  const validator = new RequiredValueValidator({ message: 'error', required: true });
  expect(() => validator.validate(value)).toThrowError('error');
});

Also applies to: 12-14, 18-20

packages/ui/src/components/organisms/Form/_Validator/types.ts (2)

11-11: Consider expanding TFormats type

The TFormats type currently only supports 'email'. Consider adding other common formats like 'url', 'date', 'phone', etc.

-export type TFormats = 'email';
+export type TFormats = 'email' | 'url' | 'date' | 'phone' | 'ipv4' | 'ipv6';

43-53: Consider making validation field required

In UIElementV2 interface, the validation field is marked as Partial<Record<...>>, which means all validators are optional. Consider making certain validators required based on the element type.

Also, consider adding JSDoc comments to document the purpose of each field, especially valueDestination, availableOn, and visibleOn.

packages/ui/src/components/organisms/Form/_Validator/value-validators/format.value.validator.unit.test.ts (2)

5-30: Enhance test coverage for format validation

The current tests only cover email format. Consider adding:

  1. Tests for other supported formats (if any)
  2. Edge cases for email validation:
    • Empty string
    • Whitespace-only string
    • Complex email formats (e.g., with subdomains)

52-57: Fix typo in test description

"interlopation" should be "interpolation"

-    test('error message should stay same if interlopation tag is not present', () => {
+    test('error message should stay same if interpolation tag is not present', () => {
packages/ui/src/components/organisms/Form/_Validator/value-validators/minimum.value.validator.unit.test.ts (2)

4-22: Add tests for edge cases

Consider adding tests for:

  1. Non-numeric values
  2. Decimal values
  3. Very large numbers
  4. Negative numbers

57-62: Fix typo in test description

"interlopation" should be "interpolation"

-    test('error message should stay same if interlopation tag is not present', () => {
+    test('error message should stay same if interpolation tag is not present', () => {
packages/ui/src/components/organisms/Form/_Validator/value-validators/maximum.value.validator.unit.test.ts (2)

1-63: Consider creating shared test utilities

The test structure is identical across all validator test files. Consider:

  1. Creating shared test utilities for common validator test scenarios
  2. Using a test factory pattern to reduce code duplication

Example approach:

// testUtils.ts
export const createValidatorTests = (
  ValidatorClass: any,
  config: {
    validValues: any[];
    invalidValues: any[];
    paramName: string;
    paramValue: any;
  }
) => {
  describe('validation', () => {
    // Generate standard test cases
  });
};

// maximum.value.validator.unit.test.ts
createValidatorTests(MaximumValueValidator, {
  validValues: [4, 5],
  invalidValues: [6, null, undefined],
  paramName: 'maximum',
  paramValue: 5
});

57-62: Fix typo in test description

"interlopation" should be "interpolation"

-    test('error message should stay same if interlopation tag is not present', () => {
+    test('error message should stay same if interpolation tag is not present', () => {
packages/ui/src/components/organisms/Form/_Validator/value-validators/min-length.value.validator.unit.test.ts (2)

8-8: Consider using a more specific type instead of any

Instead of using any type casting, consider using a more specific type or undefined directly to maintain type safety.

-expect(() => validator.validate(undefined as any)).toThrowError('error');
+expect(() => validator.validate(undefined)).toThrowError('error');

57-57: Fix typo in test description

There's a typo in the test description: "interlopation" should be "interpolation"

-test('error message should stay same if interlopation tag is not present', () => {
+test('error message should stay same if interpolation tag is not present', () => {
packages/ui/src/components/organisms/Form/_Validator/value-validators/max-length.value.validator.unit.test.ts (2)

8-8: Consider using a more specific type instead of any

Instead of using any type casting, consider using a more specific type or undefined directly to maintain type safety.

-expect(() => validator.validate(undefined as any)).toThrowError('error');
+expect(() => validator.validate(undefined)).toThrowError('error');

57-57: Fix typo in test description

There's a typo in the test description: "interlopation" should be "interpolation"

-test('error message should stay same if interlopation tag is not present', () => {
+test('error message should stay same if interpolation tag is not present', () => {
packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/ui-element.ts (3)

21-36: Simplify boolean logic and improve type safety in isRequired()

The method has complex boolean logic and uses type casting that could be improved.

 isRequired() {
   const requiredParams = this.element.validation?.required as IRequiredValueValidatorParams;
   const documentParams = this.element.validation?.document as IDocumentValueValidatorParams;

   const applyRules = requiredParams?.applyWhen || documentParams?.applyWhen || [];

   if (applyRules.length) {
-    const isShouldApplyRequired = applyRules.every(rule =>
-      testRule(this.context as AnyObject, rule),
-    );
-
-    return Boolean(isShouldApplyRequired);
+    return applyRules.every(rule => testRule(this.context as AnyObject, rule));
   } else {
-    return Boolean(requiredParams?.required) || Boolean(documentParams?.documentId);
+    return !!(requiredParams?.required || documentParams?.documentId);
   }
 }

46-52: Consider consolidating formatting methods

The three formatting methods could be consolidated into a single method to reduce code duplication.

-private formatValueDestination(valueDestination: string) {
-  return this.formatValueDestinationAndApplyStackIndexes(valueDestination);
-}
-
-private formatValueDestinationAndApplyStackIndexes(valueDestination: string) {
-  return formatValueDestinationAndApplyStackIndexes(valueDestination, this.stack);
-}
+private formatValueDestination(valueDestination: string) {
+  return formatValueDestinationAndApplyStackIndexes(valueDestination, this.stack);
+}

73-78: Consider using enum or constants for field types

The field type strings are hardcoded. Consider using an enum or constants for better maintainability.

+const FIELD_TYPES = {
+  FIELD_LIST: 'field-list',
+  FIELD: 'field',
+  UI: 'ui',
+} as const;
+
 getFieldType() {
-  if (this.element.element === 'fieldlist') return 'field-list';
-  if (this.element.element in fieldElelements) return 'field';
+  if (this.element.element === 'fieldlist') return FIELD_TYPES.FIELD_LIST;
+  if (this.element.element in fieldElelements) return FIELD_TYPES.FIELD;

-  return 'ui';
+  return FIELD_TYPES.UI;
 }
packages/ui/package.json (1)

80-81: Consider updating testing library versions

The specified versions of testing libraries are quite old:

  • @testing-library/dom@^10.4.0 (latest: 9.3.3)
  • @testing-library/react@^13.3.0 (latest: 14.1.2)

Consider updating to the latest versions for better React 18 compatibility and new features.

packages/ui/src/components/organisms/Form/_Validator/value-validators/document.value.validator.unit.test.ts (2)

11-13: Replace 'as any' type assertions with proper types

Consider creating proper type definitions for the test data instead of using 'as any' assertions. This would make the tests more type-safe and serve as documentation.

Example:

interface ValidationContext {
  stack: number[];
  context: {
    data?: {
      items?: Array<{
        documents: Document[];
      }>;
    };
  };
}

interface Document {
  id: string;
  pages: Array<{
    ballerineFileId?: string;
  }>;
}

Also applies to: 23-25, 34-53, 63-71


4-73: Add test cases for invalid document structure and bounds checking

The "validation will fail" section has good coverage but could be enhanced with:

  1. Test case for invalid document structure (missing pages array)
  2. Test case for pageIndex out of bounds
  3. Test case for negative pageIndex

Example test cases:

test('when document has invalid structure (missing pages)', () => {
  const validator = new DocumentValueValidator({
    documentId: '123',
    pathToDocuments: 'documents',
  });
  
  const context = {
    documents: [{ id: '123' }], // Missing pages array
  };
  
  expect(() => 
    validator.validate(null, { context, stack: [] })
  ).toThrowError();
});

test('when pageIndex is out of bounds', () => {
  const validator = new DocumentValueValidator({
    documentId: '123',
    pathToDocuments: 'documents',
    pageIndex: 999,
  });
  
  const context = {
    documents: [{
      id: '123',
      pages: [{ ballerineFileId: 'someFileId' }],
    }],
  };
  
  expect(() => 
    validator.validate(null, { context, stack: [] })
  ).toThrowError();
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a0d208a and 75d4a34.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (82)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/organisms/Form/.gitignore (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/context/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/context/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/context/validator-context.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/external/useValidator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/external/useValidator/useValidator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/useValidatorRef.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/useValidatorRef.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/types/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/format-error-message.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/format-error-message.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/get-validator/get-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/get-validator/get-validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/get-validator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/register-validator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/register-validator/register-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/register-validator/register-validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/format/format-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/format/format.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/format/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/format/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum-value-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/required/required-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/required/required.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/required/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/Validator.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/ui-element.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/useValidate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/utils/format-value-destination-and-apply-stack-indexes.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/validate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidatedInput/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidatedInput/useValidatedInput.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidator/useValidator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/validator.context.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validator-manager.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/document.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/document.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/format.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/format.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/max-length.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/max-length.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/maximum.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/maximum.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/min-length.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/min-length.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/minimum.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/minimum.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/pattern.value.validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/pattern.value.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/required.value-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/required.value-validator.unit.test.ts (1 hunks)
⛔ Files not processed due to max files limit (3)
  • packages/ui/src/components/organisms/Form/_Validator/value-validators/value-validator.abstract.ts
  • packages/ui/src/setupTests.ts
  • packages/ui/vite.config.ts
✅ Files skipped from review due to trivial changes (21)
  • packages/ui/src/components/organisms/Form/.gitignore
  • packages/ui/src/components/organisms/Form/_Validator/index.ts
  • packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/index.ts
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/index.ts
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidator/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/types.ts
  • packages/ui/src/components/organisms/Form/Validator/utils/register-validator/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/index.ts
  • packages/ui/src/components/organisms/Form/Validator/context/index.ts
  • packages/ui/src/components/organisms/Form/Validator/utils/get-validator/index.ts
  • packages/ui/src/components/organisms/Form/Validator/hooks/external/useValidator/index.ts
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/index.ts
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidatedInput/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/required/types.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/format/index.ts
  • packages/ui/src/components/organisms/Form/Validator/index.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/index.ts
  • packages/ui/src/components/organisms/Form/_Validator/hooks/useValidate/index.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/ui/src/components/organisms/Form/_Validator/value-validators/document.value.validator.ts

[error] 26-26: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

🔇 Additional comments (11)
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/types.ts (1)

1-3: LGTM!

The interface IValidatorRef is correctly defined and follows TypeScript best practices.

packages/ui/src/components/organisms/Form/Validator/validators/pattern/types.ts (1)

1-3: LGTM!

The interface IPatternValidatorParams is properly defined with a pattern string parameter.

packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidatorRef/useValidatorRef.ts (1)

1-4: ⚠️ Potential issue

Add missing React imports

The implementation is missing required React imports.

Add the missing imports:

-import { useImperativeHandle } from 'react';
+import { useImperativeHandle, useMemo } from 'react';
 import { useValidator } from '../../external/useValidator/useValidator';
 import { IValidatorRef } from './types';

Likely invalid or redundant comment.

packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length-validator.ts (1)

11-13: 🛠️ Refactor suggestion

Add null/undefined check for input value

The validator should explicitly handle undefined/null values before checking length. Currently, optional chaining (?.) is used, but the logic could be clearer.

Consider this implementation:

-  if (value?.length > params.value.maxLength) {
+  if (value === undefined || value === null) {
+    throw new Error(formatErrorMessage(message, 'maxLength', params.value.maxLength.toString()));
+  }
+  if (value.length > params.value.maxLength) {
     throw new Error(formatErrorMessage(message, 'maxLength', params.value.maxLength.toString()));
   }

Likely invalid or redundant comment.

packages/ui/src/components/organisms/Form/Validator/validators/index.ts (1)

18-18: LGTM! Well-structured validator extension mechanism.

The validatorsExtends object provides a clean way to extend the validation system with custom validators.

packages/ui/src/components/organisms/Form/Validator/utils/register-validator/register-validator.unit.test.ts (1)

5-7: LGTM! Clean mock implementation.

Good use of vi.mock for isolating the test cases.

packages/ui/src/components/organisms/Form/_Validator/value-validators/format.value.validator.ts (1)

1-3: 🛠️ Refactor suggestion

Consider adding input sanitization

The validator directly passes the input to the email validator without sanitization. Consider adding input sanitization to prevent potential security issues.

Let's check if there are any security measures in place:

packages/ui/src/components/organisms/Form/_Validator/value-validators/pattern.value.validator.unit.test.ts (1)

1-1: Consider using a more specific import path

The import path seems to be inconsistent with the file location. The test file is in organisms/Form/_Validator but imports from providers/Validator.

packages/ui/package.json (1)

54-54: Verify email-validator dependency

The email-validator package is pinned to the latest version (2.0.4). However, let's verify there are no known vulnerabilities.

✅ Verification successful

✅ email-validator dependency is secure and up-to-date

The package is pinned to version 2.0.4, which is currently the latest version available on npm registry. No security vulnerabilities were found in the GitHub Security Advisory Database for this package.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for security advisories for email-validator package

# Check PyPI for latest versions
curl -s https://registry.npmjs.org/email-validator | jq '.["dist-tags"].latest'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "email-validator") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 438

packages/ui/src/components/organisms/Form/_Validator/value-validators/document.value.validator.unit.test.ts (2)

122-159: Well-structured test cases for nested data

The test.each implementation for nested data structures is well-organized and comprehensive. It effectively tests various levels of nesting using parameterized tests.


162-185: Good error message testing coverage

The error message tests effectively verify both default and custom error messages.


const document = documents?.find((document: Document) => document.id === documentId);

debugger;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove the unnecessary debugger statement

There is an unexpected debugger; statement on line 26, which can interrupt execution and should be removed before committing the code.

Apply this diff to remove the debugger statement:

-    debugger;
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-26: This is an unexpected use of the debugger statement.

Unsafe fix: Remove debugger statement

(lint/suspicious/noDebugger)

Comment on lines 65 to 67
const element = elements[i] as UIElementV2;
const uiElement = new UIElement(element, context, stack);
if (!element) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Check for undefined element before instantiating UIElement

The element is being used to create a new UIElement instance before checking if it is undefined. This could lead to runtime errors if element is undefined. Move the null check before using element to prevent potential issues.

Apply this diff to correct the order of operations:

         const element = elements[i] as UIElementV2;
+        if (!element) continue;
         const uiElement = new UIElement(element, context, stack);
-        if (!element) continue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const element = elements[i] as UIElementV2;
const uiElement = new UIElement(element, context, stack);
if (!element) continue;
const element = elements[i] as UIElementV2;
if (!element) continue;
const uiElement = new UIElement(element, context, stack);

Comment on lines 50 to 51
//@ts-ignore
message: error.message,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using @ts-ignore; properly handle the error object

Using @ts-ignore suppresses TypeScript's type checking and may hide legitimate errors. Instead, ensure the error object is appropriately typed or use a type guard to handle it safely.

Apply this diff to handle the error typing:

          } catch (error) {
-           //@ts-ignore
-           message: error.message,
+           if (error instanceof Error) {
+             return {
+               message: error.message,
+               element: element.element,
+               id: element.getId(),
+               valueDestination: element.getValueDestination(),
+               stack,
+             };
+           } else {
+             return {
+               message: 'An unknown error occurred during validation.',
+               element: element.element,
+               id: element.getId(),
+               valueDestination: element.getValueDestination(),
+               stack,
+             };
+           }
          }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +3
export interface IMaxLengthValueValidatorParams {
maxLength: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc documentation and consider adding type constraints.

  1. Add documentation for clarity:
+/**
+ * Parameters for maximum length validation
+ * @example
+ * ```typescript
+ * const params: IMaxLengthValueValidatorParams = { maxLength: 100 };
+ * // Used to validate input length, such as comment length, description length, etc.
+ * ```
+ */
 export interface IMaxLengthValueValidatorParams {
+  /** The maximum allowed length (inclusive) */
   maxLength: number;
 }
  1. Consider adding a type constraint to ensure positive values:
 export interface IMaxLengthValueValidatorParams {
-  maxLength: number;
+  maxLength: number & { readonly brand: unique symbol };
 }

+/**
+ * Creates a valid maxLength parameter
+ * @throws {Error} if value is negative or not an integer
+ */
+export function createMaxLength(value: number): IMaxLengthValueValidatorParams['maxLength'] {
+  if (value < 0 || !Number.isInteger(value)) {
+    throw new Error('maxLength must be a non-negative integer');
+  }
+  return value as IMaxLengthValueValidatorParams['maxLength'];
+}

Comment on lines +1 to +2
export const formatErrorMessage = (message: string, key: string, value: string) =>
message.replaceAll(`{${key}}`, value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and improve type safety

The current implementation could be enhanced in several ways:

  1. Add null/undefined checks
  2. Support multiple value types (number, boolean)
  3. Handle edge cases with special characters

Consider this improved implementation:

-export const formatErrorMessage = (message: string, key: string, value: string) =>
-  message.replaceAll(`{${key}}`, value);
+export const formatErrorMessage = (
+  message: string,
+  key: string,
+  value: string | number | boolean
+) => {
+  if (!message || !key) return message;
+  const safeValue = String(value);
+  return message.replaceAll(`{${key}}`, safeValue);
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const formatErrorMessage = (message: string, key: string, value: string) =>
message.replaceAll(`{${key}}`, value);
export const formatErrorMessage = (
message: string,
key: string,
value: string | number | boolean
) => {
if (!message || !key) return message;
const safeValue = String(value);
return message.replaceAll(`{${key}}`, safeValue);
};

Comment on lines 17 to 25
export type TValidationParams =
| IFormatValueValidatorParams
| IMaxLengthValueValidatorParams
| IMinLengthValueValidatorParams
| IMaximumValueValidatorParams
| IMinLengthValueValidatorParams
| IRequiredValueValidatorParams
| IPatternValidatorParams
| IDocumentValueValidatorParams;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix duplicate import in TValidationParams

IMinLengthValueValidatorParams appears twice in the union type.

export type TValidationParams =
  | IFormatValueValidatorParams
  | IMaxLengthValueValidatorParams
  | IMinLengthValueValidatorParams
  | IMaximumValueValidatorParams
- | IMinLengthValueValidatorParams
  | IRequiredValueValidatorParams
  | IPatternValidatorParams
  | IDocumentValueValidatorParams;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type TValidationParams =
| IFormatValueValidatorParams
| IMaxLengthValueValidatorParams
| IMinLengthValueValidatorParams
| IMaximumValueValidatorParams
| IMinLengthValueValidatorParams
| IRequiredValueValidatorParams
| IPatternValidatorParams
| IDocumentValueValidatorParams;
export type TValidationParams =
| IFormatValueValidatorParams
| IMaxLengthValueValidatorParams
| IMinLengthValueValidatorParams
| IMaximumValueValidatorParams
| IRequiredValueValidatorParams
| IPatternValidatorParams
| IDocumentValueValidatorParams;

Comment on lines 18 to 19
//@ts-ignore
const validator = new FormatValueValidator({ format: 'unsupported', message: 'error' });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using @ts-ignore

Consider creating a type-safe approach instead of suppressing the type check. This could involve:

  1. Using a union type for supported formats
  2. Creating a test-specific mock validator

Comment on lines 25 to 35
test('when value is above minimum', () => {
const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });

expect(() => validator.validate(5)).not.toThrow();
});

test('when value is equal to minimum', () => {
const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });

expect(() => validator.validate(5)).not.toThrow();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove duplicate test case

The test cases "when value is above minimum" and "when value is equal to minimum" both test the same scenario (value = 5). Consider:

  1. Removing one of the tests
  2. Modifying "when value is above minimum" to actually test a value above the minimum
-    test('when value is above minimum', () => {
+    test('when value is strictly above minimum', () => {
       const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });
 
-      expect(() => validator.validate(5)).not.toThrow();
+      expect(() => validator.validate(6)).not.toThrow();
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('when value is above minimum', () => {
const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });
expect(() => validator.validate(5)).not.toThrow();
});
test('when value is equal to minimum', () => {
const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });
expect(() => validator.validate(5)).not.toThrow();
});
test('when value is strictly above minimum', () => {
const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });
expect(() => validator.validate(6)).not.toThrow();
});
test('when value is equal to minimum', () => {
const validator = new MinimumValueValidator({ minimum: 5, message: 'error' });
expect(() => validator.validate(5)).not.toThrow();
});

Comment on lines 25 to 35
test('when value is below maximum', () => {
const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });

expect(() => validator.validate(4)).not.toThrow();
});

test('when value is equal to maximum', () => {
const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });

expect(() => validator.validate(5)).not.toThrow();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove duplicate test case

Similar to the minimum validator tests, these test cases overlap. Consider:

  1. Removing one of the tests
  2. Modifying "when value is below maximum" to test a distinctly lower value
-    test('when value is below maximum', () => {
+    test('when value is strictly below maximum', () => {
       const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });
 
-      expect(() => validator.validate(4)).not.toThrow();
+      expect(() => validator.validate(3)).not.toThrow();
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('when value is below maximum', () => {
const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });
expect(() => validator.validate(4)).not.toThrow();
});
test('when value is equal to maximum', () => {
const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });
expect(() => validator.validate(5)).not.toThrow();
});
test('when value is strictly below maximum', () => {
const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });
expect(() => validator.validate(3)).not.toThrow();
});
test('when value is equal to maximum', () => {
const validator = new MaximumValueValidator({ maximum: 5, message: 'error' });
expect(() => validator.validate(5)).not.toThrow();
});

Comment on lines 60 to 67
getValidatorsParams() {
const validatorKeys = Object.keys(this.element.validation || {});

return validatorKeys.map(key => ({
validator: key,
params: this.element.validation[key as keyof UIElementV2['validation']] as TValidationParams,
}));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add type safety for validation parameters

The current implementation might access undefined properties. Consider adding type guards.

 getValidatorsParams() {
+  if (!this.element.validation) {
+    return [];
+  }
+
   const validatorKeys = Object.keys(this.element.validation || {});

   return validatorKeys.map(key => ({
     validator: key,
     params: this.element.validation[key as keyof UIElementV2['validation']] as TValidationParams,
   }));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getValidatorsParams() {
const validatorKeys = Object.keys(this.element.validation || {});
return validatorKeys.map(key => ({
validator: key,
params: this.element.validation[key as keyof UIElementV2['validation']] as TValidationParams,
}));
}
getValidatorsParams() {
if (!this.element.validation) {
return [];
}
const validatorKeys = Object.keys(this.element.validation || {});
return validatorKeys.map(key => ({
validator: key,
params: this.element.validation[key as keyof UIElementV2['validation']] as TValidationParams,
}));
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (5)
packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts (2)

8-9: Correct typos in TODO comments

There are minor typos in the TODO comments.

-// TODO: Codnitional Apply
+// TODO: Conditional Apply
-// TODO: Test coverage ror custom validators
+// TODO: Test coverage for custom validators

21-21: Avoid variable shadowing for better code clarity

Shadowing the schema variable within the forEach can lead to confusion. Consider renaming the parameter to improve readability.

-  schema.forEach(schema => {
+  schema.forEach(item => {
packages/ui/src/components/organisms/Form/Validator/utils/format-id/format-id.ts (1)

4-6: Avoid using underscore prefix for local variables

Using an underscore prefix (_id) for a local variable can be misleading. Consider renaming it to formattedId for clarity.

 export const formatId = (id: string, stack: TDeepthLevelStack) => {
-  const _id = `${id}${stack.length ? `-${stack.join('-')}` : ''}`;

-  return _id;
+  const formattedId = `${id}${stack.length ? `-${stack.join('-')}` : ''}`;

+  return formattedId;
 };
packages/ui/src/components/organisms/Form/Validator/utils/create-validation-error/create-validation-error.unit.test.ts (1)

4-58: Test suite structure looks good, but could be enhanced.

The test cases effectively validate the createValidationError function's behavior. However, consider these improvements:

  1. Extract test data into constants for reusability:
+const TEST_CASES = {
+  WITH_STACK: {
+    params: {
+      id: 'test',
+      invalidValue: 'invalid',
+      message: 'error message',
+      stack: [1, 2],
+    },
+    expected: {
+      id: 'test-1-2',
+      originId: 'test',
+      invalidValue: 'invalid',
+      message: ['error message'],
+    },
+  },
+  // ... other test cases
+};

 describe('createValidationError', () => {
-  it('should create validation error with formatted id', () => {
-    const params = {
-      id: 'test',
-      invalidValue: 'invalid',
-      message: 'error message',
-      stack: [1, 2],
-    };
+  it.each(Object.entries(TEST_CASES))('%s', (_, { params, expected }) => {
     const result = createValidationError(params);
-    expect(result).toEqual({
-      id: 'test-1-2',
-      originId: 'test',
-      invalidValue: 'invalid',
-      message: ['error message'],
-    });
+    expect(result).toEqual(expected);
   });
  1. Add test case for undefined/null stack:
it('should handle undefined stack', () => {
  const params = {
    id: 'test',
    invalidValue: 'invalid',
    message: 'error message',
    stack: undefined,
  };
  
  const result = createValidationError(params);
  
  expect(result).toEqual({
    id: 'test-',
    originId: 'test',
    invalidValue: 'invalid',
    message: ['error message'],
  });
});
packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.unit.test.ts (1)

76-374: Improve test data organization for better maintainability.

The test cases for plain objects contain a lot of duplicate test data and schema definitions.

Extract common test data and schemas into shared fixtures:

const TEST_SCHEMAS = {
  required: {
    id: 'name',
    valueDestination: 'name',
    validators: [{ type: 'required', message: 'Field is required.', value: {} }],
  },
  // ... other schemas
};

const TEST_CASES = {
  valid: [
    {
      name: 'required field',
      value: { name: 'John' },
      schema: [TEST_SCHEMAS.required],
    },
    // ... other cases
  ],
  invalid: [
    {
      name: 'required field with null',
      value: { name: null },
      schema: [TEST_SCHEMAS.required],
      expectedError: 'Field is required.',
    },
    // ... other cases
  ],
};

Then use these fixtures in the tests:

describe('plain objects', () => {
  describe('will be valid', () => {
    test.each(TEST_CASES.valid)('$name', ({ value, schema }) => {
      expect(validate(value, schema)).toEqual([]);
    });
  });
  
  describe('will be invalid', () => {
    test.each(TEST_CASES.invalid)('$name', ({ value, schema, expectedError }) => {
      const errors = validate(value, schema);
      expect(errors[0]?.message[0]).toEqual(expectedError);
    });
  });
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 75d4a34 and b2c7a44.

📒 Files selected for processing (25)
  • packages/ui/src/components/organisms/Form/Validator/types/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/create-validation-error/create-validation-error.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/create-validation-error/create-validation-error.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/create-validation-error/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-id/format-id.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-id/format-id.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-id/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-value-destination/format-value-destination.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-value-destination/format-value-destination.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/format-value-destination/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/types.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/format/format-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/format/format.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum-value-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum.validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern.validator.unit.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/ui/src/components/organisms/Form/Validator/utils/create-validation-error/index.ts
  • packages/ui/src/components/organisms/Form/Validator/utils/format-value-destination/index.ts
  • packages/ui/src/components/organisms/Form/Validator/utils/format-id/index.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum-value-validator.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length-validator.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length-validator.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/format/format.validator.unit.test.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/max-length/max-length.validator.unit.test.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum-validator.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length.validator.unit.test.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern-validator.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/maximum/maximum.validator.unit.test.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/minimum/minimum.validator.unit.test.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/format/format-validator.ts
  • packages/ui/src/components/organisms/Form/Validator/validators/pattern/pattern.validator.unit.test.ts
  • packages/ui/src/components/organisms/Form/Validator/types/index.ts
🔇 Additional comments (5)
packages/ui/src/components/organisms/Form/Validator/utils/format-value-destination/format-value-destination.unit.test.ts (1)

1-3: Well-structured test organization

The test suite is well organized with clear describe blocks and meaningful test descriptions. The setup follows testing best practices.

Also applies to: 4-8

packages/ui/src/components/organisms/Form/Validator/utils/validate/types.ts (1)

1-3: Interface definition looks good

The IValidateParams interface is well-defined and adds flexibility to the validation parameters.

packages/ui/src/components/organisms/Form/Validator/utils/create-validation-error/create-validation-error.ts (1)

1-27: Implementation of createValidationError is sound

The function correctly constructs validation error objects with the necessary details.

packages/ui/src/components/organisms/Form/Validator/utils/format-id/format-id.unit.test.ts (1)

1-31: Unit tests effectively cover key scenarios

The tests validate the behavior of formatId under various conditions, ensuring reliability.

packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.unit.test.ts (1)

1-3: LGTM: Imports are clean and well-organized.

The imports are minimal and properly structured.

Comment on lines +3 to +11
export const formatValueDestination = (valueDestination: string, stack: TDeepthLevelStack) => {
let _valueDestination = valueDestination;

stack.forEach((stack, index) => {
_valueDestination = _valueDestination.replace(`$${index}`, stack.toString());
});

return _valueDestination;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Multiple critical improvements needed in the implementation

The current implementation has several potential issues that need to be addressed:

  1. String.replace() only replaces the first occurrence of a pattern
  2. No input validation for null/undefined values
  3. Unsafe toString() conversion

Consider this improved implementation:

-export const formatValueDestination = (valueDestination: string, stack: TDeepthLevelStack) => {
+export const formatValueDestination = (valueDestination: string | undefined | null, stack: TDeepthLevelStack) => {
+  if (!valueDestination) return '';
+  if (!Array.isArray(stack)) return valueDestination;
+
   let _valueDestination = valueDestination;

   stack.forEach((stack, index) => {
-    _valueDestination = _valueDestination.replace(`$${index}`, stack.toString());
+    const placeholder = `$${index}`;
+    const value = typeof stack === 'object' ? JSON.stringify(stack) : String(stack);
+    while (_valueDestination.includes(placeholder)) {
+      _valueDestination = _valueDestination.replace(placeholder, value);
+    }
   });

   return _valueDestination;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const formatValueDestination = (valueDestination: string, stack: TDeepthLevelStack) => {
let _valueDestination = valueDestination;
stack.forEach((stack, index) => {
_valueDestination = _valueDestination.replace(`$${index}`, stack.toString());
});
return _valueDestination;
};
export const formatValueDestination = (valueDestination: string | undefined | null, stack: TDeepthLevelStack) => {
if (!valueDestination) return '';
if (!Array.isArray(stack)) return valueDestination;
let _valueDestination = valueDestination;
stack.forEach((stack, index) => {
const placeholder = `$${index}`;
const value = typeof stack === 'object' ? JSON.stringify(stack) : String(stack);
while (_valueDestination.includes(placeholder)) {
_valueDestination = _valueDestination.replace(placeholder, value);
}
});
return _valueDestination;
};

Comment on lines +4 to +45
describe('formatValueDestination', () => {
it('should be defined', () => {
expect(formatValueDestination).toBeDefined();
});

describe('formatting', () => {
it('should format simple value destination', () => {
const valueDestination = 'tasks[$0].name';
const stack = [1];

expect(formatValueDestination(valueDestination, stack)).toBe('tasks[1].name');
});

it('should format nested value destination', () => {
const valueDestination = 'tasks[$0].siblings[$1].name';
const stack = [1, 2];

expect(formatValueDestination(valueDestination, stack)).toBe('tasks[1].siblings[2].name');
});

it('should handle empty stack', () => {
const valueDestination = 'tasks.name';
const stack: number[] = [];

expect(formatValueDestination(valueDestination, stack)).toBe('tasks.name');
});

it('should handle value destination without placeholders', () => {
const valueDestination = 'tasks[0].siblings[1].name';
const stack = [1, 2];

expect(formatValueDestination(valueDestination, stack)).toBe('tasks[0].siblings[1].name');
});

it('should replace placeholder with index', () => {
const valueDestination = 'tasks[$0].siblings[$1].name';
const stack = [1];

expect(formatValueDestination(valueDestination, stack)).toBe('tasks[1].siblings[$1].name');
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add test cases for edge cases and error conditions

While the current test suite covers basic functionality, several important test cases are missing:

  1. Handling of null/undefined inputs
  2. Multiple occurrences of the same placeholder
  3. Complex stack values (objects, arrays)
  4. Large stack indices

Add these test cases:

it('should handle null/undefined inputs', () => {
  expect(formatValueDestination(null, [])).toBe('');
  expect(formatValueDestination(undefined, [])).toBe('');
});

it('should replace multiple occurrences of same placeholder', () => {
  const valueDestination = 'tasks[$0].name[$0]';
  const stack = [1];
  expect(formatValueDestination(valueDestination, stack)).toBe('tasks[1].name[1]');
});

it('should handle complex stack values', () => {
  const valueDestination = 'tasks[$0].config[$1]';
  const stack = [{ id: 1 }, [1, 2, 3]];
  expect(formatValueDestination(valueDestination, stack))
    .toBe('tasks[{"id":1}].config[[1,2,3]]');
});

it('should handle large indices', () => {
  const valueDestination = 'tasks[$0]';
  const stack = [Number.MAX_SAFE_INTEGER];
  expect(formatValueDestination(valueDestination, stack))
    .toBe(`tasks[${Number.MAX_SAFE_INTEGER}]`);
});

Comment on lines +44 to +47
if (abortEarly) {
throw validationErrors;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid throwing non-Error objects; use a custom Error class

Throwing non-Error objects like arrays can lead to unexpected behavior. Consider creating a custom ValidationErrors class to encapsulate the validation errors.

+class ValidationErrors extends Error {
+  constructor(public validationErrors: IValidationError[]) {
+    super('Validation errors occurred');
+  }
+}

...

           if (abortEarly) {
-            throw validationErrors;
+            throw new ValidationErrors(validationErrors);
           }

...

     } catch (exception) {
-      if (exception instanceof Error) {
+      if (exception instanceof ValidationErrors) {
+        return exception.validationErrors;
+      } else if (exception instanceof Error) {
         throw exception;
       }
-      return validationErrors;
     }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +504 to +554
describe('when validating array entries as root', () => {
it('will be valid', () => {
const value = [
{
name: 'John Doe',
},
];

const schema = [
{
id: 'list',
children: [
{
id: 'name',
valueDestination: '[$0].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
],
},
] as IValidationSchema[];

expect(validate(value, schema)).toEqual([]);
});

it('will be invalid', () => {
const value = [
{
name: null,
},
];

const schema = [
{
id: 'list',
children: [
{
id: 'name',
valueDestination: '[$0].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
],
},
] as IValidationSchema[];

const errors = validate(value, schema);

expect(errors.length).toBe(1);
expect(errors[0]?.message[0]).toEqual('Field is required.');
});
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add edge cases for array validation.

The array validation tests could be enhanced with additional edge cases.

Add the following test cases:

describe('when validating array entries as root', () => {
  it('should handle empty arrays', () => {
    const value = [];
    const schema = [/* ... */];
    expect(validate(value, schema)).toEqual([]);
  });

  it('should handle non-array values', () => {
    const value = 'not an array';
    const schema = [/* ... */];
    expect(() => validate(value, schema)).toThrow();
  });

  it('should handle arrays with undefined/null items', () => {
    const value = [undefined, null, { name: 'John' }];
    const schema = [/* ... */];
    const errors = validate(value, schema);
    expect(errors.length).toBeGreaterThan(0);
  });
});

Comment on lines +445 to +501
it('will be invalid', () => {
const testValue = {
name: 'John',
tasks: [
{
name: 'Jane',
},
{
name: 'Jim',
siblings: [
{
name: 'Jill',
},
],
},
],
};

const schema = [
{
id: 'name',
valueDestination: 'name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
{
id: 'tasks',
valueDestination: 'tasks',
validators: [
{
type: 'minLength',
message: 'Field is invalid.',
value: { minLength: 2 },
},
],
children: [
{
id: 'tasksName',
valueDestination: 'tasks[$0].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
{
id: 'siblings',
valueDestination: 'tasks[$0].siblings',
children: [
{
id: 'siblingsName',
valueDestination: 'tasks[$0].siblings[$1].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
],
},
],
},
] as IValidationSchema[];

expect(validate(testValue, schema)).toEqual([]);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect test case in "will be invalid" section.

The test case titled "will be invalid" is actually testing a valid case. The test data and expectations are identical to the "will be valid" test case above it.

Modify the test case to actually test an invalid scenario:

 it('will be invalid', () => {
   const testValue = {
     name: 'John',
     tasks: [
       {
-        name: 'Jane',
+        name: null,  // Make this invalid
       },
       {
         name: 'Jim',
         siblings: [
           {
-            name: 'Jill',
+            name: null,  // Make this invalid
           },
         ],
       },
     ],
   };

   const schema = [/* ... */];

-  expect(validate(testValue, schema)).toEqual([]);
+  const errors = validate(testValue, schema);
+  expect(errors.length).toBeGreaterThan(0);
+  expect(errors).toContainEqual(expect.objectContaining({
+    id: expect.stringContaining('name'),
+    message: ['Field is required.'],
+  }));
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('will be invalid', () => {
const testValue = {
name: 'John',
tasks: [
{
name: 'Jane',
},
{
name: 'Jim',
siblings: [
{
name: 'Jill',
},
],
},
],
};
const schema = [
{
id: 'name',
valueDestination: 'name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
{
id: 'tasks',
valueDestination: 'tasks',
validators: [
{
type: 'minLength',
message: 'Field is invalid.',
value: { minLength: 2 },
},
],
children: [
{
id: 'tasksName',
valueDestination: 'tasks[$0].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
{
id: 'siblings',
valueDestination: 'tasks[$0].siblings',
children: [
{
id: 'siblingsName',
valueDestination: 'tasks[$0].siblings[$1].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
],
},
],
},
] as IValidationSchema[];
expect(validate(testValue, schema)).toEqual([]);
});
it('will be invalid', () => {
const testValue = {
name: 'John',
tasks: [
{
name: null,
},
{
name: 'Jim',
siblings: [
{
name: null,
},
],
},
],
};
const schema = [
{
id: 'name',
valueDestination: 'name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
{
id: 'tasks',
valueDestination: 'tasks',
validators: [
{
type: 'minLength',
message: 'Field is invalid.',
value: { minLength: 2 },
},
],
children: [
{
id: 'tasksName',
valueDestination: 'tasks[$0].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
{
id: 'siblings',
valueDestination: 'tasks[$0].siblings',
children: [
{
id: 'siblingsName',
valueDestination: 'tasks[$0].siblings[$1].name',
validators: [{ type: 'required', message: 'Field is required.', value: {} }],
},
],
},
],
},
] as IValidationSchema[];
const errors = validate(testValue, schema);
expect(errors.length).toBeGreaterThan(0);
expect(errors).toContainEqual(expect.objectContaining({
id: expect.stringContaining('name'),
message: ['Field is required.'],
}));
});

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (6)
packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/remove-validator.ts (1)

1-5: Consider using immutable state management

Direct mutation of shared state can lead to race conditions and hard-to-debug issues, especially in a UI component library.

Consider these architectural improvements:

  1. Use an immutable pattern with a new validators registry
  2. Implement an event-driven system for validator changes
  3. Add a validation context to manage validator lifecycle

Example approach:

import { ValidatorType, ValidatorFn } from '../../types';

class ValidatorRegistry {
  private validators = new Map<ValidatorType, ValidatorFn>();

  removeValidator(type: ValidatorType): boolean {
    return this.validators.delete(type);
  }

  getValidator(type: ValidatorType): ValidatorFn | undefined {
    return this.validators.get(type);
  }

  setValidator(type: ValidatorType, fn: ValidatorFn): void {
    this.validators.set(type, fn);
  }
}

export const validatorRegistry = new ValidatorRegistry();
export const removeValidator = (type: ValidatorType) => 
  validatorRegistry.removeValidator(type);
packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/remove-validator.unit.test.ts (2)

1-7: Consider enhancing type safety in mock setup.

While the mock setup is functional, it could benefit from explicit typing to ensure type safety and improve maintainability.

Consider this improvement:

 import { beforeEach, describe, expect, it, vi } from 'vitest';
 import { removeValidator } from './remove-validator';
+import type { ValidatorFn } from '../../types';
 
 vi.mock('../../validators', () => ({
-  validatorsExtends: {},
+  validatorsExtends: {} as Record<string, ValidatorFn>,
 }));

18-51: Enhance test coverage with additional scenarios.

While the existing tests cover basic functionality, consider adding these scenarios for more comprehensive coverage:

  1. Multiple validator removal
  2. Error handling for invalid inputs
  3. Edge cases (e.g., empty string, special characters)

Also, consider making test descriptions more behavior-focused.

Here's an example of additional test cases to consider:

it('should handle removal of multiple validators simultaneously', () => {
  const mockValidator1 = vi.fn();
  const mockValidator2 = vi.fn();
  validatorsExtends['test1'] = mockValidator1;
  validatorsExtends['test2'] = mockValidator2;
  
  removeValidator('test1');
  removeValidator('test2');
  
  expect(Object.keys(validatorsExtends)).toHaveLength(0);
});

it('should handle special characters in validator keys', () => {
  const mockValidator = vi.fn();
  const specialKey = 'test@#$%';
  validatorsExtends[specialKey] = mockValidator;
  
  removeValidator(specialKey);
  
  expect(validatorsExtends[specialKey]).toBeUndefined();
});
packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts (1)

68-76: Simplify error handling structure

The nested try-catch blocks and error handling could be simplified.

   try {
     run(schema);
   } catch (exception) {
-    if (exception instanceof Error) {
+    if (Array.isArray(exception)) {
+      return exception as IValidationError[];
+    } else if (exception instanceof Error) {
       throw exception;
     }
-
-    return validationErrors;
+    return [];
   }
packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.unit.test.ts (2)

6-11: Add test suite documentation.

Consider adding a JSDoc comment block at the beginning of the test suite to document the purpose and scope of these tests.

+/**
+ * Test suite for the validate utility function.
+ * Covers validation scenarios including:
+ * - Basic validation with abort early options
+ * - Plain object validation
+ * - Nested object validation
+ * - Array validation
+ * - Conditional validation
+ * - Custom validators
+ */
 describe('validate', () => {
   it('should be defined', () => {
     expect(validate).toBeDefined();
   });

819-825: Enhance type safety in custom validator implementation.

The custom validator implementation could be improved with better type safety and error handling.

Consider this enhanced implementation:

-const evenNumberValidator = (value: number, _: ICommonValidator) => {
+const evenNumberValidator = (value: unknown, validator: ICommonValidator): void => {
   if (typeof value !== 'number') return true;

   if (value % 2 !== 0) {
-    throw new Error('Number is not even');
+    throw new Error(validator.message || 'Number is not even');
   }
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e471f81 and 7cb51fe.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • packages/ui/package.json (2 hunks)
  • packages/ui/src/components/organisms/Form/Validator/types/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/get-validator/get-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/remove-validator.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/remove-validator.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/helpers.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.unit.test.ts (1 hunks)
  • services/workflows-service/prisma/data-migrations (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/index.ts
  • services/workflows-service/prisma/data-migrations
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/package.json
  • packages/ui/src/components/organisms/Form/Validator/utils/get-validator/get-validator.ts
🔇 Additional comments (5)
packages/ui/src/components/organisms/Form/Validator/utils/remove-validator/remove-validator.ts (1)

1-1: Verify validator usage before removal

We should verify if the validator being removed is currently in use by any forms to prevent runtime errors.

✅ Verification successful

Let me try a different search strategy to verify the validator usage.


Based on the search results, I can now generate the final response:

Safe to remove validator

The validatorsExtends is part of a well-structured validator management system where validators can be dynamically registered and removed. The search results show that:

  • validatorsExtends is only used internally within the validator utility functions
  • The removal operation is properly tested in remove-validator.unit.test.ts
  • There are no direct references to specific validator types from validatorsExtends in the actual form implementations
  • The system uses a fallback mechanism in get-validator.ts that checks both baseValidatorsMap and validatorsExtends
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for validator usage in the codebase
echo "Searching for validator usage patterns..."

# Look for direct validator references
rg -t typescript -t javascript "validatorsExtends\[.*\]" 

# Look for validation type references in form configurations
ast-grep --pattern 'validator: { type: $_ }'

Length of output: 233


Script:

#!/bin/bash
# Search for validator usage patterns with ripgrep
echo "Searching for validator usage..."

# Look for direct validator references
rg "validatorsExtends"

# Look for validator type definitions and usage
rg "validator.*type"

# Look for form validation configurations
rg "validation.*type"

Length of output: 9868

packages/ui/src/components/organisms/Form/Validator/types/index.ts (1)

33-38: Message type inconsistency in IValidationError

There are conflicting implementations of IValidationError with different message types across the codebase.

packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts (1)

60-64: ⚠️ Potential issue

Add protection against circular references

The recursive validation of nested schemas could lead to infinite loops with circular references.

packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.unit.test.ts (2)

505-555: Add edge cases for array validation.

The array validation tests could be enhanced with additional edge cases.

Add the following test cases:

 describe('when validating array entries as root', () => {
+  it('should handle empty arrays', () => {
+    const value = [];
+    const schema = [/* ... */];
+    expect(validate(value, schema)).toEqual([]);
+  });
+
+  it('should handle non-array values', () => {
+    const value = 'not an array';
+    const schema = [/* ... */];
+    expect(() => validate(value, schema)).toThrow();
+  });
+
+  it('should handle arrays with undefined/null items', () => {
+    const value = [undefined, null, { name: 'John' }];
+    const schema = [/* ... */];
+    const errors = validate(value, schema);
+    expect(errors.length).toBeGreaterThan(0);
+  });

446-502: ⚠️ Potential issue

Fix incorrect test case in "will be invalid" section.

The test case titled "will be invalid" is actually testing a valid case. The test data and expectations are identical to the "will be valid" test case above it.

Modify the test case to actually test an invalid scenario:

 it('will be invalid', () => {
   const testValue = {
     name: 'John',
     tasks: [
       {
-        name: 'Jane',
+        name: null,  // Make this invalid
       },
       {
         name: 'Jim',
         siblings: [
           {
-            name: 'Jill',
+            name: null,  // Make this invalid
           },
         ],
       },
     ],
   };

   const schema = [/* ... */];

-  expect(validate(testValue, schema)).toEqual([]);
+  const errors = validate(testValue, schema);
+  expect(errors.length).toBeGreaterThan(0);
+  expect(errors).toContainEqual(expect.objectContaining({
+    id: expect.stringContaining('name'),
+    message: ['Field is required.'],
+  }));
 });

Comment on lines +3 to +5
export const removeValidator = (type: string) => {
delete validatorsExtends[type];
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and type safety

The current implementation has several critical issues:

  1. Silent failures if the validator doesn't exist
  2. No type checking for valid validator types
  3. No return value to indicate success/failure

Consider implementing this safer version:

-export const removeValidator = (type: string) => {
+import { ValidatorType } from '../../types';
+
+export const removeValidator = (type: ValidatorType): boolean => {
+  if (!(type in validatorsExtends)) {
+    return false;
+  }
   delete validatorsExtends[type];
+  return true;
 };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +8 to +16
describe('removeValidator', async () => {
const validatorsExtends = vi.mocked(await import('../../validators')).validatorsExtends;

beforeEach(() => {
// Clear validators before each test
Object.keys(validatorsExtends).forEach(key => {
delete validatorsExtends[key];
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve test suite setup and isolation.

The current setup has several areas for improvement:

  1. The async in the describe block is unnecessary as it's not being used properly.
  2. The cleanup logic could be simplified.
  3. The mocked import could be better isolated.

Consider this refactoring:

-describe('removeValidator', async () => {
-  const validatorsExtends = vi.mocked(await import('../../validators')).validatorsExtends;
+describe('removeValidator', () => {
+  let validatorsExtends: Record<string, ValidatorFn>;
 
   beforeEach(() => {
+    validatorsExtends = vi.mocked(import('../../validators')).validatorsExtends;
     // Clear validators before each test
-    Object.keys(validatorsExtends).forEach(key => {
-      delete validatorsExtends[key];
-    });
+    Object.keys(validatorsExtends).forEach(key => delete validatorsExtends[key]);
   });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +4 to +6
export const isShouldApplyValidation = (rule: IValidationRule, context: object) => {
return Boolean(jsonLogic.apply(rule.value, context));
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve function naming and error handling

  1. The function name isShouldApplyValidation is grammatically incorrect. Consider renaming to shouldApplyValidation.
  2. Add error handling for invalid rule.value to prevent runtime errors.
-export const isShouldApplyValidation = (rule: IValidationRule, context: object) => {
-  return Boolean(jsonLogic.apply(rule.value, context));
+export const shouldApplyValidation = (rule: IValidationRule, context: object) => {
+  try {
+    return jsonLogic.apply(rule.value, context);
+  } catch (error) {
+    console.error('Invalid validation rule:', error);
+    return false;
+  }
+};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const isShouldApplyValidation = (rule: IValidationRule, context: object) => {
return Boolean(jsonLogic.apply(rule.value, context));
};
export const shouldApplyValidation = (rule: IValidationRule, context: object) => {
try {
return jsonLogic.apply(rule.value, context);
} catch (error) {
console.error('Invalid validation rule:', error);
return false;
}
};

Comment on lines +16 to +21
export interface ICommonValidator<T = object, TValidatorType extends string = TBaseValidators> {
type: TValidatorType;
value: T;
message?: string;
applyWhen?: IValidationRule;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and documentation for common validator

The default type parameter T = object is too loose and could lead to type-unsafe implementations.

-export interface ICommonValidator<T = object, TValidatorType extends string = TBaseValidators> {
+/** Common structure for all validators */
+export interface ICommonValidator<
+  T = unknown,
+  TValidatorType extends string = TBaseValidators
+> {
   type: TValidatorType;
   value: T;
   message?: string;
   applyWhen?: IValidationRule;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ICommonValidator<T = object, TValidatorType extends string = TBaseValidators> {
type: TValidatorType;
value: T;
message?: string;
applyWhen?: IValidationRule;
}
/** Common structure for all validators */
export interface ICommonValidator<
T = unknown,
TValidatorType extends string = TBaseValidators
> {
type: TValidatorType;
value: T;
message?: string;
applyWhen?: IValidationRule;
}

Comment on lines +1 to +6
export type TBaseValidationRules = 'json-logic';

export interface IValidationRule {
type: TBaseValidationRules;
value: object;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and documentation for validation rules

  1. The TBaseValidationRules type is too restrictive. Consider allowing for future rule types.
  2. The value type in IValidationRule should be more specific than object.
-export type TBaseValidationRules = 'json-logic';
+/** Supported validation rule types */
+export type TBaseValidationRules = 
+  | 'json-logic'
+  | 'expression' // Future rule types
+  | 'custom';    // Future rule types

+/** Structure of a validation rule */
 export interface IValidationRule {
   type: TBaseValidationRules;
-  value: object;
+  value: Record<string, unknown>;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type TBaseValidationRules = 'json-logic';
export interface IValidationRule {
type: TBaseValidationRules;
value: object;
}
/** Supported validation rule types */
export type TBaseValidationRules =
| 'json-logic'
| 'expression' // Future rule types
| 'custom'; // Future rule types
/** Structure of a validation rule */
export interface IValidationRule {
type: TBaseValidationRules;
value: Record<string, unknown>;
}

Comment on lines +42 to +49
try {
validate(value, validator as unknown as ICommonValidator);
} catch (exception) {
const error = createValidationError({
id,
invalidValue: value,
message: (exception as Error).message,
stack,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve type safety and error handling in validation

  1. Unsafe type casting with unknown should be avoided
  2. Error message handling could be improved
-          validate(value, validator as unknown as ICommonValidator);
+          validate(value, validator);
         } catch (exception) {
           const error = createValidationError({
             id,
             invalidValue: value,
-            message: (exception as Error).message,
+            message: exception instanceof Error 
+              ? exception.message 
+              : 'Unknown validation error',
             stack,
           });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
validate(value, validator as unknown as ICommonValidator);
} catch (exception) {
const error = createValidationError({
id,
invalidValue: value,
message: (exception as Error).message,
stack,
try {
validate(value, validator);
} catch (exception) {
const error = createValidationError({
id,
invalidValue: value,
message: exception instanceof Error
? exception.message
: 'Unknown validation error',
stack,

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (23)
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useManualValidate.unit.test.ts (4)

6-15: Consider improving the mock setup for better maintainability

The mock setup could be enhanced by:

  1. Extracting the mock error object to a reusable constant
  2. Using proper TypeScript types instead of inline objects
+const mockValidationError = {
+  id: 'name' as const,
+  originId: 'name' as const,
+  message: ['error'],
+  invalidValue: 'John',
+} as const;
+
 vi.mock('../../../utils/validate', () => ({
-  validate: vi.fn().mockReturnValue([
-    {
-      id: 'name',
-      originId: 'name',
-      message: ['error'],
-      invalidValue: 'John',
-    },
-  ]),
+  validate: vi.fn().mockReturnValue([mockValidationError]),
 }));

17-23: Enhance test setup with proper types and comprehensive mock data

The test setup could be improved by:

  1. Adding proper type definitions for the schema
  2. Including more comprehensive test data to cover various validation scenarios
+interface ValidationSchema {
+  id: string;
+  validators: Array<unknown>;
+  rules: Array<unknown>;
+}
+
+const mockSchema: ValidationSchema[] = [
+  {
+    id: 'name',
+    validators: [],
+    rules: [],
+  },
+];
+
 describe('useManualValidate', () => {
   const mockContext = { name: 'John' };
-  const mockSchema = [{ id: 'name', validators: [], rules: [] }];
+  const mockSchema = mockSchema;

25-29: Consider expanding initialization tests

The initialization test could be enhanced by verifying additional initial states such as:

  1. Initial loading state
  2. Initial validation state
  3. Edge cases with empty/null schema or context

1-93: Consider adding tests for important scenarios

While the current test coverage is good, consider adding tests for:

  1. Error handling scenarios (invalid schema, validation errors)
  2. Async validation scenarios
  3. Multiple field validation
  4. Complex validation rules
  5. Performance testing for large forms
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useSyncValidate.unit.test.ts (2)

7-16: Consider making the mock more flexible for different test scenarios.

The current mock setup returns a static error object. Consider:

  1. Moving the mock error object to a separate test fixtures file for reusability.
  2. Making the mock implementation more flexible to return different errors based on test scenarios.

Here's a suggested improvement:

// __fixtures__/validationErrors.ts
export const mockValidationError = {
  id: 'name',
  originId: 'name',
  message: ['error'],
  invalidValue: 'John',
};

// Current file
vi.mock('../../../utils/validate', () => ({
  validate: vi.fn().mockImplementation((context, schema, options) => {
    // Allow tests to modify the response based on inputs
    return [mockValidationError];
  }),
}));

19-24: Enhance test data setup with explicit types and meaningful validators.

The mock context and schema could be improved for better test coverage:

  1. Add explicit TypeScript interfaces for the context and schema.
  2. Include actual validators and rules in the schema for more realistic testing.

Here's a suggested improvement:

interface ValidationContext {
  name: string;
  [key: string]: unknown;
}

interface ValidationSchema {
  id: string;
  validators: Array<unknown>; // Replace with actual validator type
  rules: Array<unknown>; // Replace with actual rule type
}

const mockContext: ValidationContext = { name: 'John' };
const mockSchema: ValidationSchema[] = [{
  id: 'name',
  validators: [{ type: 'required', message: 'Name is required' }],
  rules: [{ type: 'minLength', value: 2 }],
}];
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useValidate.ts (2)

22-22: Consider reducing the default validation delay

The default validation delay of 500ms might feel sluggish for users. Consider reducing it to 300ms for a better balance between performance and user experience.

- validationDelay = 500,
+ validationDelay = 300,

41-53: Optimize validation errors memoization

The current memoization logic could be simplified and optimized by combining the conditions.

  const validationErrors = useMemo(() => {
-   if (!validateOnChange) return manualValidationErrors;
-
-   if (validateSync) return syncValidationErrors;
-
-   return asyncValidationErrors;
+   return !validateOnChange
+     ? manualValidationErrors
+     : validateSync
+     ? syncValidationErrors
+     : asyncValidationErrors;
  }, [
    manualValidationErrors,
    syncValidationErrors,
    asyncValidationErrors,
    validateOnChange,
    validateSync,
  ]);
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useValidate.unit.test.ts (2)

392-430: Add edge case tests for validation delay

The async validation tests should include edge cases for the validation delay, such as:

  • Multiple rapid changes within the delay period
  • Zero delay
  • Very long delay

Example test case:

it('should debounce validation for rapid changes', async () => {
  vi.useFakeTimers();
  let context: { name?: string } = {};
  const schema: IValidationSchema[] = [/* schema */];
  
  const { result, rerender } = renderHook(() => 
    useValidate(context, schema, { validationDelay: 300 })
  );
  
  // Simulate rapid changes
  context = { name: 'J' };
  rerender();
  await vi.advanceTimersByTimeAsync(100);
  
  context = { name: 'Jo' };
  rerender();
  await vi.advanceTimersByTimeAsync(100);
  
  context = { name: 'John' };
  rerender();
  await vi.advanceTimersByTimeAsync(300);
  
  expect(result.current.errors).toEqual([]);
});

180-211: Improve test descriptions for validation error resolution

The test descriptions could be more descriptive to better communicate the test scenarios.

- it('will return manual validation errors if validateOnChange is false', () => {
+ it('should use manual validation when automatic validation is disabled (validateOnChange: false)', () => {

- it('will return sync validation errors if validateOnChange is true and validateSync is true', () => {
+ it('should use synchronous validation when both automatic and sync validation are enabled', () => {

- it('will return async validation errors if validateOnChange is true and validateSync is false', () => {
+ it('should default to asynchronous validation when sync validation is disabled', () => {

Also applies to: 213-244, 246-277

packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useAsyncValidate.ts (2)

8-13: Add JSDoc documentation for the interface and its parameters.

While the parameter names are clear, adding documentation would help users understand their impact on validation behavior.

Consider adding documentation like this:

+/**
+ * Configuration parameters for async validation behavior
+ */
 export interface IUseAsyncValidateParams {
+  /** Delay in milliseconds before validation is triggered (default: 500) */
   validationDelay?: number;
+  /** Whether to validate immediately on mount (default: false) */
   validateAsync?: boolean;
+  /** Whether to validate on context changes (default: true) */
   validateOnChange?: boolean;
+  /** Whether to stop validation on first error (default: false) */
   abortEarly?: boolean;
 }

15-46: Consider performance implications and testing strategies.

While the implementation is solid, here are some architectural considerations:

  1. Large forms with frequent changes might trigger too many validations.
  2. Complex validation schemas might cause performance issues.
  3. Network-based validations would benefit from cancellation tokens.

Consider:

  • Adding a maxValidationsPerSecond parameter
  • Implementing validation queue management
  • Adding support for AbortController in the validate utility
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useAsyncValidate.unit.test.ts (4)

18-20: Consider enhancing the debounce mock implementation.

The current debounce mock is oversimplified and might not catch timing-related issues. Consider implementing a more realistic mock that simulates actual debouncing behavior for edge cases.

-vi.mock('lodash/debounce', () => ({
-  default: (fn: any) => fn,
-}));
+vi.mock('lodash/debounce', () => ({
+  default: (fn: any, wait: number) => {
+    let timeout: NodeJS.Timeout;
+    return (...args: any[]) => {
+      clearTimeout(timeout);
+      timeout = setTimeout(() => fn(...args), wait);
+    };
+  },
+}));

23-24: Enhance test coverage with more comprehensive schema mocks.

The current mock schema is minimal. Consider adding test cases with different validator types and rule combinations to ensure the hook handles various schema configurations correctly.

const mockSchemas = {
  simple: [{ id: 'name', validators: [], rules: [] }],
  withValidators: [{
    id: 'name',
    validators: [
      { type: 'required', message: 'Required' },
      { type: 'minLength', value: 3, message: 'Too short' }
    ],
    rules: []
  }],
  withRules: [{
    id: 'name',
    validators: [],
    rules: [{ type: 'dependency', field: 'email', value: true }]
  }]
};

35-43: Add test cases for all flag combinations.

Current tests check individual flags, but it would be valuable to test all possible combinations of validateAsync and validateOnChange flags.

// Add these test cases:
it('should not validate when both flags are false', () => {
  renderHook(() => useAsyncValidate(mockContext, mockSchema, {
    validateAsync: false,
    validateOnChange: false
  }));
  expect(validate).not.toHaveBeenCalled();
});

it('should not validate when validateAsync is true but validateOnChange is false', () => {
  renderHook(() => useAsyncValidate(mockContext, mockSchema, {
    validateAsync: true,
    validateOnChange: false
  }));
  expect(validate).not.toHaveBeenCalled();
});

7-16: Add type safety to mock error structure.

The mock error structure should use proper types to ensure type safety and maintain consistency with the actual validation errors.

+type ValidationError = {
+  id: string;
+  originId: string;
+  message: string[];
+  invalidValue: unknown;
+};

 vi.mock('../../../utils/validate', () => ({
   validate: vi.fn().mockReturnValue([
-    {
+    {
       id: 'name',
       originId: 'name',
       message: ['error'],
       invalidValue: 'John',
-    },
+    } as ValidationError,
   ]),
 }));
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useSyncValidate.ts (4)

5-9: Add JSDoc documentation to the interface.

The interface would benefit from documentation explaining the purpose and behavior of each configuration flag.

+/**
+ * Configuration parameters for synchronous validation
+ */
 export interface IUseSyncValidateParams {
+  /** When true, stops validation on the first error encountered */
   abortEarly?: boolean;
+  /** Controls whether synchronous validation is enabled */
   validateSync?: boolean;
+  /** Controls whether validation runs on value changes */
   validateOnChange?: boolean;
 }

2-3: Consider restructuring imports to reduce path depth.

The deep relative imports (../../../) suggest that the validation-related files could be reorganized to improve maintainability.

Consider:

  1. Moving common types and utilities to a more accessible location
  2. Using barrel exports (index.ts) to simplify imports
  3. Creating a dedicated validation module structure

11-15: Add generic type parameter for better type safety.

The context parameter could benefit from stronger typing to ensure type safety at the call site.

-export const useSyncValidate = (
+export const useSyncValidate = <T extends object>(
-  context: object,
+  context: T,
   schema: IValidationSchema[],
   params: IUseSyncValidateParams = {},
 ) => {

18-20: Improve early return logic clarity.

The early return condition could be more explicit about its purpose and could benefit from being moved before the useMemo.

+  // Skip validation if either sync validation or onChange validation is disabled
+  if (!validateSync || !validateOnChange) return [];
+
   return useMemo(() => {
-    if (!validateSync || !validateOnChange) return [];
-
     return validate(context, schema, { abortEarly });
   }, [context, schema, abortEarly, validateSync, validateOnChange]);
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useManualValidate.ts (3)

5-7: Add JSDoc documentation for the interface.

Adding documentation would improve code maintainability and help other developers understand the purpose of the abortEarly flag.

+/**
+ * Parameters for manual validation configuration
+ * @interface IUseManualValidateParams
+ * @property {boolean} [abortEarly] - When true, validation will stop after the first error
+ */
 export interface IUseManualValidateParams {
   abortEarly?: boolean;
 }

1-3: Consider restructuring imports to reduce nesting depth.

The deep relative import paths (../../../types and ../../../utils/validate) suggest that commonly used types and utilities could be centralized or made more accessible.

Consider creating a barrel file (index.ts) at a higher level to expose these shared resources, which would simplify imports to something like:

import { IValidationError, IValidationSchema, validate } from '@/validation';

9-13: Strengthen type safety for the context parameter.

The context parameter is typed as a generic object, which provides limited type safety. Consider making it generic or more specific to your use case.

-export const useManualValidate = (
+export const useManualValidate = <T extends object>(
-  context: object,
+  context: T,
   schema: IValidationSchema[],
   params: IUseManualValidateParams = {},
-): [IValidationError[], () => void] => {
+): [IValidationError[], () => void] => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb51fe and 4d2c708.

📒 Files selected for processing (10)
  • packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useAsyncValidate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useAsyncValidate.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useManualValidate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useManualValidate.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useSyncValidate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useSyncValidate.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useValidate.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useValidate.unit.test.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/index.ts
🔇 Additional comments (6)
packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useSyncValidate.unit.test.ts (1)

1-108: Overall test implementation looks good!

The test file is well-structured, covers key functionality, and follows testing best practices. While there are opportunities for enhancement, the current implementation provides a solid foundation for testing the useSyncValidate hook.

packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx (2)

23-23: ⚠️ Potential issue

Implement proper validation context value

The context provider is initialized with an empty object, which doesn't provide the required validation functionality to child components.

Consider implementing the context value with proper validation state and methods:

- return <ValidatorContext.Provider value={{}}>{children}</ValidatorContext.Provider>;
+ return (
+   <ValidatorContext.Provider
+     value={{
+       errors: [],
+       values: value,
+       validate: async () => {
+         // Implement validation logic using schema
+       },
+     }}
+   >
+     {children}
+   </ValidatorContext.Provider>
+ );

11-12: ⚠️ Potential issue

Implement validation option props

The validateOnChange and validateSync props are defined but not utilized in the component implementation.

These props should be used to control validation behavior:

  1. validateOnChange should trigger validation when the value changes
  2. validateSync should determine if validation is synchronous or asynchronous

Also applies to: 20-20

packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useAsyncValidate.ts (1)

1-7: LGTM! Well-organized imports with appropriate dependencies.

The choice of lodash's debounce and the organization of local validation utilities shows good architectural decisions.

packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useSyncValidate.ts (1)

1-23: Verify validate utility implementation and usage patterns.

Let's ensure the validate utility and hook usage patterns are consistent across the codebase.

✅ Verification successful

Let me gather more information about the validate utility implementation since it wasn't found in the first search.


Let me check the test coverage and implementation details of the validate utility to ensure it's properly integrated.


Based on the gathered information, I can now generate the final response:

Hook implementation and integration verified successfully

The useSyncValidate hook is properly integrated with the validation framework:

  • Correctly imports and uses the validate utility from the utils directory
  • Has comprehensive test coverage for both sync and async validation scenarios
  • Properly handles validation parameters (abortEarly, validateSync, validateOnChange)
  • Interfaces and types are well-defined in the types directory
  • The validate utility is properly implemented with proper error handling and abort-early functionality
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check validate utility implementation
ast-grep --pattern 'export function validate($_, $_, $_) {
  $$$
}'

# Check hook usage patterns
rg -A 5 'useSyncValidate'

Length of output: 13108


Script:

#!/bin/bash
# Search for validate utility with a more specific pattern
ast-grep --pattern 'export const validate = ($_, $_, $_) => {
  $$$
}'

# Also try finding the validate function in the utils directory
rg -A 10 'export (const|function) validate' packages/ui/src/components/organisms/Form/Validator/utils/

Length of output: 1390


Script:

#!/bin/bash
# Check validate utility tests
rg -A 10 "describe\('validate" packages/ui/src/components/organisms/Form/Validator/utils/validate/

# Check for any validation error interfaces and types
rg -A 5 "interface IValidation" packages/ui/src/components/organisms/Form/Validator/types/

Length of output: 3099

packages/ui/src/components/organisms/Form/Validator/hooks/internal/useValidate/useManualValidate.ts (1)

1-24: Verify integration with ValidatorProvider context.

The hook seems to operate independently of the ValidatorProvider mentioned in the summary. Let's verify if this is intentional or if it should integrate with the provider's context.

✅ Verification successful

Let me gather more information about the ValidatorContext and its usage.


Let me gather more information about how the useValidate hook integrates with ValidatorContext.


Based on the gathered information, I can now provide the final response:

The useManualValidate hook's independence from ValidatorProvider is intentional and correct.

The hook is part of the internal validation system that handles direct validation logic, while the ValidatorProvider serves as a higher-level component for form-wide validation context. The current implementation follows a proper separation of concerns:

  • useManualValidate is an internal hook that handles the core validation logic
  • useValidate (parent hook) composes various validation strategies including manual validation
  • ValidatorProvider and useValidator handle form-wide validation context
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for ValidatorProvider implementation and its context usage
ast-grep --pattern 'export const ValidatorProvider = ({ children }) => {
  $$$
}'

# Search for other hooks using ValidatorContext
rg -l "useContext.*ValidatorContext"

Length of output: 218


Script:

#!/bin/bash
# Check the ValidatorContext implementation
ast-grep --pattern 'const ValidatorContext = createContext'

# Check the useValidator implementation to understand context usage
ast-grep --pattern 'export const useValidator = $$$'

# Search for any other manual validation hooks usage
rg "useManualValidate"

Length of output: 36202


Script:

#!/bin/bash
# Check the implementation of useValidate
ast-grep --pattern 'export const useValidate = $$$'

# Check the ValidatorProvider implementation
rg -A 10 "export const ValidatorProvider"

# Check if there are any other validator hooks that use context
rg -A 5 "useContext.*Validator"

Length of output: 24255

Comment on lines +85 to +90
rerender({
context: { ...mockContext, newField: 'value' } as any,
schema: mockSchema,
params: { abortEarly: false },
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove type assertion and use proper typing

The use of as any is a code smell and should be avoided. Instead, properly type the test data:

-      context: { ...mockContext, newField: 'value' } as any,
+      context: { ...mockContext, newField: 'value' },

Consider creating an interface for the context type to make the test more type-safe:

interface TestContext {
  name: string;
  newField?: string;
}

const mockContext: TestContext = { name: 'John' };

Comment on lines +26 to +108
it('should initialize with empty validation errors when validateSync is false', () => {
const { result } = renderHook(() =>
useSyncValidate(mockContext, mockSchema, { validateSync: false }),
);
expect(result.current).toEqual([]);
expect(validate).not.toHaveBeenCalled();
});

it('should initialize with empty validation errors when validateOnChange is false', () => {
const { result } = renderHook(() =>
useSyncValidate(mockContext, mockSchema, { validateOnChange: false }),
);
expect(result.current).toEqual([]);
expect(validate).not.toHaveBeenCalled();
});

it('should validate and return errors when validateSync and validateOnChange are true', () => {
const { result } = renderHook(() =>
useSyncValidate(mockContext, mockSchema, {
validateSync: true,
validateOnChange: true,
}),
);

expect(validate).toHaveBeenCalledWith(mockContext, mockSchema, { abortEarly: false });
expect(result.current).toEqual([
{
id: 'name',
originId: 'name',
message: ['error'],
invalidValue: 'John',
},
]);
});

it('should pass abortEarly param to validate function', () => {
renderHook(() =>
useSyncValidate(mockContext, mockSchema, {
validateSync: true,
validateOnChange: true,
abortEarly: true,
}),
);

expect(validate).toHaveBeenCalledWith(mockContext, mockSchema, { abortEarly: true });
});

it('should revalidate when context changes', () => {
const { rerender } = renderHook(
({ context }) =>
useSyncValidate(context, mockSchema, {
validateSync: true,
validateOnChange: true,
}),
{
initialProps: { context: mockContext },
},
);

const newContext = { name: 'Jane' };
rerender({ context: newContext });

expect(validate).toHaveBeenCalledWith(newContext, mockSchema, { abortEarly: false });
});

it('should revalidate when schema changes', () => {
const { rerender } = renderHook(
({ schema }) =>
useSyncValidate(mockContext, schema, {
validateSync: true,
validateOnChange: true,
}),
{
initialProps: { schema: mockSchema },
},
);

const newSchema = [{ id: 'email', validators: [], rules: [] }];
rerender({ schema: newSchema });

expect(validate).toHaveBeenCalledWith(mockContext, newSchema, { abortEarly: false });
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance test coverage with additional test cases.

While the current tests cover basic functionality, consider adding these test cases:

  1. Verify that validation errors are cleared when validation passes
  2. Test validation error message formatting
  3. Add negative test cases for invalid inputs
  4. Test validation performance with large datasets

Here's a suggested test case for clearing errors:

it('should clear errors when validation passes', () => {
  // First render with failing validation
  const { rerender } = renderHook(
    ({ context }) =>
      useSyncValidate(context, mockSchema, {
        validateSync: true,
        validateOnChange: true,
      }),
    {
      initialProps: { context: { name: '' } }, // Invalid input
    },
  );

  // Mock validate to return no errors
  (validate as jest.Mock).mockReturnValueOnce([]);

  // Rerender with valid input
  rerender({ context: { name: 'Valid Name' } });

  expect(result.current).toEqual([]);
});

}

export const useValidate = (
context: object,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety for the context parameter

The context parameter is typed as object, which is too generic and could lead to runtime errors.

- context: object,
+ context: Record<string, unknown>,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
context: object,
context: Record<string, unknown>,

Comment on lines +39 to +43
useEffect(() => {
if (!validateAsync || !validateOnChange) return;

validateWithDebounce(context, schema, { abortEarly });
}, [context, schema, validateAsync, validateOnChange, abortEarly, validateWithDebounce]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent memory leaks by cleaning up pending validations.

The current implementation doesn't cancel pending validations on unmount or when dependencies change.

Apply this fix:

 useEffect(() => {
   if (!validateAsync || !validateOnChange) return;

   validateWithDebounce(context, schema, { abortEarly });
+
+  return () => {
+    validateWithDebounce.cancel();
+  };
 }, [context, schema, validateAsync, validateOnChange, abortEarly, validateWithDebounce]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!validateAsync || !validateOnChange) return;
validateWithDebounce(context, schema, { abortEarly });
}, [context, schema, validateAsync, validateOnChange, abortEarly, validateWithDebounce]);
useEffect(() => {
if (!validateAsync || !validateOnChange) return;
validateWithDebounce(context, schema, { abortEarly });
return () => {
validateWithDebounce.cancel();
};
}, [context, schema, validateAsync, validateOnChange, abortEarly, validateWithDebounce]);

Comment on lines +27 to +29
const [validationErrors, setValidationErrors] = useState<IValidationError[]>(() =>
validateAsync ? validate(context, schema, { abortEarly }) : [],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding error handling for initial validation.

The initial validation in useState could throw an error and crash the component.

Apply this fix:

 const [validationErrors, setValidationErrors] = useState<IValidationError[]>(() =>
-  validateAsync ? validate(context, schema, { abortEarly }) : [],
+  validateAsync ? 
+    try {
+      return validate(context, schema, { abortEarly });
+    } catch (error) {
+      console.error('Initial validation failed:', error);
+      return [];
+    }
+    : [],
 );

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +31 to +37
const validateWithDebounce = useCallback(
debounce((context: object, schema: IValidationSchema[], params: IUseAsyncValidateParams) => {
const errors = validate(context, schema, params);
setValidationErrors(errors);
}, validationDelay),
[validationDelay],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix dependency array in useCallback and handle stale closures.

The current implementation has potential issues with stale closures and unnecessary recreations.

Apply this fix:

 const validateWithDebounce = useCallback(
-  debounce((context: object, schema: IValidationSchema[], params: IUseAsyncValidateParams) => {
+  debounce((context: object, schema: IValidationSchema[]) => {
     const errors = validate(context, schema, params);
     setValidationErrors(errors);
   }, validationDelay),
-  [validationDelay],
+  [validationDelay, params.abortEarly],
 );

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +45 to +74
it('should validate and set errors when validateAsync and validateOnChange are true', () => {
const { result } = renderHook(() =>
useAsyncValidate(mockContext, mockSchema, {
validateAsync: true,
validateOnChange: true,
}),
);

expect(validate).toHaveBeenCalledWith(mockContext, mockSchema, { abortEarly: false });
expect(result.current).toEqual([
{
id: 'name',
originId: 'name',
message: ['error'],
invalidValue: 'John',
},
]);
});

it('should pass abortEarly param to validate function', () => {
renderHook(() =>
useAsyncValidate(mockContext, mockSchema, {
validateAsync: true,
validateOnChange: true,
abortEarly: true,
}),
);

expect(validate).toHaveBeenCalledWith(mockContext, mockSchema, { abortEarly: true });
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling test cases.

The current tests don't verify behavior when validation fails unexpectedly. Add test cases for error scenarios to ensure robust error handling.

it('should handle validation errors gracefully', async () => {
  vi.mocked(validate).mockImplementationOnce(() => {
    throw new Error('Validation failed');
  });

  const { result } = renderHook(() =>
    useAsyncValidate(mockContext, mockSchema, {
      validateAsync: true,
      validateOnChange: true,
    }),
  );

  expect(result.current).toEqual([]);
  // Optionally, verify error was logged or handled appropriately
});

Comment on lines +76 to +111
it('should revalidate when context changes', () => {
const { rerender } = renderHook(
({ context }) =>
useAsyncValidate(context, mockSchema, {
validateAsync: true,
validateOnChange: true,
}),
{
initialProps: { context: mockContext },
},
);

const newContext = { name: 'Jane' };
rerender({ context: newContext });

expect(validate).toHaveBeenCalledWith(newContext, mockSchema, { abortEarly: false });
});

it('should revalidate when schema changes', () => {
const { rerender } = renderHook(
({ schema }) =>
useAsyncValidate(mockContext, schema, {
validateAsync: true,
validateOnChange: true,
}),
{
initialProps: { schema: mockSchema },
},
);

const newSchema = [{ id: 'email', validators: [], rules: [] }];
rerender({ schema: newSchema });

expect(validate).toHaveBeenCalledWith(mockContext, newSchema, { abortEarly: false });
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add cleanup behavior test cases.

Add tests to verify proper cleanup when the component unmounts, especially important for async operations.

it('should cleanup pending validations on unmount', () => {
  const { unmount } = renderHook(() =>
    useAsyncValidate(mockContext, mockSchema, {
      validateAsync: true,
      validateOnChange: true,
    }),
  );

  unmount();
  
  // Verify that pending validations are cancelled
  // and resources are cleaned up
});

Comment on lines +21 to +22
return validate(context, schema, { abortEarly });
}, [context, schema, abortEarly, validateSync, validateOnChange]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential validation errors.

The validate function call could throw errors that should be handled gracefully.

   return useMemo(() => {
-    return validate(context, schema, { abortEarly });
+    try {
+      return validate(context, schema, { abortEarly });
+    } catch (error) {
+      console.error('Validation error:', error);
+      return [];  // Return empty array on validation failure
+    }
   }, [context, schema, abortEarly, validateSync, validateOnChange]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return validate(context, schema, { abortEarly });
}, [context, schema, abortEarly, validateSync, validateOnChange]);
try {
return validate(context, schema, { abortEarly });
} catch (error) {
console.error('Validation error:', error);
return []; // Return empty array on validation failure
}
}, [context, schema, abortEarly, validateSync, validateOnChange]);

Comment on lines +18 to +21
const _validate = useCallback(() => {
const errors = validate(context, schema, { abortEarly });
setValidationErrors(errors);
}, [context, schema, abortEarly]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for the validate function.

The validation function should handle potential errors that might occur during validation to prevent uncaught exceptions.

 const _validate = useCallback(() => {
-    const errors = validate(context, schema, { abortEarly });
-    setValidationErrors(errors);
+    try {
+      const errors = validate(context, schema, { abortEarly });
+      setValidationErrors(errors);
+    } catch (error) {
+      console.error('Validation failed:', error);
+      setValidationErrors([{
+        path: '',
+        message: 'An unexpected error occurred during validation'
+      }]);
+    }
 }, [context, schema, abortEarly]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const _validate = useCallback(() => {
const errors = validate(context, schema, { abortEarly });
setValidationErrors(errors);
}, [context, schema, abortEarly]);
const _validate = useCallback(() => {
try {
const errors = validate(context, schema, { abortEarly });
setValidationErrors(errors);
} catch (error) {
console.error('Validation failed:', error);
setValidationErrors([{
path: '',
message: 'An unexpected error occurred during validation'
}]);
}
}, [context, schema, abortEarly]);

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (10)
packages/ui/src/components/organisms/Form/Validator/helpers.ts (1)

1-3: Add input validation and JSDoc documentation

While the implementation is correct, consider adding input validation and documentation for better maintainability and type safety.

 import { IValidationError } from './types';

+/**
+ * Checks if there are any validation errors in the array
+ * @param errors - Array of validation errors
+ * @throws {Error} If errors array is null or undefined
+ * @returns {boolean} True if there are no errors, false otherwise
+ */
 export const checkIfValid = (errors: IValidationError[]) => {
+  if (!Array.isArray(errors)) {
+    throw new Error('Invalid input: errors must be an array');
+  }
   return errors.length === 0;
 };
packages/ui/src/components/organisms/Form/Validator/_stories/components/JsonEditor/JsonEditor.tsx (3)

5-9: Consider adding stronger typing for the value prop

The value: any type is too permissive and could lead to runtime errors. Consider constraining it to valid JSON types.

interface IJSONEditorProps {
-  value: any;
+  value: Record<string, unknown> | unknown[] | string | number | boolean | null;
  readOnly?: boolean;
  onChange?: (value: any) => void;
}

34-34: Address the focus loss TODO

The focus loss issue can be addressed by implementing a debounced update or by preserving cursor position.

Would you like me to provide an implementation that preserves the editor's focus and cursor position during updates?


11-53: Consider architectural improvements

A few suggestions to improve the component's architecture:

  1. Abstract the editor implementation behind an interface to make it easier to swap out jsoneditor for another library
  2. Implement React.memo to prevent unnecessary rerenders
  3. Consider exposing editor methods through an imperative handle using useImperativeHandle

Would you like me to provide an example implementation of these architectural improvements?

packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/ErrorsList.tsx (1)

3-13: Consider enhancing error display and handling

The current implementation could be improved in several ways:

  1. Add error boundary or fallback for undefined errors
  2. Format errors in a more user-friendly way
  3. Add proper TypeScript types for the error structure

Would you like me to provide a more robust implementation with these improvements?

packages/ui/src/components/organisms/Form/Validator/_stories/components/ValidatorParams/ValidatorParams.tsx (2)

6-15: Enhance type safety with strict interfaces

Consider making the Props interface more strict and adding proper types for validation parameters.

interface ValidationParams {
  validateOnChange: boolean;
  validateSync: boolean;
  validationDelay: number | undefined;
  abortEarly: boolean;
}

interface Props {
  params: ValidationParams;
  onChange: (params: ValidationParams) => void;
  onSave: () => Promise<void>;
}

66-70: Add loading state for save operation

The save button should show a loading state while changes are being applied.

 <Button 
   type="button" 
+  disabled={isLoading}
   onClick={onSave}
 >
-  Save
+  {isLoading ? 'Saving...' : 'Save'}
 </Button>
packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/Story.tsx (1)

24-34: Consider using a reducer for complex state management

The validator parameters state could be better managed using useReducer for better maintainability.

Would you like me to provide an implementation using useReducer?

packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/schema.ts (2)

4-24: Consider adjusting name validation constraints

  1. The minLength: 1 validation is redundant since the field is already marked as required.
  2. The maxLength: 10 might be too restrictive for names, potentially causing issues with longer names or names from different cultures.

Consider this adjustment:

   {
     id: 'firstname-field',
     valueDestination: 'firstName',
     validators: [
       {
         type: 'required',
         message: 'Name is required',
         value: {},
       },
-      {
-        type: 'minLength',
-        value: { minLength: 1 },
-        message: 'Name must be at least {minLength} characters long',
-      },
       {
         type: 'maxLength',
-        value: { maxLength: 10 },
+        value: { maxLength: 50 },
         message: 'Name must be at most {maxLength} characters long',
       },
     ],
   }

25-35: Maintain consistent validation rules between name fields

The last name field has fewer validation rules compared to the first name field. Consider applying consistent validation rules across related fields.

Consider adding similar length validation:

   {
     id: 'lastname-field',
     valueDestination: 'lastName',
     validators: [
       {
         type: 'required',
         message: 'Last name is required',
         value: {},
       },
+      {
+        type: 'maxLength',
+        value: { maxLength: 50 },
+        message: 'Last name must be at most {maxLength} characters long',
+      },
     ],
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4d2c708 and ce18a0c.

📒 Files selected for processing (14)
  • packages/ui/package.json (3 hunks)
  • packages/ui/src/components/organisms/Form/Validator/Validator.stories.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/JsonEditor/JsonEditor.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/ErrorsList.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/Story.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/context.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/schema.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/ValidatorParams/ValidatorParams.tsx (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/ValidatorParams/index.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/helpers.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/helpers.unit.test.ts (1 hunks)
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts (1 hunks)
✅ Files skipped from review due to trivial changes (4)
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/index.ts
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/ValidatorParams/index.ts
  • packages/ui/src/components/organisms/Form/Validator/Validator.stories.tsx
  • packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/context.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx
  • packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts
  • packages/ui/package.json
🧰 Additional context used
🪛 Biome (1.9.4)
packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/Story.tsx

[error] 11-11: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

🔇 Additional comments (5)
packages/ui/src/components/organisms/Form/Validator/helpers.unit.test.ts (1)

1-3: Verify integration with ValidatorProvider

Let's ensure the helper function is properly integrated with the ValidatorProvider component.

✅ Verification successful

Integration with ValidatorProvider is properly implemented

The verification confirms that checkIfValid is correctly imported and used within the ValidatorProvider component to determine form validity state, which is then passed through the context provider.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of checkIfValid in ValidatorProvider
ast-grep --pattern 'import { checkIfValid } from "./helpers"' 
rg -A 5 "checkIfValid" packages/ui/src/components/organisms/Form/Validator/ValidatorProvider.tsx

Length of output: 672

packages/ui/src/components/organisms/Form/Validator/_stories/components/Story/schema.ts (4)

1-3: LGTM!

The schema is properly typed and exported.


92-134: Review nested list validation structure

  1. The validators: [] for nested-list-item-sublist is empty. Verify if this is intentional.
  2. The deep nesting structure might make validation error messages difficult to understand for users.

Let's check how validation messages are handled for nested structures:

✅ Verification successful

Let me try a different search strategy to understand how validation messages are handled and if empty validators are a common pattern:


Let me check the validation message handling implementation specifically:


Based on the validation implementation code I can now provide the final response:

Empty validators array is valid, nested validation messages are handled properly

  1. The empty validators array for nested-list-item-sublist is intentional and valid because:

    • It serves as a structural container for its children
    • The validation logic in validate.ts properly handles empty validator arrays
    • Child elements still have their own validators that will be processed
  2. The nested validation messages are well-handled because:

    • The validation system tracks the validation context through a stack mechanism
    • Error messages include the full path context through the stack parameter
    • The createValidationError utility ensures proper error context preservation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for nested validation message handling
rg -A 5 "nested.*validation|nested.*message"

Length of output: 696


Script:

#!/bin/bash
# Search for validator patterns in schema files
fd "schema.ts" --exec rg -A 5 "validators:\s*\[\s*\]|validators:\s*\[$" {}

# Search for validation message handling in the Form/Validator directory
fd . "packages/ui/src/components/organisms/Form/Validator" --type f --exec rg -l "message" {}

# Look for validation message handling implementation
ast-grep --pattern 'message: $MESSAGE'

Length of output: 6934


Script:

#!/bin/bash
# Check validation message handling in the format-error-message utility
cat packages/ui/src/components/organisms/Form/Validator/utils/format-error-message/format-error-message.ts

# Look for validation error handling in the validate utility
cat packages/ui/src/components/organisms/Form/Validator/utils/validate/validate.ts

# Check if there are other similar nested validation schemas
rg -A 5 "nestedList.*validators" 

Length of output: 2651


36-62: Review age validation constraints and conditional logic

  1. The age range of 18-65 might be too restrictive depending on the use case.
  2. The conditional validation requires both first and last names to be present before validating age, which might not be the desired behavior in all scenarios.

Let's verify if these age restrictions align with other parts of the application:


63-91: Review array index handling in valueDestination

The valueDestination: 'list[$0]' pattern might not correctly handle all array indices. Consider if this needs to support dynamic array access.

Let's check how array indices are handled in the validator implementation:

✅ Verification successful

These commands will help us understand:

  1. How array indices are handled in valueDestination patterns
  2. The validator implementation details
  3. Any array/list index handling patterns in the codebase

These commands will help us understand:

  1. How valueDestination with array indices is formatted and handled
  2. The validation logic for array values

Based on the search results, I can now provide a conclusive response about the array index handling in the validator:

Array indices are properly handled through a robust formatting system

The validator implementation includes a dedicated formatValueDestination function that correctly handles array indices in the following ways:

  1. It supports both single and nested array access patterns (e.g., list[$0] and nestedList[$0].sublist[$1].value)
  2. The indices are dynamically replaced during validation using a stack-based system
  3. The test suite demonstrates proper handling of:
    • Simple array access: tasks[1].name
    • Nested array access: tasks[1].siblings[2].name
    • Partial index replacement: tasks[1].siblings[$1].name

The valueDestination: 'list[$0]' pattern in your code is valid and will work correctly with the validator implementation.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for array index handling in validator code
ast-grep --pattern 'valueDestination.*\$[0-9]'

Length of output: 48


Script:

#!/bin/bash
# Let's search for array index handling patterns more broadly
rg -A 5 "valueDestination.*\[.*\]" 

Length of output: 46055


Script:

#!/bin/bash
# Let's also look for any validator implementation files
fd "validator" --type f --exec cat {}

Length of output: 36211


Script:

#!/bin/bash
# Search for array or list handling in validator code
rg -A 5 "\[\$\d+\]|\$\{index\}|\bindex\b" 

Length of output: 67784


Script:

#!/bin/bash
# Let's look at the validator implementation to understand how array indices are handled
rg -A 5 "formatValueDestination|valueDestination.*\[.*\]" packages/ui/src/components/organisms/Form/Validator/

Length of output: 20716


Script:

#!/bin/bash
# Let's also check for any array validation logic
ast-grep --pattern 'function validate($_, $_) {
  $$$
  value.forEach($_, $_) {
    $$$
  }
  $$$
}'

Length of output: 102

Comment on lines +5 to +17
describe('helpers', () => {
describe('checkIfValid', () => {
it('should return true if there are no errors', () => {
expect(checkIfValid([])).toBe(true);
});

it('should return false if there are errors', () => {
expect(
checkIfValid([{ message: 'error', element: 'element' } as unknown as IValidationError]),
).toBe(false);
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance test coverage and improve test data structure

The current tests cover basic scenarios, but could be improved for better reliability and maintainability.

 describe('helpers', () => {
+  // Test data
+  const mockValidationError: IValidationError = {
+    message: 'error',
+    element: 'element'
+  };
+
   describe('checkIfValid', () => {
     it('should return true if there are no errors', () => {
       expect(checkIfValid([])).toBe(true);
     });

     it('should return false if there are errors', () => {
-      expect(
-        checkIfValid([{ message: 'error', element: 'element' } as unknown as IValidationError]),
-      ).toBe(false);
+      expect(checkIfValid([mockValidationError])).toBe(false);
     });
+
+    it('should throw error for invalid input', () => {
+      expect(() => checkIfValid(null as any)).toThrow('Invalid input');
+      expect(() => checkIfValid(undefined as any)).toThrow('Invalid input');
+      expect(() => checkIfValid({} as any)).toThrow('Invalid input');
+    });
   });
 });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +19 to +29
useEffect(() => {
if (!containerRef.current) return;

if (editorRef.current) return;

editorRef.current = new JSONEditor(containerRef.current!, {
onChange: () => {
editorRef.current && onChange && onChange(editorRef.current.get());
},
});
}, [containerRef, editorRef]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix useEffect dependencies and add cleanup

The current implementation has two issues:

  1. Missing onChange in the dependencies array could lead to stale closures
  2. Missing cleanup function could lead to memory leaks
  useEffect(() => {
    if (!containerRef.current) return;
    if (editorRef.current) return;

    editorRef.current = new JSONEditor(containerRef.current!, {
      onChange: () => {
        editorRef.current && onChange && onChange(editorRef.current.get());
      },
    });
+   
+   return () => {
+     if (editorRef.current) {
+       editorRef.current.destroy();
+       editorRef.current = null;
+     }
+   };
-  }, [containerRef, editorRef]);
+  }, [containerRef, editorRef, onChange]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!containerRef.current) return;
if (editorRef.current) return;
editorRef.current = new JSONEditor(containerRef.current!, {
onChange: () => {
editorRef.current && onChange && onChange(editorRef.current.get());
},
});
}, [containerRef, editorRef]);
useEffect(() => {
if (!containerRef.current) return;
if (editorRef.current) return;
editorRef.current = new JSONEditor(containerRef.current!, {
onChange: () => {
editorRef.current && onChange && onChange(editorRef.current.get());
},
});
return () => {
if (editorRef.current) {
editorRef.current.destroy();
editorRef.current = null;
}
};
}, [containerRef, editorRef, onChange]);

Comment on lines +31 to +37
useEffect(() => {
if (!editorRef.current) return;

//TODO: Each set of value rerenders editor and loses focus, find workarounds
editorRef.current.set(value);
}, [editorRef, readOnly]);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect dependencies and remove redundant effect

The effect has readOnly in its dependencies but doesn't use it. Also, this effect seems redundant with the next one.

-  useEffect(() => {
-    if (!editorRef.current) return;
-
-    //TODO: Each set of value rerenders editor and loses focus, find workarounds
-    editorRef.current.set(value);
-  }, [editorRef, readOnly]);

Comment on lines +52 to +53
return <div className="h-full" ref={containerRef} />;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error boundary and loading state

The component should handle JSON parsing errors gracefully and show a loading state during initialization.

+ import { ErrorBoundary } from 'react-error-boundary';
+ 
+ const EditorErrorFallback = ({ error, resetErrorBoundary }) => (
+   <div className="h-full p-4 text-red-500">
+     <p>Error loading editor: {error.message}</p>
+     <button onClick={resetErrorBoundary}>Try again</button>
+   </div>
+ );

  export const JSONEditorComponent: FunctionComponent<IJSONEditorProps> = ({
    value,
    readOnly,
    onChange,
  }) => {
    const containerRef = useRef<HTMLDivElement | null>(null);
    const editorRef = useRef<JSONEditor | null>(null);
+   const [isLoading, setIsLoading] = useState(true);

    // ... existing effects ...

+   useEffect(() => {
+     if (editorRef.current) {
+       setIsLoading(false);
+     }
+   }, [editorRef.current]);

-   return <div className="h-full" ref={containerRef} />;
+   return (
+     <ErrorBoundary FallbackComponent={EditorErrorFallback}>
+       <div className="h-full relative" ref={containerRef}>
+         {isLoading && (
+           <div className="absolute inset-0 flex items-center justify-center bg-white bg-opacity-50">
+             Loading editor...
+           </div>
+         )}
+       </div>
+     </ErrorBoundary>
+   );
  };

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +8 to +10
{errors.map((error, index) => (
<div key={index}>{JSON.stringify(error)}</div>
))}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid using array index as React key

Using array index as key can lead to rendering issues when the array items change. Consider using a unique identifier from the error object or a stable hash of the error content.

-{errors.map((error, index) => (
-  <div key={index}>{JSON.stringify(error)}</div>
-))}
+{errors.map((error) => (
+  <div key={typeof error === 'string' ? error : JSON.stringify(error)}>
+    {typeof error === 'string' ? error : JSON.stringify(error)}
+  </div>
+))}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +45 to +55
<Input
type="number"
placeholder="500"
value={params.validationDelay || ''}
onChange={e =>
onChange({
...params,
validationDelay: e.target.value === '' ? undefined : Number(e.target.value),
})
}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation and accessibility for validationDelay input

The number input needs validation and accessibility improvements:

  1. Prevent negative values
  2. Add aria-label for screen readers
  3. Add min/max constraints
 <Input
   type="number"
+  min="0"
+  max="5000"
+  aria-label="Validation delay in milliseconds"
   placeholder="500"
   value={params.validationDelay || ''}
   onChange={e => {
+    const value = e.target.value === '' ? undefined : Math.max(0, Number(e.target.value));
     onChange({
       ...params,
-      validationDelay: e.target.value === '' ? undefined : Number(e.target.value),
+      validationDelay: value,
     })
   }}
 />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Input
type="number"
placeholder="500"
value={params.validationDelay || ''}
onChange={e =>
onChange({
...params,
validationDelay: e.target.value === '' ? undefined : Number(e.target.value),
})
}
/>
<Input
type="number"
min="0"
max="5000"
aria-label="Validation delay in milliseconds"
placeholder="500"
value={params.validationDelay || ''}
onChange={e => {
const value = e.target.value === '' ? undefined : Math.max(0, Number(e.target.value));
onChange({
...params,
validationDelay: value,
})
}}
/>

Comment on lines +9 to +18
const evenNumbersValidator = (value: number) => {
// Ignoring validation if value is not a number
if (isNaN(value)) return true;

if (value % 2 !== 0) {
throw new Error('Value is not even');
}

return true;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve evenNumbersValidator implementation

The validator needs several improvements:

  1. Replace unsafe isNaN with Number.isNaN
  2. Add proper type checking
  3. Improve error message
-const evenNumbersValidator = (value: number) => {
+const evenNumbersValidator = (value: unknown): boolean => {
   // Ignoring validation if value is not a number
-  if (isNaN(value)) return true;
+  if (typeof value !== 'number' || Number.isNaN(value)) return true;

   if (value % 2 !== 0) {
-    throw new Error('Value is not even');
+    throw new Error(`Value ${value} is not an even number`);
   }

   return true;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const evenNumbersValidator = (value: number) => {
// Ignoring validation if value is not a number
if (isNaN(value)) return true;
if (value % 2 !== 0) {
throw new Error('Value is not even');
}
return true;
};
const evenNumbersValidator = (value: unknown): boolean => {
// Ignoring validation if value is not a number
if (typeof value !== 'number' || Number.isNaN(value)) return true;
if (value % 2 !== 0) {
throw new Error(`Value ${value} is not an even number`);
}
return true;
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 11-11: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.

See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.

(lint/suspicious/noGlobalIsNan)

return true;
};

registerValidator('evenNumber', evenNumbersValidator);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add cleanup for validator registration

The validator registration should be cleaned up when the component unmounts.

+import { useEffect } from 'react';

 // ... other code ...

-registerValidator('evenNumber', evenNumbersValidator);
+useEffect(() => {
+  registerValidator('evenNumber', evenNumbersValidator);
+  return () => {
+    // Assuming there's an unregisterValidator function
+    unregisterValidator('evenNumber');
+  };
+}, []);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +1 to +134
},
{
id: 'age-field',
valueDestination: 'age',
validators: [
{
type: 'required',
message: 'Age is required',
value: {},
applyWhen: {
type: 'json-logic',
value: {
and: [{ '!!': { var: 'firstName' } }, { '!!': { var: 'lastName' } }],
},
},
},
{
type: 'minimum',
value: { minimum: 18 },
message: 'You must be at least {minimum} years old',
},
{
type: 'maximum',
value: { maximum: 65 },
message: 'You must be at most {maximum} years old',
},
],
},
{
id: 'list-field',
valueDestination: 'list',
validators: [
{
type: 'required',
message: 'List is required',
value: {},
},
{
type: 'minLength',
value: { minLength: 1 },
message: 'List must be at least {minLength} items long',
},
],
children: [
{
id: 'list-item',
valueDestination: 'list[$0]',
validators: [
{
type: 'maxLength',
message: 'Item must be at most {maxLength} characters long',
value: { maxLength: 5 },
},
],
},
],
},
{
id: 'nested-list',
valueDestination: 'nestedList',
validators: [
{
type: 'required',
value: {},
message: 'Nested list is required',
},
],
children: [
{
id: 'nested-list-item-value',
valueDestination: 'nestedList[$0].value',
validators: [
{
type: 'required',
value: {},
message: 'Nested list item value is required',
},
],
},
{
id: 'nested-list-item-sublist',
valueDestination: 'nestedList[$0].sublist',
validators: [],
children: [
{
id: 'nested-list-subitem',
valueDestination: 'nestedList[$0].sublist[$1].value',
validators: [
{
type: 'maxLength',
value: { maxLength: 10 },
message: 'Subitem must be at most {maxLength} characters long',
},
],
},
],
},
],
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Additional test coverage needed for complex validation scenarios

Based on the test files examined, there are gaps in test coverage that need to be addressed:

  1. No tests found for nested structure validation (parent-child relationships in the schema)
  2. Missing tests for conditional validation using applyWhen with json-logic
  3. No coverage for array index handling (e.g., list[$0], nestedList[$0].sublist[$1].value)
  4. While basic message formatting is tested, complex nested path validation messages need coverage

The existing tests only cover basic validator functionality (min-length, required) and registration, but not the complex scenarios present in the schema.

🔗 Analysis chain

Ensure comprehensive test coverage

Given the complexity of the validation schema, especially with nested structures and conditional validations, ensure that there are comprehensive tests covering:

  1. Conditional validation scenarios
  2. Nested list validation
  3. Array index handling
  4. Error message formatting

Let's check the test coverage:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for related test files
fd -g "*validator*.test.{ts,tsx}"

Length of output: 1186


Script:

#!/bin/bash
# Let's check the content of some key test files to verify coverage of complex scenarios
echo "=== Checking test coverage for nested structures ==="
rg -A 5 "describe.*nest" packages/ui/src/components/organisms/Form/Validator/**/*.test.{ts,tsx}

echo -e "\n=== Checking test coverage for conditional validation ==="
rg -A 5 "describe.*condition|applyWhen" packages/ui/src/components/organisms/Form/Validator/**/*.test.{ts,tsx}

echo -e "\n=== Checking test coverage for array handling ==="
rg -A 5 "describe.*array|\[\$\d\]" packages/ui/src/components/organisms/Form/Validator/**/*.test.{ts,tsx}

echo -e "\n=== Checking test coverage for error message formatting ==="
rg -A 5 "message.*\{.*\}" packages/ui/src/components/organisms/Form/Validator/**/*.test.{ts,tsx}

Length of output: 2012


Script:

#!/bin/bash
# Let's try a different approach to check test contents
# First, let's check min-length validator tests as they handle array scenarios
cat packages/ui/src/components/organisms/Form/Validator/validators/min-length/min-length.validator.unit.test.ts

echo -e "\n=== Checking required validator for conditional validation ==="
cat packages/ui/src/components/organisms/Form/Validator/validators/required/required.validator.unit.test.ts

echo -e "\n=== Checking validator registration for nested structures ==="
cat packages/ui/src/components/organisms/Form/Validator/utils/register-validator/register-validator.unit.test.ts

Length of output: 4276

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant