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

validator: boolean #648

Merged
merged 8 commits into from
Oct 5, 2024
Merged

validator: boolean #648

merged 8 commits into from
Oct 5, 2024

Conversation

bangarang
Copy link
Collaborator

Please explain how to summarize this PR for the Changelog:

Tell code reviewer how and what to test:

@bangarang bangarang marked this pull request as ready for review October 1, 2024 06:35
Copy link
Contributor

coderabbitai bot commented Oct 1, 2024

Walkthrough

The changes introduce a new Flatfile Boolean Validator Plugin that validates boolean values with options for strict and truthy validation. It supports multiple languages and allows for custom configurations, including case sensitivity and error message handling. Additionally, a Rollup configuration file is added for building the plugin, and a new TypeScript file implements the validation logic. An end-to-end test suite is also created to ensure the functionality of the validateBoolean method under various scenarios.

Changes

Files Change Summary
validate/boolean/README.MD Introduction of the Flatfile Boolean Validator Plugin, detailing its features and configuration options.
validate/boolean/rollup.config.mjs Added a Rollup configuration file for the build process, exporting a default config.
validate/boolean/src/index.ts Implemented the Boolean validation mechanism, defining configuration options and the validateBoolean function.
validate/boolean/src/validate.boolean.plugin.e2e.spec.ts Introduced an end-to-end test suite for the validateBoolean function, covering various validation scenarios.
validate/boolean/src/validate.boolean.plugin.ts Created a Boolean validation plugin with configuration interface and validation functions.

Possibly related PRs

  • validate: isbn #618: The changes in this PR involve the introduction of a new validation plugin for ISBNs, which, like the Boolean Validator Plugin in the main PR, focuses on validating specific data formats within Flatfile records. Both plugins involve configuration options and validation logic tailored to their respective data types.

Suggested reviewers

  • carlbrugger

🪧 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: 8

🧹 Outside diff range and nitpick comments (1)
validate/boolean/rollup.config.mjs (1)

1-5: Overall, the Rollup configuration looks good, with room for minor improvements.

The configuration file is concise, well-structured, and follows best practices for Rollup configuration. The use of a custom buildConfig function from an internal package suggests a standardized approach across the organization, which is commendable.

However, there are a couple of points to consider for improvement:

  1. Documentation: Consider adding a brief comment at the top of the file explaining the purpose of this configuration, especially in relation to the boolean validator.

  2. Specificity: Verify if any specific configuration options are needed for the boolean validator. If not, it might be helpful to add a comment explaining why the default configuration is sufficient.

These minor additions would enhance the maintainability and clarity of the configuration file.

Would you like assistance in drafting the suggested documentation comments?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8c6ca84 and a7de87a.

⛔ Files ignored due to path filters (2)
  • validate/boolean/metadata.json is excluded by !**/*.json
  • validate/boolean/package.json is excluded by !**/*.json
📒 Files selected for processing (4)
  • validate/boolean/README.MD (1 hunks)
  • validate/boolean/rollup.config.mjs (1 hunks)
  • validate/boolean/src/index.ts (1 hunks)
  • validate/boolean/src/validateBoolean.e2e.spec.ts (1 hunks)
🧰 Additional context used
🪛 LanguageTool
validate/boolean/README.MD

[uncategorized] ~44-~44: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 Biome
validate/boolean/src/index.ts

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


[error] 88-88: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🔇 Additional comments (5)
validate/boolean/rollup.config.mjs (3)

1-1: LGTM: Import statement is correct and follows best practices.

The import statement correctly uses ES6 module syntax to import the buildConfig function from what appears to be an internal Flatfile package. This approach suggests a standardized configuration setup across the organization, which is a good practice for maintaining consistency.


3-3: Verify if default configuration is sufficient for boolean validator.

The configuration is created using the buildConfig function with an empty object as an argument, which will use default configuration options. While this approach is valid, it's worth confirming whether any specific configuration options are needed for a boolean validator.

To ensure we're not missing any necessary configuration, could you verify if there are any boolean-specific options that should be included? You might want to check the documentation for @flatfile/rollup-config or consult with the team about standard practices for validator configurations.


5-5: LGTM: Default export is correct.

The configuration object is correctly exported as the default export using ES6 module syntax. This is the standard approach for Rollup configuration files and will allow easy import in other parts of the build process.

validate/boolean/src/index.ts (1)

90-91: Verify the conversion of numeric values to boolean

When normalizedValue is a number, it is converted to a boolean using Boolean(normalizedValue). This means any non-zero number becomes true, and zero becomes false. Ensure that this conversion aligns with the intended validation logic and doesn't introduce unexpected behavior.

Would you like assistance in reviewing this conversion to ensure it meets the validation requirements?

validate/boolean/src/validateBoolean.e2e.spec.ts (1)

52-79: ⚠️ Potential issue

Verify the acceptance of string values in 'strict' validation

In the 'strict' validation type test, strings 'true' and 'false' are being accepted as valid boolean values. If 'strict' validation is intended to accept only boolean true and false, then string representations should be considered invalid. Please confirm whether accepting strings in 'strict' mode is the intended behavior.

Comment on lines 27 to 29
import { validateBoolean } from '@flatfile/plugin-boolean-validator';

