close
Skip to content

feat(hotkeys): Remove legacy context from Hotkeys - #4382

Merged
mergify[bot] merged 5 commits into
masterfrom
remove-hotkey-legacy-context
Dec 4, 2025
Merged

feat(hotkeys): Remove legacy context from Hotkeys#4382
mergify[bot] merged 5 commits into
masterfrom
remove-hotkey-legacy-context

Conversation

@jfox-box

@jfox-box jfox-box commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

Removes legacy context from HotkeyLayer and replaces it with HotkeyContext. This is required to support React 19 in the future.

Summary by CodeRabbit

  • New Features

    • Storybook demo showcasing the hotkey system, layered hotkeys, modal interactions and help shortcut.
  • Refactor

    • Hotkey handling migrated to a shared React context for more reliable layer behavior.
    • Help modal and hotkey components now consume hotkey data via the shared context without changing user-facing behavior.
  • Tests

    • Improved, context-aware tests with a new test wrapper and full mounting to better exercise lifecycle and layer scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@jfox-box
jfox-box requested a review from a team as a code owner December 3, 2025 23:13
@coderabbitai

coderabbitai Bot commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a HotkeyContext and migrates hotkey components and tests from the legacy React context API to the modern Context API (introduces HotkeyContext, uses HotkeyContext.Provider and .contextType, updates lifecycle usage, tests, and a new Storybook demo).

Changes

Cohort / File(s) Summary
Context core
src/components/hotkeys/HotkeyContext.js
New module: exports HotkeyContext (default null, displayName set) and HotkeyContextPropTypes (hotkeyLayer: PropTypes.object).
Component migration
src/components/hotkeys/HotkeyLayer.js, src/components/hotkeys/Hotkeys.js, src/components/hotkeys/HotkeyHelpModal.js
Replaced legacy context API with modern Context: HotkeyContext.Provider used in HotkeyLayer; consumers use *.contextType = HotkeyContext; removed static contextTypes/childContextTypes/getChildContext; lifecycle access moved to componentDidMount/componentDidUpdate and this.context.
Tests — utilities & wrappers
src/components/hotkeys/__tests__/HotkeyTestWrapper.js
New test helper: HotkeyTestWrapper renders HotkeyContext.Provider, manages internal state, and exposes a child render function for driving context updates in tests.
Tests — updated suites
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js, src/components/hotkeys/__tests__/Hotkeys.test.js, src/components/hotkeys/__tests__/HotkeyLayer.test.js
Tests updated from legacy shallow/context patterns to mount within HotkeyContext.Provider, use act() for state changes, assert provider value and registration/deregistration behavior, and inspect inner components.
Storybook
src/components/hotkeys/Hotkeys.stories.js
New Storybook demo: exports Hotkeys component and default story metadata demonstrating layered hotkeys and a modal override example.
Manifest reference
package.json
Referenced by diffs (no explicit package edits summarized).

Sequence Diagram(s)

sequenceDiagram
  participant HotkeyLayer as HotkeyLayer (Provider)
  participant Provider as HotkeyContext.Provider
  participant Hotkeys as Hotkeys (consumer)
  participant HelpModal as HotkeyHelpModal (consumer)

  HotkeyLayer->>Provider: render with value = hotkeyService
  Note right of Provider: Provides hotkeyLayer API\n(registerHotkey, deregisterHotkey, showHelp)
  Hotkeys->>Provider: read this.context on mount
  Hotkeys->>Provider: call registerHotkey(hotkeyConfig)
  HelpModal->>Provider: read this.context when opened
  alt Modal opens
    HelpModal->>Provider: read types / hotkeys
  end
  Hotkeys->>Provider: on unmount call deregisterHotkey(hotkeyConfig)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to focus on:
    • HotkeyHelpModal: lifecycle changes (constructor → componentDidMount/componentDidUpdate) and context-dependent initialization.
    • Hotkeys: registration/deregistration paths and defensive checks on unmount.
    • HotkeyLayer: provider value correctness and subtree placement.
    • Tests: HotkeyTestWrapper correctness, proper use of act(), and mount/unmount sequences.

