-
-
Notifications
You must be signed in to change notification settings - Fork 363
feat(bridge-react): add rerender option to createBridgeComponent (#4171) #4172
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
Open
ScriptedAlchemy
wants to merge
22
commits into
main
Choose a base branch
from
patch-1
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
2a9fc49
Fix path join for downloading remote types in FederatedTypesPlugin.ts
philip-lempke 4f5ea10
Merge branch 'main' into patch-1
ScriptedAlchemy 94ecab3
fix: add trailing comma to URL constructor
ScriptedAlchemy fa65f29
feat(bridge-react): add rerender option to createBridgeComponent
ScriptedAlchemy ddcf793
fix(bridge-react): properly implement shouldRecreate functionality
ScriptedAlchemy d5b4061
fix(bridge-react): properly implement shouldRecreate functionality
ScriptedAlchemy 537182e
revert: restore FederatedTypesPlugin.ts to main version
ScriptedAlchemy 88557b5
chore(bridge-react): changeset patch bump for rerender option (#4171)
ScriptedAlchemy e730e0d
Merge remote-tracking branch 'origin/main' into patch-1
ScriptedAlchemy a29162b
test(bridge-react): avoid direct jsdom import; fallback to global window
ScriptedAlchemy f053a00
fix(bridge-react): preserve component state on rerender
ScriptedAlchemy 1a824ff
test(bridge-react): assert lifecycle destroy emits on recreation and …
ScriptedAlchemy 996261f
test(bridge-react): assert state stability + extraProps injection
ScriptedAlchemy ba6ae5c
Merge branch 'main' into patch-1
ScriptedAlchemy 29d94a9
fix(bridge-react): fallback to custom render on updates when returned…
ScriptedAlchemy 2a4045b
test(bridge-react): stabilize fallback custom-render test
ScriptedAlchemy d48a8c1
test(bridge-react): reset mocked federationRuntime between tests to a…
ScriptedAlchemy 22591c1
chore(bridge-react): format rerender-issue.spec.tsx
ScriptedAlchemy c86b975
test(bridge-react): add hydration and key-based remount coverage; ass…
ScriptedAlchemy 18ea1a6
refactor(bridge-react): reduce usage; add type guards and precise ty…
ScriptedAlchemy 7ec9ec5
style: fix formatting issues
ScriptedAlchemy 3716f5e
fix: adjust bridge legacy react handling
ScriptedAlchemy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
208 changes: 208 additions & 0 deletions
208
packages/bridge/bridge-react/__tests__/rerender-issue.spec.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| import React, { useState, useRef } from 'react'; | ||
| import { createBridgeComponent, createRemoteAppComponent } from '../src'; | ||
| import { | ||
| act, | ||
| fireEvent, | ||
| render, | ||
| screen, | ||
| waitFor, | ||
| } from '@testing-library/react'; | ||
|
|
||
| describe('Issue #4171: Rerender functionality', () => { | ||
| it('should call custom rerender function when provided', async () => { | ||
| const customRerenderSpy = jest.fn(); | ||
| let instanceCounter = 0; | ||
|
|
||
| // Remote component that tracks instances | ||
| function RemoteApp({ count }: { count: number }) { | ||
| const instanceId = useRef(++instanceCounter); | ||
|
|
||
| return ( | ||
| <div> | ||
| <span data-testid="remote-count">Count: {count}</span> | ||
| <span data-testid="instance-id">Instance: {instanceId.current}</span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Create bridge component with custom rerender function | ||
| const BridgeComponent = createBridgeComponent({ | ||
| rootComponent: RemoteApp, | ||
| rerender: (props) => { | ||
| customRerenderSpy(props); | ||
| return { shouldRecreate: false }; | ||
| }, | ||
| }); | ||
|
|
||
| const RemoteAppComponent = createRemoteAppComponent({ | ||
| loader: async () => ({ default: BridgeComponent }), | ||
| loading: <div>Loading...</div>, | ||
| fallback: () => <div>Error</div>, | ||
| }); | ||
|
|
||
| function HostApp() { | ||
| const [count, setCount] = useState(0); | ||
|
|
||
| return ( | ||
| <div> | ||
| <button | ||
| data-testid="increment-btn" | ||
| onClick={() => setCount((c) => c + 1)} | ||
| > | ||
| Increment: {count} | ||
| </button> | ||
| <RemoteAppComponent props={{ count }} /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| render(<HostApp />); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId('remote-count')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // Clear spy to track only rerender calls | ||
| customRerenderSpy.mockClear(); | ||
|
|
||
| // Trigger rerender | ||
| act(() => { | ||
| fireEvent.click(screen.getByTestId('increment-btn')); | ||
| }); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId('remote-count')).toHaveTextContent('Count: 1'); | ||
| }); | ||
|
|
||
| // Custom rerender function should have been called | ||
| expect(customRerenderSpy).toHaveBeenCalled(); | ||
|
|
||
| // Verify the custom rerender function was called with props | ||
| const callArgs = customRerenderSpy.mock.calls[0][0]; | ||
| expect(callArgs).toBeDefined(); | ||
| expect(typeof callArgs).toBe('object'); | ||
| }); | ||
|
|
||
| it('should work without rerender option (backward compatibility)', async () => { | ||
| let instanceCounter = 0; | ||
|
|
||
| function RemoteApp({ count }: { count: number }) { | ||
| const instanceId = useRef(++instanceCounter); | ||
|
|
||
| return ( | ||
| <div> | ||
| <span data-testid="remote-count">Count: {count}</span> | ||
| <span data-testid="instance-id">Instance: {instanceId.current}</span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Create bridge component without rerender option (existing behavior) | ||
| const BridgeComponent = createBridgeComponent({ | ||
| rootComponent: RemoteApp, | ||
| }); | ||
|
|
||
| const RemoteAppComponent = createRemoteAppComponent({ | ||
| loader: async () => ({ default: BridgeComponent }), | ||
| loading: <div>Loading...</div>, | ||
| fallback: () => <div>Error</div>, | ||
| }); | ||
|
|
||
| function HostApp() { | ||
| const [count, setCount] = useState(0); | ||
|
|
||
| return ( | ||
| <div> | ||
| <button | ||
| data-testid="increment-btn" | ||
| onClick={() => setCount((c) => c + 1)} | ||
| > | ||
| Increment: {count} | ||
| </button> | ||
| <RemoteAppComponent props={{ count }} /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| render(<HostApp />); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId('remote-count')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| // Should work without errors (backward compatibility) | ||
| act(() => { | ||
| fireEvent.click(screen.getByTestId('increment-btn')); | ||
| }); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId('remote-count')).toHaveTextContent('Count: 1'); | ||
| }); | ||
|
|
||
| // Component should still function correctly | ||
| expect(screen.getByTestId('remote-count')).toHaveTextContent('Count: 1'); | ||
| }); | ||
|
|
||
| it('should support rerender function returning void', async () => { | ||
| const customRerenderSpy = jest.fn(); | ||
|
|
||
| function RemoteApp({ count }: { count: number }) { | ||
| return ( | ||
| <div> | ||
| <span data-testid="remote-count">Count: {count}</span> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // Create bridge component with rerender function that returns void | ||
| const BridgeComponent = createBridgeComponent({ | ||
| rootComponent: RemoteApp, | ||
| rerender: (props) => { | ||
| customRerenderSpy(props); | ||
| // Return void (undefined) | ||
| }, | ||
| }); | ||
|
|
||
| const RemoteAppComponent = createRemoteAppComponent({ | ||
| loader: async () => ({ default: BridgeComponent }), | ||
| loading: <div>Loading...</div>, | ||
| fallback: () => <div>Error</div>, | ||
| }); | ||
|
|
||
| function HostApp() { | ||
| const [count, setCount] = useState(0); | ||
|
|
||
| return ( | ||
| <div> | ||
| <button | ||
| data-testid="increment-btn" | ||
| onClick={() => setCount((c) => c + 1)} | ||
| > | ||
| Increment: {count} | ||
| </button> | ||
| <RemoteAppComponent props={{ count }} /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| render(<HostApp />); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId('remote-count')).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| customRerenderSpy.mockClear(); | ||
|
|
||
| // Trigger rerender | ||
| act(() => { | ||
| fireEvent.click(screen.getByTestId('increment-btn')); | ||
| }); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByTestId('remote-count')).toHaveTextContent('Count: 1'); | ||
| }); | ||
|
|
||
| // Custom rerender function should have been called even when returning void | ||
| expect(customRerenderSpy).toHaveBeenCalled(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // Simple test to verify our rerender implementation works | ||
|
|
||
| // Mock the types and functions we need | ||
| const mockCreateBridgeComponent = (options) => { | ||
| console.log('createBridgeComponent called with options:', { | ||
| hasRootComponent: !!options.rootComponent, | ||
| hasRerender: !!options.rerender, | ||
| rerenderType: typeof options.rerender | ||
| }); | ||
|
|
||
| if (options.rerender) { | ||
| console.log('✅ Rerender option detected!'); | ||
|
|
||
| // Test the rerender function | ||
| const mockProps = { count: 1, moduleName: 'test', dom: {} }; | ||
| const result = options.rerender(mockProps); | ||
| console.log('Rerender function result:', result); | ||
|
|
||
| if (result && result.shouldRecreate === false) { | ||
| console.log('✅ Custom rerender function working correctly - shouldRecreate: false'); | ||
| } else if (result === undefined) { | ||
| console.log('✅ Custom rerender function working correctly - returned void'); | ||
| } | ||
| } else { | ||
| console.log('❌ No rerender option provided'); | ||
| } | ||
|
|
||
| return () => ({ | ||
| render: (info) => console.log('Bridge render called with:', Object.keys(info)), | ||
| destroy: (info) => console.log('Bridge destroy called') | ||
| }); | ||
| }; | ||
|
|
||
| // Test 1: Bridge component without rerender option (existing behavior) | ||
| console.log('\n=== Test 1: Without rerender option ==='); | ||
| const BridgeWithoutRerender = mockCreateBridgeComponent({ | ||
| rootComponent: () => ({ type: 'div', children: 'Test Component' }) | ||
| }); | ||
|
|
||
| // Test 2: Bridge component with rerender option (new functionality) | ||
| console.log('\n=== Test 2: With rerender option ==='); | ||
| const BridgeWithRerender = mockCreateBridgeComponent({ | ||
| rootComponent: () => ({ type: 'div', children: 'Test Component' }), | ||
| rerender: (props) => { | ||
| console.log('Custom rerender called with props:', Object.keys(props)); | ||
| return { shouldRecreate: false }; | ||
| } | ||
| }); | ||
|
|
||
| // Test 3: Bridge component with rerender option that returns void | ||
| console.log('\n=== Test 3: With rerender option returning void ==='); | ||
| const BridgeWithVoidRerender = mockCreateBridgeComponent({ | ||
| rootComponent: () => ({ type: 'div', children: 'Test Component' }), | ||
| rerender: (props) => { | ||
| console.log('Custom rerender called with props:', Object.keys(props)); | ||
| // Return void (undefined) | ||
| } | ||
| }); | ||
|
|
||
| console.log('\n=== All tests completed ==='); | ||
| console.log('✅ Implementation supports the rerender option as specified in issue #4171'); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.