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

Switch test #786

Open
wants to merge 63 commits into
base: main
Choose a base branch
from
Open

Switch test #786

wants to merge 63 commits into from

Conversation

jindaljyoti
Copy link
Contributor

@jindaljyoti jindaljyoti commented Jan 16, 2025

Refactor and add test cases for switch component

Summary by CodeRabbit

  • Documentation

    • Updated documentation for Switch component code usage
    • Enhanced examples in storybook with controlled and uncontrolled usage
  • Tests

    • Added comprehensive test suite for Switch component
    • Implemented 4 new tests covering rendering, state management, and interaction scenarios
  • Chores

    • Minor syntax and import statement refinements in component and documentation files

Copy link
Contributor

coderabbitai bot commented Jan 16, 2025

Walkthrough

This pull request introduces updates to the Switch component, focusing on documentation improvements, syntax refinements, and the addition of a new test suite. Changes include adjustments to import statements, restructuring of the SwitchExample functional component for better readability, and the implementation of a comprehensive set of unit tests to validate the Switch component's functionality. The modifications span multiple files and enhance code consistency and test coverage.

Changes

File Change Summary
docs/app/docs/components/switch/docs/codeUsage.js Updated import statement with semicolon, restructured SwitchExample component to use explicit function definition
docs/app/docs/components/switch/page.js Updated import statement to use single quotes, removed commented-out line
src/components/ui/Switch/Switch.tsx Changed checked and color properties in SwitchProps to optional, modified handleChecked function syntax, updated input element to use controlled state
src/components/ui/Switch/tests/Switch.test.tsx Added comprehensive test suite with 4 new tests covering rendering, state management, and interaction
src/components/ui/Switch/stories/Switch.stories.js Added state management in stories, modified layout class, introduced new controlled and uncontrolled stories

Possibly related issues

  • rad-ui/ui#726: This issue requests adding tests to the Switch Component, which is directly addressed by the new test suite in this PR.

Suggested labels

automerge

Suggested reviewers

  • kotAPI

Poem

🐰 A Switch so bright, with tests in sight,
Clicking left and right with delight!
Semicolons dance, code becomes clear,
Our component's journey brings tech cheer!
Hop, hop, hooray for Switch today! 🔧

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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: 6

🧹 Nitpick comments (4)
docs/app/docs/components/switch/page.js (1)

20-20: Remove commented-out code.

Remove the commented-out UnderConstruction component as it's no longer needed.

-            {/* <Documentation.UnderConstruction/> */}
src/components/ui/Switch/tests/Switch.test.tsx (1)

5-30: Add more test coverage for Switch component.

The current test suite covers basic functionality well, but consider adding these test cases:

  1. Test different variants ('classic', 'surface', 'solid')
  2. Test accessibility attributes (role, aria-label)
  3. Test keyboard interaction (Space/Enter keys)
  4. Test disabled state

Here's an example of additional test cases:

test('renders with different variants', () => {
    const variants = ['classic', 'surface', 'solid'];
    variants.forEach(variant => {
        const { rerender } = render(<Switch checked={true} onChange={() => {}} variant={variant} />);
        const switchElement = screen.getByRole('switch');
        expect(switchElement).toHaveClass(`switch-${variant}`);
        rerender(<></>);
    });
});

test('handles keyboard interaction', () => {
    const handleChange = jest.fn();
    render(<Switch checked={true} onChange={handleChange} />);
    const switchElement = screen.getByRole('switch');
    
    fireEvent.keyDown(switchElement, { key: 'Enter' });
    expect(handleChange).toHaveBeenCalledWith(false);
    
    fireEvent.keyDown(switchElement, { key: ' ' });
    expect(handleChange).toHaveBeenCalledWith(true);
});

test('respects disabled state', () => {
    const handleChange = jest.fn();
    render(<Switch checked={true} onChange={handleChange} disabled />);
    const switchElement = screen.getByRole('switch');
    
    expect(switchElement).toBeDisabled();
    fireEvent.click(switchElement);
    expect(handleChange).not.toHaveBeenCalled();
});
docs/app/docs/components/switch/docs/codeUsage.js (2)

4-19: Enhance example code with error handling and TypeScript.

The example could be improved to demonstrate best practices:

  1. Add TypeScript for better type safety
  2. Include error handling
  3. Show more usage scenarios
-import Switch from "@radui/ui/Switch";
+import { Switch } from "@radui/ui/Switch";
+import { useState } from 'react';
 