Suggested labels

ready-to-merge

Suggested reviewers

  • tjuanitas
  • reneshen0328

Poem

🐰
I hopped from old context to Provider's light,
Layers now whisper keys in tidy sight.
Tests wake and mount, the handlers align,
I nibble stale bugs, then dance — everything's fine! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: removing legacy context from Hotkeys components and replacing it with HotkeyContext for React 19 compatibility.
Description check ✅ Passed The PR description adequately explains the primary objective (replacing legacy context with HotkeyContext to support React 19), though it consists mainly of template boilerplate.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch remove-hotkey-legacy-context

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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/hotkeys/HotkeyContext.js (1)

4-10: Clarify or tighten HotkeyContextPropTypes to match the actual context value

HotkeyContext’s value is the hotkey layer/service instance itself, whereas HotkeyContextPropTypes exposes a hotkeyLayer: PropTypes.object field. This is a bit confusing with the new contextType-based consumers, which now use this.context directly.

Consider either:

  • Updating the shape to more accurately describe the value (e.g., something like PropTypes.shape({ registerHotkey: PropTypes.func, deregisterHotkey: PropTypes.func, ... })), or
  • Removing this helper entirely if nothing still relies on legacy contextTypes.

This will keep the context contract clearer for future consumers.

src/components/hotkeys/__tests__/testHelpers.js (1)

7-23: You can pass this.setState directly instead of binding

In createContextTestWrapper, you don’t need to bind setState:

-                <HotkeyContext.Provider value={contextValue}>
-                    {renderChild(this.state, this.setState.bind(this))}
-                </HotkeyContext.Provider>
+                <HotkeyContext.Provider value={contextValue}>
+                    {renderChild(this.state, this.setState)}
+                </HotkeyContext.Provider>

this.setState is already bound to the component instance in React class components, so the explicit .bind(this) is redundant. This slightly reduces noise in a helper used across tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bf7c63 and 6d82393.