const booleanValidator = validateBoolean({
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

Inconsistent Function Naming: Use validateBoolean Consistently

The example code uses validateBoolean, but the configuration section refers to the BooleanValidator function. For clarity and consistency, please use the same function name throughout the documentation.

Suggestion: Replace BooleanValidator with validateBoolean in the configuration section to match the example usage.

Comment on lines +42 to +44
The `BooleanValidator` function accepts a configuration object with the following properties:

- `fields`: An array of field names to validate
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

Formatting Issue: Remove Extra Blank Line Before List

An extra blank line after line 42 may disrupt the list formatting in Markdown.

Suggestion: Remove the blank line at line 43 so that the list follows immediately after the introductory sentence.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~44-~44: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)


if (normalizedValue === true || normalizedValue === false) {
record.set(field, normalizedValue)
} else if (mapping.hasOwnProperty(normalizedValue)) {
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

Use Object.hasOwn() instead of hasOwnProperty

Directly accessing hasOwnProperty on an object can lead to unexpected behavior if the object has a property named hasOwnProperty. It's safer to use Object.hasOwn() for property checks to avoid potential prototype pollution issues.

Apply this diff to fix the issue:

-      } else if (mapping.hasOwnProperty(normalizedValue)) {
+      } else if (Object.hasOwn(mapping, normalizedValue)) {
📝 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
} else if (mapping.hasOwnProperty(normalizedValue)) {
} else if (Object.hasOwn(mapping, normalizedValue)) {
🧰 Tools
🪛 Biome

[error] 88-88: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Comment on lines 47 to 50
case 'skip':
default:
// Do nothing, leave the field as is
break
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 redundant case 'skip': in switch statement

In the handleNullValue function, the case 'skip': clause is unnecessary because the default: clause already handles all unspecified cases, including 'skip'. Removing this redundant case simplifies the code without changing its behavior.

Apply this diff to fix the issue:

       switch (config.handleNull) {
         case 'error':
           // ...
           break
         case 'false':
           // ...
           break
         case 'true':
           // ...
           break
-        case 'skip':
         default:
           // Do nothing, leave the field as is
           break
       }
📝 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
case 'skip':
default:
// Do nothing, leave the field as is
break
default:
// Do nothing, leave the field as is
break
🧰 Tools
🪛 Biome

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

Comment on lines 76 to 79
const defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping
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 undefined language mappings to prevent runtime errors

When config.language is set to a language not defined in languageMappings, languageMappings[config.language] will be undefined. This can cause errors when mapping is used later in the code. Consider providing a fallback to the English mapping or adding a check to ensure the mapping exists.

Apply this diff to fix the issue:

       const defaultMapping = config.language
-        ? languageMappings[config.language]
+        ? languageMappings[config.language] || languageMappings.en
         : languageMappings.en
📝 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 defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping
const defaultMapping = config.language
? languageMappings[config.language] || languageMappings.en
: languageMappings.en
const mapping = config.customMapping || defaultMapping

Comment on lines 86 to 88
if (normalizedValue === true || normalizedValue === false) {
record.set(field, normalizedValue)
} else if (mapping.hasOwnProperty(normalizedValue)) {
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

Correct type checking for boolean values

In the validateTruthyBoolean function, the condition normalizedValue === true || normalizedValue === false will almost always evaluate to false because normalizedValue is typically a string after normalization. This condition should check the type of value directly to accurately identify boolean values.

Apply this diff to fix the issue:

-      if (normalizedValue === true || normalizedValue === false) {
+      if (typeof value === 'boolean') {
         record.set(field, 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
if (normalizedValue === true || normalizedValue === false) {
record.set(field, normalizedValue)
} else if (mapping.hasOwnProperty(normalizedValue)) {
if (typeof value === 'boolean') {
record.set(field, value)
} else if (mapping.hasOwnProperty(normalizedValue)) {
🧰 Tools
🪛 Biome

[error] 88-88: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Comment on lines 52 to 157
)

await createRecords(sheetId, [
{ isActive: true },
{ isActive: false },
{ isActive: 'true' },
{ isActive: 'false' },
{ isActive: 1 },
{ isActive: 0 },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['isActive'].value).toBe(true)
expect(records[1].values['isActive'].value).toBe(false)
expect(records[2].values['isActive'].value).toBe(true)
expect(records[3].values['isActive'].value).toBe(false)
expect(records[4].values['isActive'].messages[0].message).toContain('Invalid boolean value')
expect(records[5].values['isActive'].messages[0].message).toContain('Invalid boolean value')

})

it('validates truthy boolean values', async () => {
listener.use(
validateBoolean({
fields: ['hasSubscription'],
validationType: 'truthy',
})
)

await createRecords(sheetId, [
{ hasSubscription: true },
{ hasSubscription: false },
{ hasSubscription: 'true' },
{ hasSubscription: 'false' },
{ hasSubscription: 1 },
{ hasSubscription: 0 },
{ hasSubscription: 'yes' },
{ hasSubscription: 'no' },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['hasSubscription'].value).toBeTruthy()
expect(records[1].values['hasSubscription'].value).toBeFalsy()
expect(records[2].values['hasSubscription'].value).toBeTruthy()
expect(records[3].values['hasSubscription'].value).toBeFalsy()
expect(records[4].values['hasSubscription'].value).toBeTruthy()
expect(records[5].values['hasSubscription'].value).toBeFalsy()
expect(records[6].values['hasSubscription'].value).toBeTruthy()
expect(records[7].values['hasSubscription'].value).toBeFalsy()
})

it('handles custom mapping', async () => {
listener.use(
validateBoolean({
fields: ['agreeToTerms'],
validationType: 'truthy',
customMapping: { 'agreed': true, 'disagreed': false },
})
)

await createRecords(sheetId, [
{ agreeToTerms: 'agreed' },
{ agreeToTerms: 'disagreed' },
{ agreeToTerms: 'yes' },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['agreeToTerms'].value).toBeTruthy()
expect(records[1].values['agreeToTerms'].value).toBeFalsy()
expect(records[2].values['agreeToTerms'].value).toBeTruthy()
})

it('handles null values', async () => {
listener.use(
validateBoolean({
fields: ['optIn'],
validationType: 'truthy',
handleNull: 'false',
})
)

await createRecords(sheetId, [
{ optIn: null },
{ optIn: undefined },
{ optIn: '' },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['optIn'].value).toBe(false)
expect(records[1].values['optIn'].value).toBe(false)
expect(records[2].values['optIn'].value).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

Refactor tests using parameterized testing to reduce duplication

The test cases within validateBoolean() have similar structures with repeated code blocks. Refactoring them using parameterized tests can improve maintainability and readability.

Consider refactoring using Jest's test.each or forEach to parameterize the tests:

describe('validateBoolean()', () => {
  const testCases = [
    {
      description: 'validates strict boolean values',
      listenerConfig: {
        fields: ['isActive'],
        validationType: 'strict',
      },
      records: [
        { input: { isActive: true }, expectedValue: true },
        { input: { isActive: false }, expectedValue: false },
        { input: { isActive: 'true' }, expectedValue: true },
        { input: { isActive: 'false' }, expectedValue: false },
        { input: { isActive: 1 }, expectedError: 'Invalid boolean value' },
        { input: { isActive: 0 }, expectedError: 'Invalid boolean value' },
      ],
    },
    // Add other test scenarios here
  ];

  testCases.forEach(({ description, listenerConfig, records }) => {
    it(description, async () => {
      listener.use(validateBoolean(listenerConfig));

      await createRecords(sheetId, records.map((r) => r.input));
      await listener.waitFor('commit:created');

      const resultRecords = await getRecords(sheetId);

      resultRecords.forEach((record, index) => {
        const expectedValue = records[index].expectedValue;
        const expectedError = records[index].expectedError;
        const field = listenerConfig.fields[0];

        if (expectedValue !== undefined) {
          expect(record.values[field].value).toBe(expectedValue);
        }
        if (expectedError) {
          expect(record.values[field].messages[0].message).toContain(expectedError);
        }
      });
    });
  });
});

Comment on lines 18 to 19
const logSpy = jest.spyOn(global.console, 'log')
const logErrorSpy = jest.spyOn(global.console, 'error')
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 unused console spies

The logSpy and logErrorSpy variables are declared but never used in the test suite. Removing them will clean up the code.

Apply this diff to remove the unused variables:

- const logSpy = jest.spyOn(global.console, 'log')
- const logErrorSpy = jest.spyOn(global.console, 'error')
📝 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 logSpy = jest.spyOn(global.console, 'log')
const logErrorSpy = jest.spyOn(global.console, 'error')

Copy link
Contributor

@carlbrugger carlbrugger left a comment

Choose a reason for hiding this comment

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

Approved with a few nits

validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/package.json Outdated Show resolved Hide resolved
validate/boolean/src/index.ts Outdated Show resolved Hide resolved
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: 4

🧹 Outside diff range and nitpick comments (3)
validate/boolean/src/index.ts (3)

4-19: Consider adding JSDoc comments for better documentation.

The BooleanValidatorConfig interface is well-structured and comprehensive. To improve clarity and maintainability, consider adding JSDoc comments for each field, especially for fields with multiple options or non-obvious usage.

For example:

interface BooleanValidatorConfig {
  /** List of fields to validate */
  fields: string[]
  /** Validation type: 'strict' for exact boolean values, 'truthy' for flexible parsing */
  validationType: 'strict' | 'truthy'
  // ... (add comments for other fields)
  /** Specify the sheet slug for targeted validation */
  sheetSlug?: string
}

This will provide better context for developers using this configuration interface.


98-116: Enhance error and info messages in handleInvalidValue function.

The handleInvalidValue function correctly handles invalid boolean values. However, the error and info messages could be more descriptive to provide better context. Consider the following improvements:

  1. Include the field name in the error message.
  2. Add the original invalid value to the info message.

Apply this diff to implement these suggestions:

 function handleInvalidValue(
   record: FlatfileRecord,
   field: string,
   config: BooleanValidatorConfig
 ) {
   if (config.defaultValue === undefined || config.defaultValue === 'skip') {
     record.addError(
       field,
       config.customErrorMessages?.invalidBoolean ||
-        'Invalid boolean value'
+        `Invalid boolean value for field '${field}'`
     )
   } else {
     record.set(field, config.defaultValue)
     record.addInfo(
       field,
-      `Invalid value converted to default: ${config.defaultValue}`
+      `Invalid value '${record.get(field)}' converted to default: ${config.defaultValue}`
     )
   }
 }

These changes provide more context in the error and info messages, making it easier for users to understand and debug issues.


119-141: Remove unnecessary async keyword and LGTM.

The validateBoolean function is well-implemented and correctly integrates with the Flatfile Listener. However, the async keyword on the callback function is unnecessary as there are no asynchronous operations inside it.

Apply this small change:

 export const validateBoolean = (config: BooleanValidatorConfig) => {
   return (listener: FlatfileListener) => {
     listener.use(
-      recordHook(config.sheetSlug || '**', async (record: FlatfileRecord) => {
+      recordHook(config.sheetSlug || '**', (record: FlatfileRecord) => {
         config.fields.forEach((field) => {
           // ... (existing code)
         })

         return record
       })
     )
   }
 }

Other than this minor adjustment, the implementation looks good and covers both strict and truthy validation types as specified in the configuration.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a7de87a and 19498b1.

⛔ Files ignored due to path filters (1)
  • validate/boolean/package.json is excluded by !**/*.json
📒 Files selected for processing (1)
  • validate/boolean/src/index.ts (1 hunks)
🧰 Additional context used
🪛 Biome
validate/boolean/src/index.ts

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


[error] 88-88: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🔇 Additional comments (2)
validate/boolean/src/index.ts (2)

1-2: LGTM: Imports are correct and follow best practices.

The current import syntax is appropriate and follows modern TypeScript practices. It correctly imports the necessary types and functions from the Flatfile packages.


1-141: Overall, a well-implemented and flexible boolean validation system.

This implementation provides a comprehensive and flexible boolean validation system for Flatfile. The code is well-structured, follows TypeScript best practices, and offers a wide range of configuration options to handle various scenarios.

Key strengths:

  1. Flexible configuration options through the BooleanValidatorConfig interface.
  2. Support for multiple languages and custom mappings.
  3. Handling of both strict and truthy boolean validations.
  4. Integration with the Flatfile Listener system.

Areas for improvement have been noted in previous comments, including:

  1. Adding JSDoc comments for better documentation.
  2. Making language mappings extensible.
  3. Improving error and info messages for better context.
  4. Minor optimizations and safety improvements in some functions.

These suggestions, if implemented, will further enhance the robustness and usability of the validation system. Great job overall!

🧰 Tools
🪛 Biome

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


[error] 88-88: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Comment on lines 21 to 26
const languageMappings: Record<string, Record<string, boolean>> = {
en: { yes: true, no: false, y: true, n: false },
es: { sí: true, si: true, no: false, s: true, n: false },
fr: { oui: true, non: false, o: true, n: false },
de: { ja: true, nein: false, j: true, n: 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

Consider making languageMappings extensible.

The languageMappings object is well-defined and covers common languages. To improve flexibility and allow for easy addition of new languages, consider:

  1. Exporting the languageMappings object.
  2. Providing a function to add new language mappings.

Example implementation:

export const languageMappings: Record<string, Record<string, boolean>> = {
  // ... existing mappings
}

export function addLanguageMapping(lang: string, mapping: Record<string, boolean>) {
  languageMappings[lang] = { ...languageMappings[lang], ...mapping }
}

This approach would allow users to easily extend the language support without modifying the original code.

Comment on lines 28 to 52
function handleNullValue(
record: FlatfileRecord,
field: string,
config: BooleanValidatorConfig
) {
switch (config.handleNull) {
case 'error':
record.addError(
field,
config.customErrorMessages?.nullValue ||
'Value cannot be null or undefined'
)
break
case 'false':
record.set(field, false)
break
case 'true':
record.set(field, true)
break
case 'skip':
default:
// Do nothing, leave the field as is
break
}
}
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 redundant case and improve error message.

  1. As noted in a previous review, the case 'skip': is redundant and can be removed since it's handled by the default case.

  2. Consider making the error message more descriptive. For example:

record.addError(
  field,
  config.customErrorMessages?.nullValue ||
    `Field '${field}' cannot be null or undefined`
)

This change provides more context about which field caused the error.

Apply this diff to implement these suggestions:

 function handleNullValue(
   record: FlatfileRecord,
   field: string,
   config: BooleanValidatorConfig
 ) {
   switch (config.handleNull) {
     case 'error':
       record.addError(
         field,
         config.customErrorMessages?.nullValue ||
-          'Value cannot be null or undefined'
+          `Field '${field}' cannot be null or undefined`
       )
       break
     case 'false':
       record.set(field, false)
       break
     case 'true':
       record.set(field, true)
       break
-    case 'skip':
     default:
       // Do nothing, leave the field as is
       break
   }
 }
🧰 Tools
🪛 Biome

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

Comment on lines 55 to 68
function validateStrictBoolean(
record: FlatfileRecord,
field: string,
value: any,
config: BooleanValidatorConfig
) {
if ( value === true || value === false) {
record.set(field, value)
} else if (config.convertNonBoolean) {
record.set(field, Boolean(value))
} else {
handleInvalidValue(record, field, config)
}
}
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 handling string boolean values in strict mode.

The validateStrictBoolean function correctly handles boolean values and non-boolean conversion. However, in some cases, users might expect string 'true' or 'false' to be treated as valid boolean values even in strict mode. Consider adding an optional configuration flag to handle this case:

interface BooleanValidatorConfig {
  // ... existing fields
  strictStringBooleans?: boolean
}

function validateStrictBoolean(
  record: FlatfileRecord,
  field: string,
  value: any,
  config: BooleanValidatorConfig
) {
  if (value === true || value === false) {
    record.set(field, value)
  } else if (config.strictStringBooleans && (value === 'true' || value === 'false')) {
    record.set(field, value === 'true')
  } else if (config.convertNonBoolean) {
    record.set(field, Boolean(value))
  } else {
    handleInvalidValue(record, field, config)
  }
}

This enhancement would provide more flexibility while maintaining the strict validation when needed.

Comment on lines 70 to 97
function validateTruthyBoolean(
record: FlatfileRecord,
field: string,
value: any,
config: BooleanValidatorConfig
) {
const defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping

let normalizedValue = value
if (typeof value === 'string' && !config.caseSensitive) {
normalizedValue = value.toLowerCase()
}

if (normalizedValue === true || normalizedValue === false) {
record.set(field, normalizedValue)
} else if (mapping.hasOwnProperty(normalizedValue)) {
record.set(field, mapping[normalizedValue])
} else if (typeof normalizedValue === 'number') {
record.set(field, Boolean(normalizedValue))
} else if (config.convertNonBoolean) {
record.set(field, Boolean(value))
} else {
handleInvalidValue(record, field, config)
}
}
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 validateTruthyBoolean function logic and safety.

  1. Remove the unnecessary condition for boolean check:
    The normalizedValue === true || normalizedValue === false check is redundant because normalizedValue is always a string at this point.

  2. Use Object.hasOwn() instead of hasOwnProperty:
    This change improves safety against prototype pollution.

  3. Consider using a type guard for the value parameter to improve type safety.

Apply this diff to implement these suggestions:

 function validateTruthyBoolean(
   record: FlatfileRecord,
   field: string,
-  value: any,
+  value: unknown,
   config: BooleanValidatorConfig
 ) {
   const defaultMapping = config.language
     ? languageMappings[config.language]
     : languageMappings.en
   const mapping = config.customMapping || defaultMapping

   let normalizedValue = value
   if (typeof value === 'string' && !config.caseSensitive) {
     normalizedValue = value.toLowerCase()
   }

-  if (normalizedValue === true || normalizedValue === false) {
+  if (typeof value === 'boolean') {
     record.set(field, value)
-  } else if (mapping.hasOwnProperty(normalizedValue)) {
+  } else if (typeof normalizedValue === 'string' && Object.hasOwn(mapping, normalizedValue)) {
     record.set(field, mapping[normalizedValue])
   } else if (typeof normalizedValue === 'number') {
     record.set(field, Boolean(normalizedValue))
   } else if (config.convertNonBoolean) {
     record.set(field, Boolean(value))
   } else {
     handleInvalidValue(record, field, config)
   }
 }

These changes improve the function's type safety and robustness.

📝 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
function validateTruthyBoolean(
record: FlatfileRecord,
field: string,
value: any,
config: BooleanValidatorConfig
) {
const defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping
let normalizedValue = value
if (typeof value === 'string' && !config.caseSensitive) {
normalizedValue = value.toLowerCase()
}
if (normalizedValue === true || normalizedValue === false) {
record.set(field, normalizedValue)
} else if (mapping.hasOwnProperty(normalizedValue)) {
record.set(field, mapping[normalizedValue])
} else if (typeof normalizedValue === 'number') {
record.set(field, Boolean(normalizedValue))
} else if (config.convertNonBoolean) {
record.set(field, Boolean(value))
} else {
handleInvalidValue(record, field, config)
}
}
function validateTruthyBoolean(
record: FlatfileRecord,
field: string,
value: unknown,
config: BooleanValidatorConfig
) {
const defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping
let normalizedValue = value
if (typeof value === 'string' && !config.caseSensitive) {
normalizedValue = value.toLowerCase()
}
if (typeof value === 'boolean') {
record.set(field, value)
} else if (typeof normalizedValue === 'string' && Object.hasOwn(mapping, normalizedValue)) {
record.set(field, mapping[normalizedValue])
} else if (typeof normalizedValue === 'number') {
record.set(field, Boolean(normalizedValue))
} else if (config.convertNonBoolean) {
record.set(field, Boolean(value))
} else {
handleInvalidValue(record, field, config)
}
}
🧰 Tools
🪛 Biome

[error] 88-88: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

@@ -0,0 +1,63 @@
# Flatfile Boolean Validator Plugin
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't follow our README structure

@bangarang bangarang force-pushed the feat/BooleanValidator branch from 19498b1 to 8250fcc Compare October 4, 2024 19:22
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: 7

🧹 Outside diff range and nitpick comments (2)
validate/boolean/README.MD (2)

63-63: Consider reorganizing integration information

The information about using the plugin as a RecordHook or an external constraint is valuable. However, it might be more beneficial to users if this information was placed in a dedicated "Integration" or "Usage" section earlier in the README.

Consider creating a new section, such as "Integration Options" or "Usage Methods", and moving this information there. This would make it more visible and easier for users to find when looking for ways to implement the plugin in their projects.


1-63: README provides comprehensive documentation with room for minor improvements

Overall, this README offers clear and detailed documentation for the Flatfile Boolean Validator Plugin. It covers essential aspects such as features, installation, usage, configuration, and behavior. The content is informative and well-presented.

To further enhance the document:

  1. Consider adjusting the overall structure to align with project guidelines, including adding sections like a table of contents, prerequisites, and contributing information.
  2. Reorganize the integration information into a dedicated section for better visibility.
  3. Review the document for any remaining inconsistencies in terminology or formatting.

These minor improvements will make the README even more user-friendly and aligned with project standards.

Would you like assistance in implementing any of these suggested improvements?

🧰 Tools
🪛 LanguageTool

[uncategorized] ~44-~44: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 19498b1 and 8250fcc.

⛔ Files ignored due to path filters (2)
  • validate/boolean/metadata.json is excluded by !**/*.json
  • validate/boolean/package.json is excluded by !**/*.json
📒 Files selected for processing (5)
  • validate/boolean/README.MD (1 hunks)
  • validate/boolean/rollup.config.mjs (1 hunks)
  • validate/boolean/src/index.ts (1 hunks)
  • validate/boolean/src/validate.boolean.plugin.e2e.spec.ts (1 hunks)
  • validate/boolean/src/validate.boolean.plugin.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • validate/boolean/rollup.config.mjs
  • validate/boolean/src/index.ts
🧰 Additional context used
🪛 LanguageTool
validate/boolean/README.MD

[uncategorized] ~44-~44: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 Biome
validate/boolean/src/validate.boolean.plugin.ts

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)


[error] 87-87: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

🔇 Additional comments (8)
validate/boolean/README.MD (6)

5-15: Features section looks good

The Features section provides a comprehensive and clear list of the plugin's capabilities. It effectively communicates the key functionalities to potential users.


16-23: Installation section is clear and concise

The Installation section provides a straightforward npm command for installing the plugin. This is sufficient for most users familiar with npm.


24-38: Example Usage section is clear and consistent

The Example Usage section provides a clear demonstration of how to use the plugin. The code snippet is easy to understand and shows key configuration options.

Note: The previously mentioned inconsistency in function naming (validateBoolean vs BooleanValidator) has been resolved. The example now consistently uses validateBoolean.


40-52: Configuration section is well-structured

The Configuration section clearly lists all the properties of the configuration object. Each property is briefly described, providing users with a good understanding of the available options.

Note: The previously mentioned formatting issue (extra blank line before the list) has been resolved. The list now follows immediately after the introductory sentence, improving the Markdown formatting.

The LanguageTool hint about a loose punctuation mark on line 44 appears to be a false positive. The Markdown formatting for the list is correct and consistent.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~44-~44: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)


54-61: Behavior section provides clear operational details

The Behavior section effectively outlines the key operational aspects of the plugin. It covers important topics such as validation types, null handling, and error management, giving users a good understanding of how the plugin will function in different scenarios.


1-4: ⚠️ Potential issue

Adjust README structure to follow project guidelines

The current structure of the README doesn't seem to follow the project's standard README format. Consider adjusting the structure to align with the project's guidelines.

Suggestions for improvement:

  1. Add a badges section (if applicable) after the title.
  2. Include a table of contents for easy navigation.
  3. Consider adding sections like "Prerequisites" and "Contributing" if they're part of the standard structure.

Would you like assistance in restructuring the README to match the project's standard format?

validate/boolean/src/validate.boolean.plugin.e2e.spec.ts (2)

141-141: Verify the type of 'handleNull' option

The handleNull option is set as a string 'false'. Ensure that the validateBoolean function expects a string value for handleNull. If it expects a boolean, you should pass false without quotes.

Confirm whether handleNull should be a string or boolean. If it should be a boolean, update the code:

-          handleNull: 'false',
+          handleNull: false,

172-172: Check the expected value for invalid inputs

The test asserts that the value remains 'invalid' when an invalid input is provided. Verify whether the validateBoolean function should transform invalid inputs or leave them unchanged.

If invalid inputs should be transformed or set to a default value, adjust the expectation accordingly.

Comment on lines +75 to +78
const defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping
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 unsupported languages in 'languageMappings'

If config.language is set to a language code that does not exist in languageMappings, defaultMapping will be undefined, leading to errors when accessing properties on mapping. To prevent this, ensure that defaultMapping falls back to a supported language or handle the unsupported language appropriately.

Apply this diff to fix the issue:

     const defaultMapping = config.language
-      ? languageMappings[config.language]
+      ? languageMappings[config.language] || languageMappings.en
       : languageMappings.en

This change ensures that if the specified language is not supported, the default English mapping is used instead.

Alternatively, you could add an error handling mechanism for unsupported languages:

     const defaultMapping = config.language
       ? languageMappings[config.language]
       : languageMappings.en

+    if (!defaultMapping) {
+      record.addError(
+        field,
+        `Unsupported language specified: ${config.language}`
+      )
+      return
+    }

     const mapping = config.customMapping || defaultMapping

This approach notifies the user that an unsupported language has been specified and prevents further processing with an undefined mapping.

📝 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 defaultMapping = config.language
? languageMappings[config.language]
: languageMappings.en
const mapping = config.customMapping || defaultMapping
const defaultMapping = config.language
? languageMappings[config.language] || languageMappings.en
: languageMappings.en
const mapping = config.customMapping || defaultMapping


if (normalizedValue === true || normalizedValue === false) {
record.set(field, normalizedValue)
} else if (mapping.hasOwnProperty(normalizedValue)) {
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

Use 'Object.hasOwn()' instead of 'hasOwnProperty'

To prevent potential issues when hasOwnProperty is overridden or not present in the object's prototype chain, it's recommended to use Object.hasOwn() when checking for property existence.

Apply this diff to fix the issue:

     if (normalizedValue === true || normalizedValue === false) {
       record.set(field, normalizedValue)
-    } else if (mapping.hasOwnProperty(normalizedValue)) {
+    } else if (Object.hasOwn(mapping, normalizedValue)) {
       record.set(field, mapping[normalizedValue])
📝 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
} else if (mapping.hasOwnProperty(normalizedValue)) {
} else if (Object.hasOwn(mapping, normalizedValue)) {
🧰 Tools
🪛 Biome

[error] 87-87: Do not access Object.prototype method 'hasOwnProperty' from target object.

It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.

(lint/suspicious/noPrototypeBuiltins)

Comment on lines +47 to +48
case 'skip':
default:
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 unnecessary 'case' clause in switch statement

The 'case 'skip':' clause is unnecessary because the default case will handle any unmatched cases, making 'case 'skip':' redundant. Removing it will simplify the switch statement and clean up the code.

Apply this diff to fix the issue:

       record.set(field, true)
       break
-    case 'skip':
     default:
       // Do nothing, leave the field as is
       break
📝 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
case 'skip':
default:
default:
// Do nothing, leave the field as is
break
🧰 Tools
🪛 Biome

[error] 47-47: Useless case clause.

because the default clause is present:

Unsafe fix: Remove the useless case.

(lint/complexity/noUselessSwitchCase)

{ key: 'agreeToTerms', type: 'boolean' },
{ key: 'optIn', type: 'boolean' },
])
sheetId = workbook.sheets![0].id
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 non-null assertion operator

Using the non-null assertion operator ! on workbook.sheets![0].id can lead to runtime errors if sheets is null or undefined. Consider adding a null check to ensure safety.

Update the code to include a null check:

if (workbook.sheets && workbook.sheets.length > 0) {
  sheetId = workbook.sheets[0].id
} else {
  throw new Error('No sheets found in the workbook')
}

Comment on lines +18 to +174
expect(records[3].values['isActive'].value).toBe(false)
expect(records[4].values['isActive'].messages[0].message).toContain('Invalid boolean value')
expect(records[5].values['isActive'].messages[0].message).toContain('Invalid boolean value')

})

it('validates truthy boolean values', async () => {
listener.use(
validateBoolean({
fields: ['hasSubscription'],
validationType: 'truthy',
})
)

await createRecords(sheetId, [
{ hasSubscription: true },
{ hasSubscription: false },
{ hasSubscription: 'true' },
{ hasSubscription: 'false' },
{ hasSubscription: 1 },
{ hasSubscription: 0 },
{ hasSubscription: 'yes' },
{ hasSubscription: 'no' },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['hasSubscription'].value).toBeTruthy()
expect(records[1].values['hasSubscription'].value).toBeFalsy()
expect(records[2].values['hasSubscription'].value).toBeTruthy()
expect(records[3].values['hasSubscription'].value).toBeFalsy()
expect(records[4].values['hasSubscription'].value).toBeTruthy()
expect(records[5].values['hasSubscription'].value).toBeFalsy()
expect(records[6].values['hasSubscription'].value).toBeTruthy()
expect(records[7].values['hasSubscription'].value).toBeFalsy()
})

it('handles custom mapping', async () => {
listener.use(
validateBoolean({
fields: ['agreeToTerms'],
validationType: 'truthy',
customMapping: { 'agreed': true, 'disagreed': false },
})
)

await createRecords(sheetId, [
{ agreeToTerms: 'agreed' },
{ agreeToTerms: 'disagreed' },
{ agreeToTerms: 'yes' },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['agreeToTerms'].value).toBeTruthy()
expect(records[1].values['agreeToTerms'].value).toBeFalsy()
expect(records[2].values['agreeToTerms'].value).toBeTruthy()
})

it('handles null values', async () => {
listener.use(
validateBoolean({
fields: ['optIn'],
validationType: 'truthy',
handleNull: 'false',
})
)

await createRecords(sheetId, [
{ optIn: null },
{ optIn: undefined },
{ optIn: '' },
])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['optIn'].value).toBe(false)
expect(records[1].values['optIn'].value).toBe(false)
expect(records[2].values['optIn'].value).toBe(false)
})

it('handles errors', async () => {
listener.use(
validateBoolean({
fields: ['isActive'],
validationType: 'truthy',
})
)

await createRecords(sheetId, [{ isActive: 'invalid' }])
await listener.waitFor('commit:created')

const records = await getRecords(sheetId)

expect(records[0].values['isActive'].value).toBe('invalid')
expect(records[0].values['isActive'].messages[0].message).toContain('Invalid boolean 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

Refactor repetitive test setup code

There is repetition in setting up the listener and creating records across the test cases. Consider abstracting common patterns into helper functions to adhere to the DRY (Don't Repeat Yourself) principle.

Create helper functions to streamline your tests:

function applyValidation(options) {
  listener.use(validateBoolean(options))
}

async function createAndFetchRecords(data) {
  await createRecords(sheetId, data)
  await listener.waitFor('commit:created')
  return await getRecords(sheetId)
}

You can then refactor your tests:

it('validates strict boolean values', async () => {
  applyValidation({
    fields: ['isActive'],
    validationType: 'strict',
  })

  const records = await createAndFetchRecords([
    { isActive: true },
    // ...other test data
  ])

  // assertions...
})

Comment on lines +42 to +43
logSpy.mockReset()
logErrorSpy.mockReset()
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

Eliminate unnecessary mock resets

Since the console spies are not used, resetting them is unnecessary. You can remove these lines to simplify the afterEach hook.

Apply this diff to remove the unnecessary resets:

-    logSpy.mockReset()
-    logErrorSpy.mockReset()
📝 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
logSpy.mockReset()
logErrorSpy.mockReset()

Comment on lines +18 to +19
const logSpy = jest.spyOn(global.console, 'log')
const logErrorSpy = jest.spyOn(global.console, 'error')
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 unused console spies

The console spies logSpy and logErrorSpy are declared but not utilized in the tests. Consider removing them to clean up the code.

Apply this diff to remove the unused declarations:

-  const logSpy = jest.spyOn(global.console, 'log')
-  const logErrorSpy = jest.spyOn(global.console, 'error')
📝 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 logSpy = jest.spyOn(global.console, 'log')
const logErrorSpy = jest.spyOn(global.console, 'error')

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: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 8250fcc and 9af220e.

📒 Files selected for processing (1)
  • validate/boolean/README.MD (1 hunks)
🧰 Additional context used
🪛 LanguageTool
validate/boolean/README.MD

[uncategorized] ~50-~50: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)

🔇 Additional comments (6)
validate/boolean/README.MD (6)

1-10: LGTM: Clear and concise header and infocard

The header and infocard provide a clear introduction to the plugin, including its name, a brief description, and the correct event type. This section effectively sets the context for the rest of the documentation.


11-20: LGTM: Comprehensive features list

The Features section provides a clear and concise list of the plugin's capabilities. This helps users quickly understand what the plugin offers and how it can be useful in their projects.


22-28: LGTM: Clear installation instructions

The Installation section provides clear and concise instructions for installing the plugin using npm. The use of a code block for the command enhances readability and allows for easy copy-pasting.


30-44: LGTM: Clear and consistent example usage

The Example Usage section provides a clear demonstration of how to use the plugin with various configuration options. The use of validateBoolean is consistent with the function name mentioned in the AI-generated summary, resolving the previous naming inconsistency issue.


60-69: LGTM: Clear and comprehensive behavior description

The Behavior section provides a well-organized and easy-to-understand description of the plugin's functionality in various scenarios. This information helps users understand how the plugin will handle different inputs and configurations.


46-58: ⚠️ Potential issue

Fix formatting and function naming inconsistency

  1. Remove the extra blank line at line 49 to ensure proper Markdown list formatting.
  2. Replace BooleanValidator with validateBoolean to maintain consistency with the example usage and the exported function name.

Apply this diff to fix the issues:

 ## Configuration
 
-The `BooleanValidator` function accepts a configuration object with the following properties:
+The `validateBoolean` function accepts a configuration object with the following properties:
 
 - `fields`: An array of field names to validate
 - `validationType`: 'strict' or 'truthy'
🧰 Tools
🪛 LanguageTool

[uncategorized] ~50-~50: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)

Comment on lines +1 to +69
<!-- START_INFOCARD -->
# @flatfile/plugin-validate-boolean

The `@flatfile/plugin-validate-boolean` plugin provides comprehensive boolean validation capabilities, offering flexible configuration options to handle various boolean representations across different languages and use cases.

**Event Type:**
`listener.on('commit:created')`

<!-- END_INFOCARD -->

## Features

- Supports both strict and truthy boolean validation
- Multi-language support (English, Spanish, French, German)
- Custom mapping for boolean values
- Case-sensitive and case-insensitive options
- Configurable null value handling
- Option to convert non-boolean values
- Custom error messages
- Default value setting

## Installation

To install the plugin, run the following command:

```bash
npm install @flatfile/plugin-validate-boolean
```

## Example Usage

```javascript
import { validateBoolean } from '@flatfile/plugin-validate-boolean';

const booleanValidator = validateBoolean({
fields: ['isActive', 'hasSubscription'],
validationType: 'truthy',
language: 'en',
handleNull: 'false',
convertNonBoolean: true
});

listener.use(booleanValidator);
```

## Configuration

The `BooleanValidator` function accepts a configuration object with the following properties:

- `fields`: An array of field names to validate
- `validationType`: 'strict' or 'truthy'
- `customMapping`: A custom mapping of string values to boolean
- `caseSensitive`: Whether the validation should be case-sensitive
- `handleNull`: How to handle null values ('error', 'false', 'true', or 'skip')
- `convertNonBoolean`: Whether to convert non-boolean values to boolean
- `language`: The language for predefined mappings ('en', 'es', 'fr', 'de')
- `customErrorMessages`: Custom error messages for different scenarios
- `defaultValue`: A default value to use for invalid inputs

## Behavior

1. **Strict Validation**: Only accepts 'true', 'false', true, or false as valid inputs.
2. **Truthy Validation**: Accepts various representations of true/false, including language-specific terms.
3. **Null Handling**: Configurable behavior for null or undefined values.
4. **Non-Boolean Conversion**: Option to convert non-boolean values to boolean.
5. **Error Handling**: Adds errors or info messages to the record for invalid inputs.
6. **Default Value**: Option to set a default value for invalid inputs instead of raising an error.

The plugin can be used either as a RecordHook or as an external constraint, providing flexibility in integration with your Flatfile setup.
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

Suggestion: Restructure README to follow standard format

While the content of the README is comprehensive and well-organized, it doesn't follow the standard README structure used in the project. Consider restructuring the document to align with the project's conventions, which might include sections like:

  1. Introduction
  2. Installation
  3. Usage
  4. API Reference
  5. Configuration
  6. Examples
  7. Contributing
  8. License

This restructuring will improve consistency across the project's documentation and make it easier for users to find information.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~50-~50: Loose punctuation mark.
Context: ...th the following properties: - fields: An array of field names to validate - `...

(UNLIKELY_OPENING_PUNCTUATION)

@bangarang bangarang merged commit 492b91c into main Oct 5, 2024
44 checks passed
@bangarang bangarang deleted the feat/BooleanValidator branch October 5, 2024 18:23
@coderabbitai coderabbitai bot mentioned this pull request Oct 30, 2024
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.

2 participants