mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-10 19:52:50 +00:00
feat(ui): gate the Issues feature behind the issues-feature flag (#13090)
This commit is contained in:
1 parent
ba0e78188b
commit
7f9eb2cb11
12 files changed
+318
-26
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { useFeatureFlagStateMock } = vi.hoisted(() => ({
|
||||
useFeatureFlagStateMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/feature-flags", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/feature-flags")>()),
|
||||
useFeatureFlagState: useFeatureFlagStateMock,
|
||||
}));
|
||||
|
||||
import { FEATURE_FLAG, FEATURE_FLAG_STATE } from "@/lib/feature-flags";
|
||||
|
||||
import { FeatureFlagGate } from "./feature-flag-gate";
|
||||
|
||||
const renderGate = () =>
|
||||
render(
|
||||
<FeatureFlagGate
|
||||
flag={FEATURE_FLAG.ISSUES}
|
||||
fallback={<p>off state</p>}
|
||||
pending={<p>pending state</p>}
|
||||
>
|
||||
<p>on state</p>
|
||||
</FeatureFlagGate>,
|
||||
);
|
||||
|
||||
describe("FeatureFlagGate", () => {
|
||||
beforeEach(() => {
|
||||
useFeatureFlagStateMock.mockReset();
|
||||
});
|
||||
|
||||
it("should ask for the given flag", () => {
|
||||
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.OFF);
|
||||
|
||||
renderGate();
|
||||
|
||||
expect(useFeatureFlagStateMock).toHaveBeenCalledWith("issues-feature");
|
||||
});
|
||||
|
||||
it("should render the children when the flag is on", () => {
|
||||
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.ON);
|
||||
|
||||
renderGate();
|
||||
|
||||
expect(screen.getByText("on state")).toBeInTheDocument();
|
||||
expect(screen.queryByText("off state")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("pending state")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the fallback when the flag is off", () => {
|
||||
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.OFF);
|
||||
|
||||
renderGate();
|
||||
|
||||
expect(screen.getByText("off state")).toBeInTheDocument();
|
||||
expect(screen.queryByText("on state")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the pending slot while the flag is unresolved", () => {
|
||||
useFeatureFlagStateMock.mockReturnValue(FEATURE_FLAG_STATE.PENDING);
|
||||
|
||||
renderGate();
|
||||
|
||||
expect(screen.getByText("pending state")).toBeInTheDocument();
|
||||
expect(screen.queryByText("on state")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("off state")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["off", FEATURE_FLAG_STATE.OFF],
|
||||
["pending", FEATURE_FLAG_STATE.PENDING],
|
||||
])(
|
||||
"should render nothing by default when the flag is %s",
|
||||
(_label, state) => {
|
||||
useFeatureFlagStateMock.mockReturnValue(state);
|
||||
|
||||
const { container } = render(
|
||||
<FeatureFlagGate flag={FEATURE_FLAG.ISSUES}>
|
||||
<p>on state</p>
|
||||
</FeatureFlagGate>,
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import {
|
||||
FEATURE_FLAG_STATE,
|
||||
type FeatureFlag,
|
||||
useFeatureFlagState,
|
||||
} from "@/lib/feature-flags";
|
||||
|
||||
interface FeatureFlagGateProps {
|
||||
flag: FeatureFlag;
|
||||
children: ReactNode;
|
||||
// Rendered once the flag resolves to off. Defaults to nothing.
|
||||
fallback?: ReactNode;
|
||||
// Rendered while the flag is unresolved. Defaults to nothing so an enabled
|
||||
// user never sees the off-state fallback flash before PostHog answers, and
|
||||
// a build that never initializes PostHog shows neither branch.
|
||||
pending?: ReactNode;
|
||||
}
|
||||
|
||||
// Client gate over a boolean feature flag. Server-rendered children are still
|
||||
// produced on the server (RSC payload); the gate only decides what the
|
||||
// browser shows, so keep it for presentation, not for authorization.
|
||||
export function FeatureFlagGate({
|
||||
flag,
|
||||
children,
|
||||
fallback = null,
|
||||
pending = null,
|
||||
}: FeatureFlagGateProps) {
|
||||
const state = useFeatureFlagState(flag);
|
||||
if (state === FEATURE_FLAG_STATE.ON) return <>{children}</>;
|
||||
if (state === FEATURE_FLAG_STATE.PENDING) return <>{pending}</>;
|
||||
return <>{fallback}</>;
|
||||
}
|
||||
Reference in new issue
Block a user