📒 Files selected for processing (8)
  • src/components/hotkeys/HotkeyContext.js (1 hunks)
  • src/components/hotkeys/HotkeyHelpModal.js (3 hunks)
  • src/components/hotkeys/HotkeyLayer.js (2 hunks)
  • src/components/hotkeys/Hotkeys.js (4 hunks)
  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (6 hunks)
  • src/components/hotkeys/__tests__/HotkeyLayer.test.js (2 hunks)
  • src/components/hotkeys/__tests__/Hotkeys.test.js (4 hunks)
  • src/components/hotkeys/__tests__/testHelpers.js (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-15T17:04:28.279Z
Learnt from: bfoxx1906
Repo: box/box-ui-elements PR: 4275
File: src/elements/content-sidebar/activity-feed/comment/__tests__/Comment.test.js:259-260
Timestamp: 2025-09-15T17:04:28.279Z
Learning: When wrapping enzyme mount() tests with IntlProvider for class-based components, use wrapper.find('ComponentName') to access the actual component instance and state, since the wrapper becomes the provider instead of the component.

Applied to files:

  • src/components/hotkeys/__tests__/Hotkeys.test.js
  • src/components/hotkeys/__tests__/testHelpers.js
  • src/components/hotkeys/__tests__/HotkeyLayer.test.js
  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js
🧬 Code graph analysis (6)
src/components/hotkeys/__tests__/Hotkeys.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/testHelpers.js (2)
  • createContextTestWrapper (7-23)
  • createContextTestWrapper (7-23)
src/components/hotkeys/__tests__/testHelpers.js (1)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/HotkeyHelpModal.js (1)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/Hotkeys.js (1)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/HotkeyLayer.js (1)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/testHelpers.js (2)
  • createContextTestWrapper (7-23)
  • createContextTestWrapper (7-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint_test_build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Summary
🔇 Additional comments (6)
src/components/hotkeys/Hotkeys.js (1)

5-6: Context-based Hotkeys wiring and lifecycle are correctly configured

The migration to HotkeyContext via Hotkeys.contextType = HotkeyContext is sound. Per React's Context API, this.context is guaranteed to contain the value from the nearest <HotkeyContext.Provider> in all lifecycle methods including componentDidMount, componentDidUpdate, and componentWillUnmount.

  • componentDidMount fails fast with a clear error if used outside a provider, which matches the expected contract.
  • _addHotkeys and _removeHotkeys directly access the context value (the hotkey layer service), consistent with HotkeyLayer's provider setup.
  • The null check in _removeHotkeys provides defensive safety for edge cases, though context should be available per React's guarantees.
src/components/hotkeys/HotkeyLayer.js (1)

6-7: React Context API pattern with static contextType is correct in principle

The pattern described—wrapping Hotkeys in <HotkeyContext.Provider value={this.hotkeyService}> and consuming via static contextType = HotkeyContext to access this.context—aligns with React's documented behavior for class components. The Provider correctly exposes the service instance to all child components using that context.

However, verification of the actual implementation could not be completed due to repository access limitations. Recommend manual confirmation that:

  • HotkeyContext is the context object itself (not HotkeyContext.Provider)
  • The contextType assignment targets the correct context object
  • HotkeyHelpModal is positioned as a child of the Provider
  • Unmount ordering still preserves the deregistration sequence
src/components/hotkeys/HotkeyHelpModal.js (1)

10-14: Fix HotkeyHelpModal constructor to properly access context in initial state setup

The review comment's technical recommendation is sound: when using static contextType = MyContext, the context value is passed as the second constructor parameter, and calling super(props, context) ensures this.context is available within the constructor body.

However, the explanation contains an inaccuracy. According to React documentation, when you properly pass context to super(props, context), React does assign this.context during the super call, making it available for the remainder of the constructor. The constructor signature should be constructor(props, context) with super(props, context) to ensure context is accessible in initial state setup.

The core issues remain valid:

  • If the constructor currently reads this.context without the context parameter, it will be undefined
  • If isOpen=true on initial mount with context undefined, hotkeys/types won't load until an update triggers componentDidUpdate
  • The test helper needing manual reassignment suggests the current implementation doesn't properly initialize from context

The recommended fix (using constructor(props, context) and super(props, context)) is the correct approach to ensure context is available during initial state initialization. This preserves the previous behavior regardless of isOpen state on first mount.

src/components/hotkeys/__tests__/HotkeyLayer.test.js (1)

22-33: The suggested selector pattern won't work reliably with shallow rendering

While preferring component types over string selectors is generally sound advice, wrapper.find(HotkeyContext.Provider) does not work reliably with Enzyme's shallow() rendering. The shallow renderer doesn't fully traverse Context providers from the modern Context API.

The current test approach—asserting wrapper.instance().hotkeyService directly—is already valid and doesn't depend on fragile selectors. If you want to test the Provider presence itself, consider switching to mount() instead of shallow(), or use React Testing Library, which handles context naturally.

src/components/hotkeys/__tests__/Hotkeys.test.js (1)

21-37: Hotkeys tests properly exercise the new context-based implementation

The tests do a good job of covering HotkeyContext usage:

  • componentDidMount tests mount Hotkeys inside <HotkeyContext.Provider> and assert registerHotkey calls; error handling verifies behavior when no provider exists.
  • The componentDidUpdate test uses createContextTestWrapper to manage state, wraps the setState call in act(), and calls wrapper.update() to confirm that removed configs trigger matching deregisterHotkey calls.
  • The explicit wrapper.unmount() while the provider is mounted ensures componentWillUnmount receives a non-null context, complementing the defensive null-check in the component.
  • Render tests verify both children rendering and the null render path when no children are present.

These tests align well with the context-based implementation in Hotkeys.js.

Also applies to: 64-78, 79-94, 123-143, 148-176

src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (1)

16-38: Refactor getWrapper to use mount() with createContextTestWrapper pattern instead of shallow() + dive()

The manual context assignment in getWrapper (lines 16-38) works around an Enzyme limitation: shallow() + dive() doesn't automatically bind React's modern Context API (static contextType) to the component. This forces the workaround of manually setting instance.context, reinitializing hotkeys/types, and patching state.

The same test file already demonstrates the correct pattern at lines 51-57 with createTestWrapper using mount(). This approach—mounting the component within a Provider—automatically binds context without manual intervention.

Refactor getWrapper to follow the createTestWrapper pattern, which will:

  • Eliminate manual context assignment
  • Remove the need to reinitialize instance properties
  • Make the test setup clearer and less coupled to implementation details

The componentDidUpdate() tests (lines 86-130) already use this pattern successfully and can serve as a reference.

Likely an incorrect or invalid review comment.

Comment thread src/components/hotkeys/__tests__/HotkeyHelpModal.test.js Outdated
Comment thread src/components/hotkeys/__tests__/HotkeyHelpModal.test.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d82393 and 335a7ad.

📒 Files selected for processing (5)
  • src/components/hotkeys/HotkeyHelpModal.js (3 hunks)
  • src/components/hotkeys/Hotkeys.stories.js (1 hunks)
  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (5 hunks)
  • src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1 hunks)
  • src/components/hotkeys/__tests__/Hotkeys.test.js (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-15T17:04:28.279Z
Learnt from: bfoxx1906
Repo: box/box-ui-elements PR: 4275
File: src/elements/content-sidebar/activity-feed/comment/__tests__/Comment.test.js:259-260
Timestamp: 2025-09-15T17:04:28.279Z
Learning: When wrapping enzyme mount() tests with IntlProvider for class-based components, use wrapper.find('ComponentName') to access the actual component instance and state, since the wrapper becomes the provider instead of the component.

Applied to files:

  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js
  • src/components/hotkeys/__tests__/HotkeyTestWrapper.js
  • src/components/hotkeys/__tests__/Hotkeys.test.js
🧬 Code graph analysis (3)
src/components/hotkeys/HotkeyHelpModal.js (1)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1)
  • HotkeyTestWrapper (8-22)
src/components/hotkeys/__tests__/Hotkeys.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1)
  • HotkeyTestWrapper (8-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (10)
src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1)

