-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1367 from bcgov/feat/daniel-add-frontend-feature-…
…flags-1223 feat: Add front end feature flags
- Loading branch information
Showing
8 changed files
with
332 additions
and
29 deletions.
There are no files selected for viewing
This file contains 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 |
---|---|---|
|
@@ -8,6 +8,7 @@ __pycache__/ | |
*.py[cod] | ||
*$py.class | ||
docs/ | ||
.DS_Store | ||
|
||
# C extensions | ||
*.so | ||
|
This file contains 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 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 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,109 @@ | ||
import React from 'react' | ||
import { render, screen } from '@testing-library/react' | ||
import { describe, it, expect, vi, beforeEach } from 'vitest' | ||
import withFeatureFlag from '../withFeatureFlag.jsx' // Adjust the import path as necessary | ||
import { isFeatureEnabled } from '@/constants/config.js' | ||
|
||
// Mock the isFeatureEnabled function | ||
vi.mock('@/constants/config.js', () => ({ | ||
isFeatureEnabled: vi.fn() | ||
})) | ||
|
||
// Mock Navigate component | ||
vi.mock('react-router-dom', () => ({ | ||
...vi.importActual('react-router-dom'), | ||
Navigate: ({ to }) => <div data-test="navigate">Navigate to {to}</div> | ||
})) | ||
|
||
// Define a mock component to be wrapped | ||
const MockComponent = () => <div>Feature Enabled Content</div> | ||
|
||
describe('withFeatureFlag HOC', () => { | ||
beforeEach(() => { | ||
vi.clearAllMocks() | ||
}) | ||
|
||
it('renders the wrapped component when the feature flag is enabled', () => { | ||
isFeatureEnabled.mockReturnValue(true) | ||
|
||
const WrappedComponent = withFeatureFlag( | ||
MockComponent, | ||
'new-feature', | ||
'/fallback' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
expect(screen.getByText('Feature Enabled Content')).toBeInTheDocument() | ||
}) | ||
|
||
it('redirects to the specified path when the feature flag is disabled and redirect is provided', () => { | ||
isFeatureEnabled.mockReturnValue(false) | ||
|
||
const WrappedComponent = withFeatureFlag( | ||
MockComponent, | ||
'new-feature', | ||
'/fallback' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
const navigateElement = screen.getByTestId('navigate') | ||
expect(navigateElement).toBeInTheDocument() | ||
expect(navigateElement).toHaveTextContent('Navigate to /fallback') | ||
}) | ||
|
||
it('renders null when the feature flag is disabled and no redirect is provided', () => { | ||
isFeatureEnabled.mockReturnValue(false) | ||
|
||
const WrappedComponent = withFeatureFlag(MockComponent, 'new-feature') | ||
|
||
const { container } = render(<WrappedComponent />) | ||
|
||
expect(container.firstChild).toBeNull() | ||
}) | ||
|
||
it('sets the correct display name for the wrapped component', () => { | ||
isFeatureEnabled.mockReturnValue(true) | ||
|
||
const WrappedComponent = withFeatureFlag( | ||
MockComponent, | ||
'new-feature', | ||
'/fallback' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
expect(WrappedComponent.displayName).toBe('WithFeatureFlag(MockComponent)') | ||
}) | ||
|
||
it('handles undefined featureFlag gracefully by rendering the wrapped component', () => { | ||
isFeatureEnabled.mockReturnValue(false) | ||
|
||
const WrappedComponent = withFeatureFlag( | ||
MockComponent, | ||
undefined, | ||
'/fallback' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
const navigateElement = screen.getByTestId('navigate') | ||
expect(navigateElement).toBeInTheDocument() | ||
expect(navigateElement).toHaveTextContent('Navigate to /fallback') | ||
}) | ||
|
||
it('handles null props correctly by passing them to the wrapped component', () => { | ||
isFeatureEnabled.mockReturnValue(true) | ||
|
||
const WrappedComponent = withFeatureFlag( | ||
MockComponent, | ||
'new-feature', | ||
'/fallback' | ||
) | ||
|
||
render(<WrappedComponent prop1={null} />) | ||
|
||
expect(screen.getByText('Feature Enabled Content')).toBeInTheDocument() | ||
}) | ||
}) |
This file contains 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 |
---|---|---|
@@ -1 +1,137 @@ | ||
describe.todo() | ||
import React from 'react' | ||
import { render, screen } from '@testing-library/react' | ||
import { describe, it, expect, vi, beforeEach } from 'vitest' | ||
import withRole from '../withRole.jsx' | ||
import { useCurrentUser } from '@/hooks/useCurrentUser' | ||
|
||
// Mock the useCurrentUser hook | ||
vi.mock('@/hooks/useCurrentUser') | ||
|
||
// Mock Navigate component | ||
vi.mock('react-router-dom', () => ({ | ||
...vi.importActual('react-router-dom'), | ||
Navigate: ({ to }) => <div data-test="navigate">Navigate to {to}</div> | ||
})) | ||
|
||
// Define a mock component to be wrapped | ||
const MockComponent = () => <div>Protected Content</div> | ||
|
||
describe('withRole HOC', () => { | ||
beforeEach(() => { | ||
vi.clearAllMocks() | ||
}) | ||
|
||
it('renders Loading... when currentUser is undefined', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: undefined | ||
}) | ||
|
||
const WrappedComponent = withRole( | ||
MockComponent, | ||
['admin', 'user'], | ||
'/login' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
expect(screen.getByText('Loading...')).toBeInTheDocument() | ||
}) | ||
|
||
it('renders the wrapped component when user has an allowed role', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: { | ||
roles: [{ name: 'user' }, { name: 'editor' }] | ||
} | ||
}) | ||
|
||
const WrappedComponent = withRole( | ||
MockComponent, | ||
['admin', 'user'], | ||
'/login' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
expect(screen.getByText('Protected Content')).toBeInTheDocument() | ||
}) | ||
|
||
it('redirects to the specified path when user does not have an allowed role and redirect is provided', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: { | ||
roles: [{ name: 'guest' }] | ||
} | ||
}) | ||
|
||
const WrappedComponent = withRole( | ||
MockComponent, | ||
['admin', 'user'], | ||
'/login' | ||
) | ||
|
||
render(<WrappedComponent />) | ||
|
||
const navigateElement = screen.getByTestId('navigate') | ||
expect(navigateElement).toBeInTheDocument() | ||
expect(navigateElement).toHaveTextContent('Navigate to /login') | ||
}) | ||
|
||
it('renders null when user does not have an allowed role and no redirect is provided', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: { | ||
roles: [{ name: 'guest' }] | ||
} | ||
}) | ||
|
||
const WrappedComponent = withRole(MockComponent, ['admin', 'user']) | ||
|
||
const { container } = render(<WrappedComponent />) | ||
|
||
expect(container.firstChild).toBeNull() | ||
}) | ||
|
||
it('sets the correct display name for the wrapped component', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: { | ||
roles: [{ name: 'admin' }] | ||
} | ||
}) | ||
|
||
const WrappedComponent = withRole(MockComponent, ['admin'], '/login') | ||
|
||
render(<WrappedComponent />) | ||
|
||
expect(WrappedComponent.displayName).toBe('WithRole(MockComponent)') | ||
}) | ||
|
||
it('handles currentUser with no roles gracefully', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: { | ||
roles: [] | ||
} | ||
}) | ||
|
||
const WrappedComponent = withRole(MockComponent, ['admin'], '/login') | ||
|
||
render(<WrappedComponent />) | ||
|
||
const navigateElement = screen.getByTestId('navigate') | ||
expect(navigateElement).toBeInTheDocument() | ||
expect(navigateElement).toHaveTextContent('Navigate to /login') | ||
}) | ||
|
||
it('handles currentUser.roles being undefined gracefully', () => { | ||
useCurrentUser.mockReturnValue({ | ||
data: { | ||
// roles is undefined | ||
} | ||
}) | ||
|
||
const WrappedComponent = withRole(MockComponent, ['admin'], '/login') | ||
|
||
render(<WrappedComponent />) | ||
|
||
const navigateElement = screen.getByTestId('navigate') | ||
expect(navigateElement).toBeInTheDocument() | ||
expect(navigateElement).toHaveTextContent('Navigate to /login') | ||
}) | ||
}) |
This file contains 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,26 @@ | ||
import { Navigate } from 'react-router-dom' | ||
import { isFeatureEnabled } from '@/constants/config.js' | ||
|
||
export const withFeatureFlag = (WrappedComponent, featureFlag, redirect) => { | ||
const WithFeatureFlag = (props) => { | ||
const isEnabled = isFeatureEnabled(featureFlag) | ||
|
||
if (!isEnabled && redirect) { | ||
return <Navigate to={redirect} /> | ||
} | ||
if (!isEnabled && !redirect) { | ||
return null | ||
} | ||
|
||
return <WrappedComponent {...props} /> | ||
} | ||
|
||
// Display name for the wrapped component | ||
WithFeatureFlag.displayName = `WithFeatureFlag(${ | ||
WrappedComponent.displayName || WrappedComponent.name || 'Component' | ||
})` | ||
|
||
return WithFeatureFlag | ||
} | ||
|
||
export default withFeatureFlag |
This file contains 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
Oops, something went wrong.