-const SwitchExample = () => {
-    const [checked, setChecked] = React.useState(true)
+const SwitchExample: React.FC = () => {
+    const [checked, setChecked] = useState<boolean>(true);
+    const [error, setError] = useState<string | null>(null);
 
-    const handleChange = (state) => {
-          setChecked(state)
+    const handleChange = (state: boolean) => {
+        try {
+            setChecked(state);
+            setError(null);
+        } catch (err) {
+            setError('Failed to update switch state');
+        }
     }
    
     return (    
        <div>
-         <Switch checked={checked} onChange={handleChange} />   
+         <Switch 
+           checked={checked} 
+           onChange={handleChange}
+           aria-label="Toggle switch example"
+         />
+         {error && <p role="alert" className="error">{error}</p>}
        </div>

Line range hint 23-67: Optimize SCSS styles for better maintainability.

Consider these improvements to the styles:

  1. Use more specific transition properties
  2. Add hover and focus states for better accessibility
  3. Consider reducing selector specificity
 .rad-ui-Switch {
     opacity: 0;  
     position: absolute;   
     + button{
         position: relative;
         display: flex;
         align-items: center;
         font-size: 1.2rem;
         color: var(--rad-ui-color-accent-500);
         &::before{
             content: ""; 
             width: 2rem;
             height: 1.05rem;
             background-color: var(--rad-ui-color-accent-500);
             border-radius: 1rem;
             margin-right: .25rem;
-            transition: 200ms ease-in-out;
+            transition: background-color 200ms ease-in-out;
         }
         &::after{
             position: absolute;
             left: .1rem;
             content: ""; 
             width: .9rem;
             height: .9rem;
             background-color: var(--rad-ui-color-accent-50);
             border-radius: 1rem;
-            transition: 200ms ease-in-out;
+            transition: transform 200ms ease-in-out, background-color 200ms ease-in-out;
         }
+        &:hover::before {
+            background-color: var(--rad-ui-color-accent-600);
+        }
+        &:focus-visible {
+            outline: 2px solid var(--rad-ui-color-accent-500);
+            outline-offset: 2px;
+        }
     } 
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91b2343 and ee81a0a.

📒 Files selected for processing (6)
  • docs/app/docs/components/switch/SwitchWrapper.js (1 hunks)
  • docs/app/docs/components/switch/docs/codeUsage.js (1 hunks)
  • docs/app/docs/components/switch/page.js (1 hunks)
  • src/components/ui/Switch/Switch.tsx (1 hunks)
  • src/components/ui/Switch/stories/Switch.stories.js (2 hunks)
  • src/components/ui/Switch/tests/Switch.test.tsx (1 hunks)
🔇 Additional comments (2)
docs/app/docs/components/switch/SwitchWrapper.js (1)

8-12: LGTM! Clean state management implementation.

The state management implementation follows React best practices with a clear state variable and handler function.

docs/app/docs/components/switch/page.js (1)

18-18: LGTM! Clean integration of SwitchWrapper.

The SwitchWrapper component is properly integrated into the documentation.

setIsChecked(state)
}

return <Switch checked={isChecked} onChange={handleChange} readOnly/>
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 redundant readOnly prop.

The readOnly prop contradicts the interactive nature of the component since you're already handling state changes via onChange.

-    return <Switch checked={isChecked} onChange={handleChange} readOnly/> 
+    return <Switch checked={isChecked} onChange={handleChange}/> 
📝 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 <Switch checked={isChecked} onChange={handleChange} readOnly/>
return <Switch checked={isChecked} onChange={handleChange}/>

{variants.map((variant, index) => (
<Switch defaultChecked={args} key={index} variant={variant} onChange={handleChange} {...args} />
<Switch defaultChecked={isChecked} key={index} variant={variant} onChange={handleChange} {...args} />
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 controlled/uncontrolled component pattern mixing.

The Switch is currently mixing controlled and uncontrolled patterns by using both defaultChecked and onChange. This can lead to unexpected behavior. Use either controlled (checked) or uncontrolled (defaultChecked) pattern, but not both.

-            <Switch defaultChecked={isChecked} key={index} variant={variant} onChange={handleChange} {...args} />
+            <Switch checked={isChecked} key={index} variant={variant} onChange={handleChange} {...args} />
📝 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
<Switch defaultChecked={isChecked} key={index} variant={variant} onChange={handleChange} {...args} />
<Switch checked={isChecked} key={index} variant={variant} onChange={handleChange} {...args} />

Comment on lines 29 to 36
<div>
<input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
onChange={() => {}}
data-state={isChecked ? 'on' : 'off'}
aria-checked={isChecked}
/>
<button type="button" onClick={handleChecked} role="switch"></button>
</>
</div>
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 accessibility issues and improve component structure.

  1. Having both an input and a button with switch functionality violates accessibility guidelines.
  2. The wrapper div should have proper styling context and ARIA attributes.
-        <div>
+        <div className={className} role="presentation">
             <input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
                onChange={handleChange}
                data-state={isChecked ? 'on' : 'off'}
                aria-checked={isChecked}
             />
-            <button type="button" onClick={handleChecked} role="switch"></button>
         </div>
📝 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
<div>
<input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
onChange={() => {}}
data-state={isChecked ? 'on' : 'off'}
aria-checked={isChecked}
/>
<button type="button" onClick={handleChecked} role="switch"></button>
</>
</div>
<div className={className} role="presentation">
<input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
onChange={handleChange}
data-state={isChecked ? 'on' : 'off'}
aria-checked={isChecked}
/>
</div>

Comment on lines 17 to 20
const Switch = ({ children, customRootClass = '', className = '', defaultChecked=true, checked=true, onChange, ...props }:SwitchProps) => {

const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);

const [isChecked, setIsChecked] = useState(checked || defaultChecked);

const [isChecked, setIsChecked] = useState(checked);
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 default values for boolean props and simplify state management.

  1. Default values for checked and defaultChecked should be removed as they could mask issues.
  2. The defaultChecked prop is unused in state initialization.
-const Switch = ({ children, customRootClass = '', className = '', defaultChecked=true, checked=true, onChange, ...props }:SwitchProps) => {
+const Switch = ({ children, customRootClass = '', className = '', checked, onChange, ...props }:SwitchProps) => {
📝 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 Switch = ({ children, customRootClass = '', className = '', defaultChecked=true, checked=true, onChange, ...props }:SwitchProps) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const [isChecked, setIsChecked] = useState(checked || defaultChecked);
const [isChecked, setIsChecked] = useState(checked);
const Switch = ({ children, customRootClass = '', className = '', checked, onChange, ...props }:SwitchProps) => {
const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
const [isChecked, setIsChecked] = useState(checked);

Comment on lines 22 to 34
const handleChecked = () => {
const updatedState = !isChecked;
setIsChecked(updatedState);
onChange(updatedState);
};

return (
<>
<input type='checkbox' className={clsx(rootClass)} {...props} checked= {isChecked}/>
<div>
<input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
onChange={() => {}}
data-state={isChecked ? 'on' : 'off'}
aria-checked={isChecked}
/>
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 controlled component implementation and event handling.

  1. The empty onChange handler violates React's controlled component pattern.
  2. Having both button and input elements for the same action is redundant.
-    const handleChecked = () => {
-        const updatedState = !isChecked;
-        setIsChecked(updatedState);
-        onChange(updatedState);
-    };
+    const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+        setIsChecked(event.target.checked);
+        onChange(event.target.checked);
+    };

     return (
         <div>
             <input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
-               onChange={() => {}}
+               onChange={handleChange}
                data-state={isChecked ? 'on' : 'off'}
                aria-checked={isChecked}
             />
📝 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 handleChecked = () => {
const updatedState = !isChecked;
setIsChecked(updatedState);
onChange(updatedState);
};
return (
<>
<input type='checkbox' className={clsx(rootClass)} {...props} checked= {isChecked}/>
<div>
<input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
onChange={() => {}}
data-state={isChecked ? 'on' : 'off'}
aria-checked={isChecked}
/>
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setIsChecked(event.target.checked);
onChange(event.target.checked);
};
return (
<div>
<input type='checkbox' className={clsx(rootClass)} {...props} checked={isChecked}
onChange={handleChange}
data-state={isChecked ? 'on' : 'off'}
aria-checked={isChecked}
/>

Comment on lines 8 to 14
defaultChecked?: boolean;
checked: boolean;
color: string;
children?: React.ReactNode;
className?: string;
customRootClass?: string;
onChange : (isChecked:boolean) => void;
props?: any;
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 removing defaultChecked prop to avoid controlled/uncontrolled component confusion.

The component accepts both defaultChecked and checked props, which can lead to confusion about whether it's a controlled or uncontrolled component. Since checked is required and the component uses internal state, defaultChecked appears redundant.

export type SwitchProps = {
-    defaultChecked?: boolean;
    checked: boolean;
    children?: React.ReactNode;
    className?: string;
    customRootClass?: string;
    onChange : (isChecked:boolean) => void;
    props?: any;
}
📝 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
defaultChecked?: boolean;
checked: boolean;
color: string;
children?: React.ReactNode;
className?: string;
customRootClass?: string;
onChange : (isChecked:boolean) => void;
props?: any;
checked: boolean;
children?: React.ReactNode;
className?: string;
customRootClass?: string;
onChange : (isChecked:boolean) => void;
props?: any;

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

♻️ Duplicate comments (1)
src/components/ui/Switch/Switch.tsx (1)

28-35: ⚠️ Potential issue

Fix accessibility issues and component structure.

The current implementation has several issues:

  1. Having both an input and a button with switch functionality violates accessibility guidelines
  2. The wrapper div lacks proper styling context and ARIA attributes
  3. The empty onChange handler violates React patterns

Apply these changes:

-        <div>
+        <div className={className} role="presentation">
             <input
                 type='checkbox'
                 className={clsx(rootClass)}
                 {...props}
-                checked={isChecked}
-                onChange={() => {}}
+                checked={checked}
+                onChange={handleChange}
                 data-state={checked ? 'on' : 'off'}
                 aria-checked={checked}
             />
-            <button type="button" onClick={handleChecked} role="switch"></button>
         </div>
🧹 Nitpick comments (1)
src/components/ui/Switch/Switch.tsx (1)

16-16: Improve type safety by removing generic props.

The component accepts arbitrary props through props?: any which reduces type safety. Consider explicitly defining all expected HTML input attributes.

-export type SwitchProps = {
+export type SwitchProps = {
+    id?: string;
+    name?: string;
+    disabled?: boolean;
     checked: boolean;
     children?: React.ReactNode;
     className?: string;
     customRootClass?: string;
     onChange: (isChecked: boolean) => void;
-    props?: any;
-}
+} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'checked'>;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee81a0a and b058906.

📒 Files selected for processing (4)
  • docs/app/docs/components/switch/SwitchWrapper.js (1 hunks)
  • docs/app/docs/components/switch/page.js (1 hunks)
  • src/components/ui/Switch/Switch.tsx (1 hunks)
  • src/components/ui/Switch/stories/Switch.stories.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/app/docs/components/switch/page.js
  • docs/app/docs/components/switch/SwitchWrapper.js
  • src/components/ui/Switch/stories/Switch.stories.js
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (1)
src/components/ui/Switch/Switch.tsx (1)

19-24: ⚠️ Potential issue

Fix controlled component implementation.

The component mixes controlled and uncontrolled patterns which can lead to state synchronization issues. The component should either:

  1. Be fully controlled by parent (recommended)
  2. Or be fully uncontrolled using defaultChecked

For a controlled component implementation:

-const [isChecked, setIsChecked] = useState(checked);
-
-const handleChecked = () => {
-    const updatedState = !isChecked;
-    setIsChecked(updatedState);
-    onChange(updatedState)
-};

+const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+    onChange(event.target.checked);
+};

Likely invalid or redundant comment.

children?: React.ReactNode;
className?: string;
customRootClass?: string;
onChange : (isChecked:boolean) => void;
props?: any;
}

const Switch = ({ children, customRootClass = '', className = '', color = '', defaultChecked, checked, onChange, ...props }:SwitchProps) => {
const Switch = ({ children, customRootClass = '', className = '', checked, onChange, ...props }:SwitchProps) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

why did you remove the defaultChecked?

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

🧹 Nitpick comments (3)
src/components/ui/Switch/Switch.tsx (2)

8-14: Improve type safety and documentation for props.

The component accepts both controlled (checked) and uncontrolled (defaultChecked) modes, but the types could be more explicit:

 export type SwitchProps = {
-    defaultChecked?: boolean;
-    checked: boolean;
+    defaultChecked?: boolean;
+    checked?: boolean;
     children?: React.ReactNode;
     className?: string;
     customRootClass?: string;
-    onChange : (isChecked:boolean) => void;
+    onChange?: (isChecked: boolean) => void;
     props?: any;
 }

Also consider:

  1. Making onChange optional for uncontrolled mode
  2. Adding JSDoc comments to document the controlled/uncontrolled usage
  3. Removing the generic props?: any in favor of specific HTML input attributes

17-19: Fix code formatting.

Remove unnecessary blank lines to comply with ESLint rules:

 const Switch = ({ children, customRootClass = '', className = '', defaultChecked, checked, onChange, ...props }:SwitchProps) => {
-    
     const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
🧰 Tools
🪛 eslint

[error] 17-19: Block must not be padded by blank lines.

(padded-blocks)

src/components/ui/Switch/tests/Switch.test.tsx (1)

5-11: Enhance test coverage and organization.

While the basic test cases are good, consider adding:

  1. Error cases (e.g., invalid props)
  2. Keyboard interaction tests
  3. Custom class and styling tests
  4. Snapshot tests

Also, organize tests better using nested describes:

 describe('Switch Component', () => {
-    test('renders correctly', () => {
+    describe('rendering', () => {
+        test('renders correctly', () => {
+            render(<Switch checked={true} onChange={() => {}}/>)
+            const inputElement = screen.getByRole('checkbox')
+            expect(inputElement).toBeInTheDocument();
+            expect(inputElement).toHaveAttribute('role', 'switch');
+        })
+    })
🧰 Tools
🪛 eslint

[error] 5-7: Block must not be padded by blank lines.

(padded-blocks)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b058906 and 11391d1.

📒 Files selected for processing (3)
  • src/components/ui/Switch/Switch.tsx (1 hunks)
  • src/components/ui/Switch/stories/Switch.stories.js (2 hunks)
  • src/components/ui/Switch/tests/Switch.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ui/Switch/stories/Switch.stories.js
🧰 Additional context used
🪛 eslint
src/components/ui/Switch/tests/Switch.test.tsx

[error] 5-7: Block must not be padded by blank lines.

(padded-blocks)


[error] 47-49: Block must not be padded by blank lines.

(padded-blocks)


[error] 49-49: Newline required at end of file but not found.

(eol-last)

src/components/ui/Switch/Switch.tsx

[error] 17-19: Block must not be padded by blank lines.

(padded-blocks)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build
🔇 Additional comments (3)
src/components/ui/Switch/Switch.tsx (3)

22-26: ⚠️ Potential issue

Fix event handling and controlled component behavior.

The current implementation has several issues:

  1. Empty onChange handler on input
  2. Duplicate click handlers
  3. Doesn't properly handle controlled mode
-    const handleChecked = () => {
-        const updatedState = !isChecked;
-        setIsChecked(updatedState);
-        onChange(updatedState)
-    };
+    const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+        if (checked === undefined) {
+            // Uncontrolled mode
+            setIsChecked(event.target.checked);
+        }
+        onChange?.(event.target.checked);
+    };

Likely invalid or redundant comment.


17-21: ⚠️ Potential issue

Fix controlled/uncontrolled component implementation.

The current state management doesn't properly handle controlled vs uncontrolled modes:

-const Switch = ({ children, customRootClass = '', className = '', defaultChecked, checked, onChange, ...props }:SwitchProps) => {
+const Switch = ({ children, customRootClass = '', className = '', defaultChecked = false, checked, onChange, ...props }:SwitchProps) => {
     const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME);
-    const [isChecked, setIsChecked] = useState(checked || defaultChecked);
+    const [isChecked, setIsChecked] = useState(defaultChecked);
+    
+    // In controlled mode, sync with external checked prop
+    React.useEffect(() => {
+        if (checked !== undefined) {
+            setIsChecked(checked);
+        }
+    }, [checked]);

Likely invalid or redundant comment.

🧰 Tools
🪛 eslint

[error] 17-19: Block must not be padded by blank lines.

(padded-blocks)


29-36: ⚠️ Potential issue

Fix accessibility implementation and remove redundant elements.

The current implementation violates accessibility guidelines by having both an input and button for the same action:

-        <div>
+        <div 
+            className={className}
+            role="presentation"
+        >
             <input 
                 type='checkbox' 
                 className={clsx(rootClass)} 
                 {...props} 
                 checked={isChecked}
-                onChange={() => {}}
+                onChange={handleChange}
                 data-state={isChecked ? 'on' : 'off'}
                 aria-checked={isChecked}
+                role="switch"
+                aria-label={props['aria-label'] || 'Toggle switch'}
             />
-            <button type="button" onClick={handleChecked}  role="switch"></button>
         </div>

Likely invalid or redundant comment.

Comment on lines 19 to 29
test('toggles state on click', () => {
const handleChange = jest.fn();
render(<Switch checked={true} onChange={handleChange}/>)
const switchElement = screen.getByRole('switch')

fireEvent.click(switchElement)
expect(handleChange).toHaveBeenCalledWith(false)

fireEvent.click(switchElement)
expect(handleChange).toHaveBeenCalledWith(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

Improve controlled mode test coverage.

The toggle state test needs improvement:

  1. Inconsistent role usage (switch vs checkbox)
  2. Missing important controlled mode scenarios
-    test('toggles state on click', () => {
+    describe('controlled mode', () => {
+        test('calls onChange with correct values', () => {
             const handleChange = jest.fn();
             render(<Switch checked={true} onChange={handleChange}/>)
-            const switchElement = screen.getByRole('switch')
+            const switchElement = screen.getByRole('checkbox')
             
             fireEvent.click(switchElement)
             expect(handleChange).toHaveBeenCalledWith(false)
 
             fireEvent.click(switchElement)
             expect(handleChange).toHaveBeenCalledWith(true)
-    })
+        })
+        
+        test('stays in sync with checked prop', () => {
+            const { rerender } = render(<Switch checked={true} onChange={() => {}}/>)
+            const switchElement = screen.getByRole('checkbox')
+            expect(switchElement).toBeChecked()
+            
+            rerender(<Switch checked={false} onChange={() => {}}/>)
+            expect(switchElement).not.toBeChecked()
+        })
+    })
📝 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('toggles state on click', () => {
const handleChange = jest.fn();
render(<Switch checked={true} onChange={handleChange}/>)
const switchElement = screen.getByRole('switch')
fireEvent.click(switchElement)
expect(handleChange).toHaveBeenCalledWith(false)
fireEvent.click(switchElement)
expect(handleChange).toHaveBeenCalledWith(true)
})
describe('controlled mode', () => {
test('calls onChange with correct values', () => {
const handleChange = jest.fn();
render(<Switch checked={true} onChange={handleChange}/>)
const switchElement = screen.getByRole('checkbox')
fireEvent.click(switchElement)
expect(handleChange).toHaveBeenCalledWith(false)
fireEvent.click(switchElement)
expect(handleChange).toHaveBeenCalledWith(true)
})
test('stays in sync with checked prop', () => {
const { rerender } = render(<Switch checked={true} onChange={() => {}}/>)
const switchElement = screen.getByRole('checkbox')
expect(switchElement).toBeChecked()
rerender(<Switch checked={false} onChange={() => {}}/>)
expect(switchElement).not.toBeChecked()
})
})

Comment on lines 31 to 47
test('toggle state on when clicked', () => {
render(<Switch checked={true} onChange={() => {}} defaultChecked={true} />)
const checkbox = screen.getByRole('checkbox')

expect(checkbox).toBeChecked()

fireEvent.click(checkbox)
expect(checkbox).toBeChecked()
})

test('toggle state off when clicked again', () => {
render(<Switch checked={false} onChange={() => {}} defaultChecked={false} />)
const checkbox = screen.getByRole('checkbox')

fireEvent.click(checkbox)
expect(checkbox).not.toBeChecked()
})
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 uncontrolled mode tests.

The current tests mix controlled and uncontrolled props, which is incorrect:

-    test('toggle state on when clicked', () => {
-        render(<Switch checked={true} onChange={() => {}} defaultChecked={true} />)
+    describe('uncontrolled mode', () => {
+        test('initializes with defaultChecked', () => {
+            render(<Switch defaultChecked={true} />)
+            const checkbox = screen.getByRole('checkbox')
+            expect(checkbox).toBeChecked()
+        })
+        
+        test('toggles state independently', () => {
+            render(<Switch defaultChecked={false} />)
             const checkbox = screen.getByRole('checkbox')
+            expect(checkbox).not.toBeChecked()
 
-            expect(checkbox).toBeChecked()
-
-            fireEvent.click(checkbox)
-            expect(checkbox).toBeChecked()      
-    })
-    
-    test('toggle state off when clicked again', () => {
-        render(<Switch checked={false} onChange={() => {}} defaultChecked={false} />)
-        const checkbox = screen.getByRole('checkbox')
-
-        fireEvent.click(checkbox)
-        expect(checkbox).not.toBeChecked()      
-    })
+            fireEvent.click(checkbox)
+            expect(checkbox).toBeChecked()
+            
+            fireEvent.click(checkbox)
+            expect(checkbox).not.toBeChecked()
+        })
+    })
📝 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('toggle state on when clicked', () => {
render(<Switch checked={true} onChange={() => {}} defaultChecked={true} />)
const checkbox = screen.getByRole('checkbox')
expect(checkbox).toBeChecked()
fireEvent.click(checkbox)
expect(checkbox).toBeChecked()
})
test('toggle state off when clicked again', () => {
render(<Switch checked={false} onChange={() => {}} defaultChecked={false} />)
const checkbox = screen.getByRole('checkbox')
fireEvent.click(checkbox)
expect(checkbox).not.toBeChecked()
})
describe('uncontrolled mode', () => {
test('initializes with defaultChecked', () => {
render(<Switch defaultChecked={true} />)
const checkbox = screen.getByRole('checkbox')
expect(checkbox).toBeChecked()
})
test('toggles state independently', () => {
render(<Switch defaultChecked={false} />)
const checkbox = screen.getByRole('checkbox')
expect(checkbox).not.toBeChecked()
fireEvent.click(checkbox)
expect(checkbox).toBeChecked()
fireEvent.click(checkbox)
expect(checkbox).not.toBeChecked()
})
})

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

♻️ Duplicate comments (2)
src/components/ui/Switch/tests/Switch.test.tsx (2)

13-23: ⚠️ Potential issue

Fix role inconsistency and improve controlled mode coverage.

The test uses 'switch' role while other tests use 'checkbox' role. Additionally, it's missing important controlled mode scenarios.

This issue was previously identified. Please refer to the earlier review comment about improving controlled mode test coverage, which suggests:

  1. Using consistent role ('checkbox')
  2. Adding test for checked prop sync

25-41: ⚠️ Potential issue

Fix incorrect prop combination in uncontrolled mode tests.

The tests incorrectly mix controlled (checked) and uncontrolled (defaultChecked) props, which can lead to unexpected behavior.

This issue was previously identified. Please refer to the earlier review comment about fixing uncontrolled mode tests, which provides a complete solution for properly testing uncontrolled behavior.

🧹 Nitpick comments (1)
src/components/ui/Switch/tests/Switch.test.tsx (1)

5-43: Fix formatting issues.

Please address the following formatting issues:

  1. Remove extra blank lines after the describe block start (line 5-7)
  2. Remove extra blank lines before the describe block end (line 41-43)
  3. Add newline at end of file
 describe('Switch Component', () => {
-
     test('renders correctly', () => {
     // ... tests ...
     })
-
 })
+
🧰 Tools
🪛 eslint

[error] 5-7: Block must not be padded by blank lines.

(padded-blocks)


[error] 41-43: Block must not be padded by blank lines.

(padded-blocks)


[error] 43-43: Newline required at end of file but not found.

(eol-last)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43de1af and 176a8af.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • src/components/ui/Switch/tests/Switch.test.tsx (1 hunks)
🧰 Additional context used
🪛 eslint
src/components/ui/Switch/tests/Switch.test.tsx

[error] 5-7: Block must not be padded by blank lines.

(padded-blocks)


[error] 41-43: Block must not be padded by blank lines.

(padded-blocks)


[error] 43-43: Newline required at end of file but not found.

(eol-last)

🔇 Additional comments (1)
src/components/ui/Switch/tests/Switch.test.tsx (1)

1-5: LGTM! Clean and standard test setup.

The imports and test suite setup follow best practices.

Comment on lines 7 to 11
test('renders correctly', () => {
render(<Switch checked={true} onChange={() => {}} color='' />)
const inputElement = screen.getByRole('checkbox')
expect(inputElement).toBeInTheDocument();
})
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 the basic render test with meaningful assertions.

The current test only verifies component presence. Consider adding:

  1. Accessibility attributes verification
  2. Default state checks
  3. Meaningful color prop value
 test('renders correctly', () => {
-    render(<Switch checked={true} onChange={() => {}} color='' />)
-    const inputElement = screen.getByRole('checkbox')
-    expect(inputElement).toBeInTheDocument();
+    render(<Switch checked={true} onChange={() => {}} color='primary' />)
+    const inputElement = screen.getByRole('checkbox')
+    expect(inputElement).toBeInTheDocument()
+    expect(inputElement).toHaveAttribute('aria-checked', 'true')
+    expect(inputElement).toHaveAttribute('type', 'checkbox')
+    expect(inputElement).toBeEnabled()
 })
📝 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('renders correctly', () => {
render(<Switch checked={true} onChange={() => {}} color='' />)
const inputElement = screen.getByRole('checkbox')
expect(inputElement).toBeInTheDocument();
})
test('renders correctly', () => {
render(<Switch checked={true} onChange={() => {}} color='primary' />)
const inputElement = screen.getByRole('checkbox')
expect(inputElement).toBeInTheDocument()
expect(inputElement).toHaveAttribute('aria-checked', 'true')
expect(inputElement).toHaveAttribute('type', 'checkbox')
expect(inputElement).toBeEnabled()
})

})

test('renders in controlled mode correctly', () => {
const handleChange = jest.fn();
Copy link
Collaborator

Choose a reason for hiding this comment

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

for controlled mode, create a state with useState and pass it to the component

when switch is toggled, check the state variable

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

🧹 Nitpick comments (2)
src/components/ui/Switch/stories/Switch.stories.js (1)

39-45: Simplify uncontrolled component implementation and fix formatting.

The uncontrolled story can be simplified by removing the unnecessary onChange handler. Also, there are some formatting issues to address.

 export const Uncontrolled = () => {
-    
-        return <SandboxEditor>
-            <Switch defaultChecked ={true} onChange={() => {}}/>
-
-        </SandboxEditor>
+    return <SandboxEditor>
+        <Switch defaultChecked={true} />
+    </SandboxEditor>
 }
+
🧰 Tools
🪛 eslint

[error] 39-41: Block must not be padded by blank lines.

(padded-blocks)


[error] 45-45: Newline required at end of file but not found.

(eol-last)

src/components/ui/Switch/tests/Switch.test.tsx (1)

25-42: Clean up uncontrolled mode tests.

The tests can be simplified by removing unnecessary onChange handlers and fixing formatting.

-test('renders in uncontrolled mode correctly with defaultChecked', () => {
+describe('uncontrolled mode', () => {
+    test('initializes with defaultChecked', () => {
+        render(<Switch defaultChecked={true} />)
+        const checkbox = screen.getByRole('checkbox')
+        expect(checkbox).toBeChecked()
+    })
 
-        render(<Switch defaultChecked={true} onChange={() => {}}/>)
-        const checkbox = screen.getByRole('checkbox')
-        expect(checkbox).toBeChecked()
-        })
-    
-    test('toggles state independently',() => {
-        render(<Switch  defaultChecked={false} onChange={() => {}} />)
-        const checkbox = screen.getByRole('checkbox')
-        expect(checkbox).not.toBeChecked()
+    test('toggles state independently', () => {
+        render(<Switch defaultChecked={false} />)
+        const checkbox = screen.getByRole('checkbox')
+        expect(checkbox).not.toBeChecked()
 
-        fireEvent.click(checkbox)
-        expect(checkbox).toBeChecked()
-        
-        fireEvent.click(checkbox)
-        expect(checkbox).not.toBeChecked()  
-    })    
+        fireEvent.click(checkbox)
+        expect(checkbox).toBeChecked()
+
+        fireEvent.click(checkbox)
+        expect(checkbox).not.toBeChecked()
+    })
+})
🧰 Tools
🪛 eslint

[error] 25-27: Block must not be padded by blank lines.

(padded-blocks)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 176a8af and 620fc20.

📒 Files selected for processing (3)
  • src/components/ui/Switch/Switch.tsx (2 hunks)
  • src/components/ui/Switch/stories/Switch.stories.js (3 hunks)
  • src/components/ui/Switch/tests/Switch.test.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ui/Switch/Switch.tsx
🧰 Additional context used
🪛 eslint
src/components/ui/Switch/stories/Switch.stories.js

[error] 39-41: Block must not be padded by blank lines.

(padded-blocks)


[error] 45-45: Newline required at end of file but not found.

(eol-last)

src/components/ui/Switch/tests/Switch.test.tsx

[error] 5-7: Block must not be padded by blank lines.

(padded-blocks)


[error] 25-27: Block must not be padded by blank lines.

(padded-blocks)


[error] 42-44: Block must not be padded by blank lines.

(padded-blocks)


[error] 44-44: Newline required at end of file but not found.

(eol-last)

🔇 Additional comments (4)
src/components/ui/Switch/stories/Switch.stories.js (2)

Line range hint 13-23: Fix controlled/uncontrolled component pattern mixing.

The component is still mixing controlled and uncontrolled patterns by using both defaultChecked and state management. This can lead to unexpected behavior.

-    const [isChecked, setIsChecked] = useState(true)
-    
-    const handleChange = (state) => {
-        setIsChecked(state);
-    };
-    return <SandboxEditor className="flex flex-col gap-2">
-        {variants.map((variant, index) => (
-            <Switch defaultChecked={args} key={index} variant={variant} onChange={handleChange} {...args} />
-        ))}
+    return <SandboxEditor className="flex flex-col gap-2">
+        {variants.map((variant, index) => (
+            <Switch key={index} variant={variant} {...args} />
+        ))}

28-37: LGTM! Well-implemented controlled component pattern.

The controlled story demonstrates the correct usage pattern with proper state management and onChange handling.

src/components/ui/Switch/tests/Switch.test.tsx (2)

7-11: Enhance basic render test with accessibility checks.

The test should verify accessibility attributes and meaningful props.

 test('renders correctly', () => {
-    render(<Switch checked={true} onChange={() => {}} />)
-    const inputElement = screen.getByRole('checkbox')
-    expect(inputElement).toBeInTheDocument();
+    render(<Switch checked={true} onChange={() => {}} color="primary" />)
+    const inputElement = screen.getByRole('checkbox')
+    expect(inputElement).toBeInTheDocument()
+    expect(inputElement).toHaveAttribute('aria-checked', 'true')
+    expect(inputElement).toHaveAttribute('type', 'checkbox')
+    expect(inputElement).toBeEnabled()
 })

13-23: Improve controlled mode test implementation.

The test needs improvements in role consistency and state management verification.

-test('renders in controlled mode correctly', () => {
+describe('controlled mode', () => {
+    test('calls onChange with correct values', () => {
         const handleChange = jest.fn();
         render(<Switch checked={true} onChange={handleChange} />)
-        const switchElement = screen.getByRole('switch')
+        const switchElement = screen.getByRole('checkbox')
         
         fireEvent.click(switchElement)
         expect(handleChange).toHaveBeenCalledWith(false)
 
         fireEvent.click(switchElement)
         expect(handleChange).toHaveBeenCalledWith(true)
-    })
+    })
+    
+    test('stays in sync with state', () => {
+        const TestComponent = () => {
+            const [checked, setChecked] = React.useState(true)
+            return <Switch checked={checked} onChange={setChecked} />
+        }
+        render(<TestComponent />)
+        const switchElement = screen.getByRole('checkbox')
+        
+        expect(switchElement).toBeChecked()
+        fireEvent.click(switchElement)
+        expect(switchElement).not.toBeChecked()
+    })
+})

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