1-22: LGTM! Clean test utility for context-driven testing.

This wrapper component provides a good pattern for testing components that consume context and need to verify componentDidUpdate behavior. Based on learnings, this approach correctly allows accessing component instances via wrapper.find('ComponentName') when the wrapper is a provider.

src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (2)

16-28: Good use of act() with provider-wrapped mounting.

The pattern correctly wraps mount in act() and uses wrapper.update() to ensure React has processed all updates before querying the component. Returning both wrapper and hotkeyHelpModal follows the learning about accessing component instances when wrapped in a provider.


111-127: Relative call count assertions address previous feedback.

The test now compares against initial call counts rather than expecting exact values, which is more robust when context timing may vary. This addresses the past review concern about call count assertions.

src/components/hotkeys/__tests__/Hotkeys.test.js (3)

20-37: LGTM! Clean migration to context-based testing.

The test correctly uses HotkeyContext.Provider to supply the mock hotkey layer, and sandbox.mock().thrice() properly expects exactly 3 registerHotkey calls for the 3 configs.


57-89: Good use of HotkeyTestWrapper for natural state updates.

Using HotkeyTestWrapper to manage state and trigger componentDidUpdate naturally (via setState) is cleaner than manually calling lifecycle methods. The act() wrapper ensures React processes the update synchronously.


116-138: LGTM! Unmount test correctly verifies deregistration.

The test mounts with context, then unmounts and verifies that deregisterHotkey is called for each config via the mock expectation.

src/components/hotkeys/HotkeyHelpModal.js (3)

39-58: Clean migration from constructor context to componentDidMount.

Moving context-dependent initialization to componentDidMount is the correct approach with the modern Context API, as this.context isn't reliably available during construction. The guard against missing context prevents runtime errors.


60-79: Defensive early return prevents errors when context is unavailable.

The guard if (!isOpen || !hotkeyLayer) correctly handles cases where the component renders before context is available or when the modal is closed.


201-202: Correct modern Context API usage.

Assigning HotkeyHelpModal.contextType = HotkeyContext is the standard pattern for class components consuming context. This replaces the legacy static contextTypes and enables this.context access.

src/components/hotkeys/Hotkeys.stories.js (1)

1-299: Excellent comprehensive Storybook documentation.

This story effectively demonstrates all hotkey features including:

  • Basic single-key and combination hotkeys
  • Hidden hotkeys (no type)
  • Multiple key bindings for the same action
  • Nested HotkeyLayer for modal context with override behavior
  • Help modal integration

This provides valuable documentation for developers using the hotkey system.

Comment thread src/components/hotkeys/__tests__/HotkeyHelpModal.test.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (1)

2-2: Inconsistent act import location.

This file imports act from 'react' while Hotkeys.test.js imports it from 'react-dom/test-utils'. The react export is available in React 18+, but for consistency across the test suite and backward compatibility, consider using the same import source.

-import { act } from 'react';
+import { act } from 'react-dom/test-utils';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 335a7ad and 66d9b96.

📒 Files selected for processing (2)
  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (4 hunks)
  • src/components/hotkeys/__tests__/Hotkeys.test.js (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-15T17:04:28.279Z
Learnt from: bfoxx1906
Repo: box/box-ui-elements PR: 4275
File: src/elements/content-sidebar/activity-feed/comment/__tests__/Comment.test.js:259-260
Timestamp: 2025-09-15T17:04:28.279Z
Learning: When wrapping enzyme mount() tests with IntlProvider for class-based components, use wrapper.find('ComponentName') to access the actual component instance and state, since the wrapper becomes the provider instead of the component.

Applied to files:

  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js
  • src/components/hotkeys/__tests__/Hotkeys.test.js
🧬 Code graph analysis (2)
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1)
  • HotkeyTestWrapper (8-22)
src/components/hotkeys/__tests__/Hotkeys.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1)
  • HotkeyTestWrapper (8-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (12)
src/components/hotkeys/__tests__/Hotkeys.test.js (6)

1-10: Imports look correct for the context migration.

The imports properly bring in act from react-dom/test-utils, HotkeyContext for providing context, and HotkeyTestWrapper for state-driven tests. The setup aligns with the migration from legacy context to modern Context API.


19-54: LGTM!

The componentDidMount tests correctly verify:

  1. Registration of 3 hotkeys with a mock expecting exactly 3 calls (thrice())
  2. Error thrown when mounted without HotkeyContext.Provider

The migration to mount with context provider is appropriate.


57-86: LGTM!

The test correctly uses HotkeyTestWrapper to manage state changes and verify that deregisterHotkey is called twice when removing 2 of 3 configs. The use of act() for state updates follows React testing best practices.


88-110: Shallow rendering retained for isolated error testing.

Using shallow with disableLifecycleMethods is appropriate here to test componentDidUpdate error handling in isolation without triggering componentDidMount. This is a valid pattern for testing specific lifecycle behavior.


113-135: LGTM!

The unmount test correctly verifies that all 3 hotkeys are deregistered when the component unmounts. The mock expectation (thrice()) aligns with the 3 configs provided.


138-168: LGTM!

The render tests correctly verify:

  1. Children are rendered when provided
  2. No children are rendered when none are passed

Using wrapper.find('Hotkeys').children().length is an appropriate way to verify null rendering after context migration. Based on learnings, using wrapper.find('ComponentName') to access the component when wrapped with a provider is the correct approach.

src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (6)

16-28: LGTM!

The getWrapper helper correctly wraps mounting in act(), calls wrapper.update() to sync enzyme's state, and extracts the inner HotkeyHelpModal via find('HotkeyHelpModal'). This pattern aligns with best practices for testing wrapped components. Based on learnings, accessing the actual component via wrapper.find('ComponentName') is the correct approach when wrapped with a provider.


41-68: LGTM!

The render tests properly verify modal rendering, isOpen prop propagation, and null rendering when no hotkeys exist. The assertions are appropriate for the context-wrapped testing approach.


70-115: LGTM!

The componentDidUpdate tests correctly verify:

  1. currentType is set when the modal opens with null currentType
  2. Service methods are called both on mount and when isOpen changes (2 calls each)

The use of HotkeyTestWrapper for state management and act() for updates follows proper testing patterns.


117-142: LGTM!

The test correctly verifies that DropdownMenu renders with the expected types. Accessing instance.types to verify internal state is appropriate for this unit test.


144-201: LGTM!

The test correctly verifies hotkey counts per type:

  • 2 navigation hotkeys → expects 2 .hotkey-item elements
  • 3 other hotkeys → expects 3 .hotkey-item elements

The previous review comment about assertion mismatch has been addressed.


204-244: LGTM!

The renderHotkey tests correctly verify:

  1. Single combo key shift+a+b+c renders 4 <kbd> elements
  2. Multiple hotkeys ['shift+a', 'alt+a'] render 3 children (2 keys + separator) with 4 total <kbd> elements

The assertions accurately reflect the expected DOM structure.

@jfox-box
jfox-box force-pushed the remove-hotkey-legacy-context branch from 66d9b96 to c00471d Compare December 4, 2025 19:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/components/hotkeys/__tests__/Hotkeys.test.js (2)

57-86: Add cleanup to prevent test isolation issues.

The test correctly uses act() for state updates and properly verifies deregistration behavior. However, the mounted wrapper should be unmounted after assertions to ensure proper cleanup.

Apply this diff to add cleanup:

         act(() => {
             wrapper.find('HotkeyTestWrapper').setState({ configs: [configs[1]] });
         });
 
         wrapper.update();
+        wrapper.unmount();
     });

88-110: Consider migrating to mount-based error testing for consistency.

This test uses shallow rendering with manual componentDidUpdate invocation to test the error path. However, with the new contextType usage, the behavior of this.context in a shallow render might not accurately reflect production behavior. For consistency with the componentDidMount error test (lines 40-54) and the rest of the migration, consider using mount without a context provider.

Apply this diff to align with the mount-based testing pattern:

-        test('should throw error when hotkey layer does not exist', () => {
-            const wrapper = shallow(
-                <Hotkeys
-                    configs={[
-                        new HotkeyRecord({ key: 'a' }),
-                        new HotkeyRecord({ key: 'b' }),
-                        new HotkeyRecord({ key: 'c' }),
-                    ]}
-                >
-                    <div />
-                </Hotkeys>,
-                {
-                    disableLifecycleMethods: true,
-                },
-            );
-
-            // componentDidUpdate would throw when trying to add hotkeys if context is null
-            expect(() => {
-                wrapper.instance().componentDidUpdate({
-                    configs: [new HotkeyRecord({ key: 'a' })],
-                });
-            }).toThrow();
+        test('should throw error when hotkey layer does not exist', () => {
+            const wrapper = mount(
+                <Hotkeys configs={[new HotkeyRecord({ key: 'a' })]} />,
+                { disableLifecycleMethods: true },
+            );
+
+            expect(() => {
+                wrapper.setProps({
+                    configs: [
+                        new HotkeyRecord({ key: 'a' }),
+                        new HotkeyRecord({ key: 'b' }),
+                    ],
+                });
+            }).toThrow();
         });
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (1)

95-102: Missing wrapper.update() after act() for consistency.

Other tests in this file (lines 177, 185) call wrapper.update() after act() to synchronize the enzyme wrapper with React's state. While this test checks mock call counts rather than DOM, adding wrapper.update() would maintain consistency and ensure the wrapper is properly synchronized.

             act(() => {
                 wrapper.find('HotkeyTestWrapper').setState({ isOpen: true });
             });
+            wrapper.update();

             // One call for componentDidMount, one call for componentDidUpdate
             expect(HotkeyServiceMock.getActiveHotkeys.callCount).toBe(2);
src/components/hotkeys/Hotkeys.stories.js (1)

289-299: Consider exporting the story component in the default config.

Line 291 exports HotkeyLayer as the component, but the actual story renders the Hotkeys component (defined at line 10). For Storybook documentation clarity, consider whether component: Hotkeys would be more accurate, unless the intent is to document the HotkeyLayer API with Hotkeys as a usage example.

If you want to align the export with the story:

 export default {
     title: 'Components/Hotkeys',
-    component: HotkeyLayer,
+    component: Hotkeys,
     parameters: {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66d9b96 and c00471d.

📒 Files selected for processing (3)
  • src/components/hotkeys/Hotkeys.stories.js (1 hunks)
  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (4 hunks)
  • src/components/hotkeys/__tests__/Hotkeys.test.js (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-15T17:04:28.279Z
Learnt from: bfoxx1906
Repo: box/box-ui-elements PR: 4275
File: src/elements/content-sidebar/activity-feed/comment/__tests__/Comment.test.js:259-260
Timestamp: 2025-09-15T17:04:28.279Z
Learning: When wrapping enzyme mount() tests with IntlProvider for class-based components, use wrapper.find('ComponentName') to access the actual component instance and state, since the wrapper becomes the provider instead of the component.

Applied to files:

  • src/components/hotkeys/__tests__/Hotkeys.test.js
  • src/components/hotkeys/__tests__/HotkeyHelpModal.test.js
🧬 Code graph analysis (1)
src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (2)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
src/components/hotkeys/__tests__/HotkeyTestWrapper.js (1)
  • HotkeyTestWrapper (8-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (14)
src/components/hotkeys/__tests__/Hotkeys.test.js (4)

1-10: LGTM! Imports properly support the context migration.

The addition of act, HotkeyContext, and HotkeyTestWrapper aligns with the migration from legacy context to modern Context API. The retention of shallow is appropriate for the error-handling test at line 89.


19-55: LGTM! componentDidMount tests properly validate context usage.

Both tests correctly migrate to mount-based rendering. The first test wraps the component in HotkeyContext.Provider with a mock layer, and the second test properly validates the error path when no context is provided.


113-136: LGTM! componentWillUnmount test properly validates cleanup.

The test correctly uses mount with HotkeyContext.Provider and properly calls unmount() to verify that hotkeys are deregistered during cleanup.


138-168: LGTM! Render tests properly validate component output.

Both tests correctly use mount with HotkeyContext.Provider to test rendering behavior. The assertions appropriately verify that children are rendered when provided and that the component renders nothing when no children are present.

src/components/hotkeys/__tests__/HotkeyHelpModal.test.js (6)

1-9: LGTM on import changes.

The new imports properly support the migration to the modern Context API. Using mount instead of shallow rendering is correct when testing components wrapped in context providers, and act is appropriate for React 18 state update handling. Based on learnings, using wrapper.find('ComponentName') to access the actual component instance is the correct pattern here.


16-28: Well-structured test helper.

The getWrapper function correctly wraps the mount in act(), synchronizes the enzyme wrapper with wrapper.update(), and returns both the outer wrapper and inner component reference. This pattern aligns with the retrieved learnings for testing class-based components wrapped with providers.


42-67: Tests correctly updated for context-based rendering.

The render tests properly leverage the new getWrapper helper and context pattern. The assertion at line 66 validly checks for null rendering—when a component returns null, children().get(0) returns undefined which is falsy.


105-129: LGTM on dropdown menu test.

The test properly creates a custom mock context and verifies both the internal types property and the DOM rendering of the dropdown components.


132-189: LGTM on hotkey list rendering test.

The test correctly uses the HotkeyTestWrapper pattern for state management and properly wraps state updates in act() followed by wrapper.update(). The assertions at lines 180 and 188 correctly match the mock data (2 navigation hotkeys, 3 other hotkeys).


192-231: LGTM on hotkey rendering tests.

Both tests properly use the getWrapper helper and assert on the hotkeyHelpModal reference to verify correct rendering of hotkey elements and kbd components.

src/components/hotkeys/Hotkeys.stories.js (4)

10-16: LGTM!

Clean state management with well-named variables for the demonstration.


142-167: LGTM!

The modal hotkeys correctly demonstrate layer-based overriding with the 'x' and 's' keys defined in both base and modal layers to show context-aware behavior.


170-252: Confirm duplicate helpModalShortcut is intentional.

Both the base HotkeyLayer (line 170) and the nested modal HotkeyLayer (line 252) use the same helpModalShortcut="?". Based on the UI text at line 271 ("Press ? - see only modal hotkeys in help"), this appears intentional to show context-aware help. Verify that pressing "?" in the modal correctly shows only modal-layer hotkeys and doesn't conflict with the base layer's help modal.


1-8: No changes needed. HotkeyContext is correctly provided by HotkeyLayer, which wraps the story's JSX. The story correctly uses HotkeyLayer as its root component, and HotkeyLayer internally provides the HotkeyContext.Provider (as seen in HotkeyLayer.js lines 75-86). This pattern is consistent with the codebase architecture and requires no explicit provider wrapper in the story.

Comment thread src/components/hotkeys/Hotkeys.stories.js
Comment thread src/components/hotkeys/Hotkeys.js Outdated
tjuanitas
tjuanitas previously approved these changes Dec 4, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
src/components/hotkeys/Hotkeys.js (1)

18-21: Hotkey registration logic is sound; consider small consistency cleanup

The lifecycle/context flow is coherent: you validate this.context in componentDidMount, then use it in _addHotkeys, and _removeHotkeys is defensively guarded with if (this.context).

If you want to tighten things up further (optional):

  • Either mirror the guard in _addHotkeys (if (this.context) { ... }) for symmetry, or
  • Reuse the hotkeyLayer alias from componentDidMount (or a local alias in the helpers) instead of hitting this.context inline, which makes it more explicit what you expect the context value to be.

Behavior is fine as-is; this is just for consistency/readability.

Also applies to: 44-52

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d731a2d and 20d25d0.

📒 Files selected for processing (1)
  • src/components/hotkeys/Hotkeys.js (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/hotkeys/Hotkeys.js (1)
src/components/hotkeys/HotkeyContext.js (2)
  • HotkeyContext (4-4)
  • HotkeyContext (4-4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: lint_test_build
  • GitHub Check: Summary
🔇 Additional comments (1)
src/components/hotkeys/Hotkeys.js (1)

5-5: Context wiring looks correct; confirm HotkeyContext export shape matches import

The shift to Hotkeys.contextType = HotkeyContext with a named import is the right pattern for replacing legacy context usage in this class. Just make sure src/components/hotkeys/HotkeyContext.js actually exports HotkeyContext as a named export (e.g., export const HotkeyContext = React.createContext(null);). If it’s a default export instead, this import will resolve to undefined and this.context will never be populated.

Also applies to: 62-62

@mergify

mergify Bot commented Dec 4, 2025

Copy link
Copy Markdown
Contributor

Merge Queue Status

✅ The pull request has been merged

This pull request spent 11 minutes 30 seconds in the queue, including 11 minutes 20 seconds running CI.
The checks were run in-place.

Required conditions to merge

@mergify mergify Bot added the queued label Dec 4, 2025
@mergify
mergify Bot merged commit 5db66df into master Dec 4, 2025
10 checks passed
@mergify
mergify Bot deleted the remove-hotkey-legacy-context branch December 4, 2025 22:14
@mergify mergify Bot removed the queued label Dec 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants