mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
Keep Pro runtime wording out of hidden-commercial sessions
Demo and white-label sessions hide commercial surfaces, but a licensed install on the community runtime still saw "Pulse Pro runtime required" on the Container update risk card and, inside the Patrol header's mode disclosure, "Install the Pulse Pro runtime" beside a disabled paid-mode picker. The shared availability presentation checked the runtime lock before the hidden-commercial policy, and the Patrol header passed a constant true for that policy, so the order could not be fixed without breaking the header's runtime prompt for everyone else. Hidden commercial surfaces now win over the runtime lock, and the Patrol header and banners pass the session's real policy. Sessions that show commercial surfaces keep the runtime prompt and download link unchanged.
This commit is contained in:
@@ -3191,7 +3191,10 @@ a new API state machine, queue contract, or verification-accounting field.
|
||||
`alert_triggered_analysis` stays the licence-masked API value while the
|
||||
runtime-capabilities `ai_alerts` grant or `paid_runtime_required` block
|
||||
decides which lock explanation the Patrol settings card renders, without a
|
||||
new API field
|
||||
new API field. A `paid_runtime_required` block is presentation input only:
|
||||
when the security-status presentation policy hides commercial surfaces,
|
||||
Patrol mode and container update risk both render their neutral lock and
|
||||
the browser must not surface the block's Pro download URL
|
||||
and the Patrol run-record copy, so `finding_ids` remains the API-owned
|
||||
fail-closed scoping input while the frontend presents selected history as a
|
||||
Patrol run record instead of a generic findings filter or snapshot workflow
|
||||
|
||||
@@ -4743,6 +4743,11 @@ the card that owns it, not through the shared `Toggle` `locked` and
|
||||
`lockedMessage` props, which the primitive does not render. `AISettings.tsx`
|
||||
renders the Container update risk lock from the canonical Patrol availability
|
||||
presentation and links that explanation to the switch with `ariaDescribedBy`.
|
||||
The shared Patrol feature shells (`PatrolIntelligenceHeader.tsx` and
|
||||
`PatrolIntelligenceBanners.tsx`) and that card pass the session's real
|
||||
commercial and upgrade presentation policy into the presentation, never a
|
||||
constant, so a session that hides commercial surfaces gets the neutral lock
|
||||
line and never Pulse Pro runtime wording or a download link.
|
||||
That same route-owned presentation rule also governs Patrol findings empty
|
||||
states: shared section shells under `frontend-modern/src/features/patrol/`
|
||||
must not render a green healthy empty state from `0 active findings` alone
|
||||
|
||||
@@ -726,15 +726,20 @@ attention`, `approval needed`, `outcome verified`, `no active work`) instead
|
||||
infrastructure-unchanged caveats in the default view, or imply Patrol's main
|
||||
value is a manual review chore.
|
||||
Plan-gated Patrol capabilities share that one availability presentation:
|
||||
`patrolAutonomyAvailability.ts` owns the precedence (runtime lock, then
|
||||
hidden commercial surfaces, then plan lock) and the only action labels
|
||||
`patrolAutonomyAvailability.ts` owns the precedence (hidden commercial
|
||||
surfaces, then runtime lock, then plan lock) and the only action labels
|
||||
(`Open Pro downloads`, `Plans & Billing`), while each capability supplies
|
||||
its lock state and copy. The Patrol settings Container update risk trigger
|
||||
(`ai_alerts`) uses `getPatrolAlertAnalysisAvailabilityPresentation`, so a
|
||||
locked install sees why the trigger is unavailable beside the disabled
|
||||
switch: a higher license plan, a missing Pulse Pro runtime, or, when
|
||||
commercial surfaces are hidden, a neutral not-available line with no plan
|
||||
or Pro wording. Actions appear only when upgrade prompts are allowed.
|
||||
its lock state and copy. Hidden commercial surfaces win over the runtime
|
||||
lock, so demo and white-label sessions never see Pulse Pro runtime wording,
|
||||
and every caller (the Patrol header, the Patrol banners and Patrol
|
||||
settings) passes the session's real `presentationPolicyHidesCommercialSurfaces()`
|
||||
and `presentationPolicyHidesUpgradePrompts()` values instead of a constant.
|
||||
The Patrol settings Container update risk trigger (`ai_alerts`) uses
|
||||
`getPatrolAlertAnalysisAvailabilityPresentation`, so a locked install sees
|
||||
why the trigger is unavailable beside the disabled switch: a higher
|
||||
license plan, a missing Pulse Pro runtime, or, when commercial surfaces are
|
||||
hidden, a neutral not-available line with no plan or Pro wording. Actions
|
||||
appear only when upgrade prompts are allowed.
|
||||
5. Add or change Patrol header schedule and runtime presentation through `frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx`, `frontend-modern/src/utils/aiPatrolSchedulePresentation.ts`, and `frontend-modern/src/utils/patrolRuntimePresentation.ts`.
|
||||
Patrol must not surface retired hosted-model credit badges or trial-like activation prompts in the normal self-hosted GA app, even when legacy transport fields are still present.
|
||||
6. Keep Patrol and chat identifier-label presentation aligned through the shared `frontend-modern/src/utils/textPresentation.ts`
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "357b1a505e80bb1cafbfc9cb02f36f96182c7b57",
|
||||
"verified_at": "2026-10-05T21:37:58Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx",
|
||||
"frontend-modern/src/features/patrol/patrolAutonomyAvailability.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx": "89bce4990f82104e8724e4e099a3ae06a275cf1a2d5ab875670f9bc3b4cefcff",
|
||||
"frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx": "6b899aaebeba642fd5bb792146f27302d29542a4f4ced548f71168398101faf3",
|
||||
"frontend-modern/src/features/patrol/patrolAutonomyAvailability.ts": "3d334beb4135c9c53027b79debd869c403db627f207bfb54ab004d5d191f99b0"
|
||||
},
|
||||
"routes": [
|
||||
"/settings/pulse-intelligence/patrol",
|
||||
"/patrol"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1280,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Private community-runtime stack built from this tree (backend 7782, Vite 5282 serving this checkout), mock data, dev-signed licences switched live on the same backend. Playwright Chromium at 1280x900 dark and 390x844 light.",
|
||||
"Enterprise licence on the community runtime (white-label: hideCommercial and hideUpgrade true; ai_alerts and ai_autofix blocked as paid_runtime_required): the Container update risk card reads 'Not available. This install does not include container update risk.' with no link, and the Patrol header shows the plain Watch only line with no Mode and automation disclosure, no paid-mode picker, no Pulse Pro runtime wording and no download link.",
|
||||
"Same stack and licence with the previous patrolAutonomyAvailability.ts, PatrolIntelligenceHeader.tsx and PatrolIntelligenceBanners.tsx swapped back in under the running Vite: the card showed 'Pulse Pro runtime required...' and the expanded Mode and automation disclosure showed 'Install the Pulse Pro runtime to use Patrol modes' beside a disabled paid-mode picker. Restored afterwards.",
|
||||
"Pro licence on the community runtime (commercial and upgrade prompts visible): unchanged. Card shows the runtime lock with Open Pro downloads; the Patrol page still shows the runtime prompt and the https://pulserelay.pro/download.html link with the mode picker.",
|
||||
"Free community install (licence cleared; hideUpgrade true, hideCommercial false): unchanged. Card shows the higher-license-plan line with no link; Patrol header shows plain Watch only.",
|
||||
"No horizontal overflow and no page errors in any state."
|
||||
],
|
||||
"interactions": [
|
||||
"Expanded the Patrol header 'Mode and automation' disclosure where present to read the runtime note and mode picker.",
|
||||
"Activated an Enterprise licence, a Pro licence and cleared the licence on the same backend, reloading both routes for each state."
|
||||
]
|
||||
}
|
||||
@@ -441,6 +441,32 @@ describe('AISettings model loading error states', () => {
|
||||
).toHaveAttribute('href', 'https://pulserelay.pro/download.html');
|
||||
});
|
||||
|
||||
it('keeps Pro runtime wording out when commercial surfaces are hidden', async () => {
|
||||
hasFeatureMock.mockImplementation((feature: string) => feature !== 'ai_alerts');
|
||||
presentationPolicyHidesCommercialSurfacesMock.mockReturnValue(true);
|
||||
presentationPolicyHidesUpgradePromptsMock.mockReturnValue(true);
|
||||
getRuntimeCapabilityBlockMock.mockImplementation((feature: string) =>
|
||||
feature === 'ai_alerts'
|
||||
? {
|
||||
key: 'ai_alerts',
|
||||
reason: 'paid_runtime_required',
|
||||
action_url: 'https://pulserelay.pro/download.html',
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
getSettingsMock.mockResolvedValue(lockedPatrolSettings());
|
||||
|
||||
renderComponent('patrol');
|
||||
|
||||
await screen.findByLabelText('Enable container update risk analysis');
|
||||
expect(containerRiskToggle()).toBeDisabled();
|
||||
expect(lockExplanation()).toHaveTextContent(
|
||||
'Not available. This install does not include container update risk.',
|
||||
);
|
||||
expect(lockExplanation().textContent).not.toMatch(/\bPro\b|runtime|plan/i);
|
||||
expect(within(lockExplanation()).queryByRole('link')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows no lock explanation when the install includes the feature', async () => {
|
||||
getSettingsMock.mockResolvedValue({
|
||||
...lockedPatrolSettings(),
|
||||
|
||||
@@ -6,7 +6,10 @@ import SettingsIcon from 'lucide-solid/icons/settings';
|
||||
import { AnimatedNumber } from '@/components/shared/AnimatedNumber';
|
||||
import { UpgradeButtonLink } from '@/components/shared/UpgradeLink';
|
||||
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
|
||||
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
|
||||
import {
|
||||
presentationPolicyHidesCommercialSurfaces,
|
||||
presentationPolicyHidesUpgradePrompts,
|
||||
} from '@/stores/sessionPresentationPolicy';
|
||||
import { formatRelativeTime } from '@/utils/format';
|
||||
import { getAIProviderDisplayName } from '@/utils/aiProviderPresentation';
|
||||
import { getPatrolSetupAction } from '@/utils/patrolRuntimeActions';
|
||||
@@ -35,6 +38,8 @@ export function PatrolIntelligenceBanners(props: { state: PatrolIntelligenceStat
|
||||
const autonomyAvailability = createMemo(() =>
|
||||
getPatrolAutonomyAvailabilityPresentation({
|
||||
autoFixLocked: state.autoFixLocked(),
|
||||
commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),
|
||||
upgradePromptsHidden: presentationPolicyHidesUpgradePrompts(),
|
||||
runtimeCapabilityBlock: state.autoFixCapabilityBlock(),
|
||||
runtime: state.licenseRuntimeIdentity(),
|
||||
planUpgradeDestination: getUpgradeActionDestination('ai_autofix'),
|
||||
|
||||
@@ -13,7 +13,10 @@ import { UpgradeButtonLink } from '@/components/shared/UpgradeLink';
|
||||
import type { PatrolAutonomyLevel } from '@/api/patrol';
|
||||
import { settingsTabPath } from '@/components/Settings/settingsNavigationModel';
|
||||
import { getUpgradeActionDestination } from '@/stores/licenseCommercial';
|
||||
import { presentationPolicyHidesUpgradePrompts } from '@/stores/sessionPresentationPolicy';
|
||||
import {
|
||||
presentationPolicyHidesCommercialSurfaces,
|
||||
presentationPolicyHidesUpgradePrompts,
|
||||
} from '@/stores/sessionPresentationPolicy';
|
||||
import { formatRelativeTime } from '@/utils/format';
|
||||
import { getPatrolPageHeaderMeta } from '@/utils/patrolPagePresentation';
|
||||
import { getPatrolTriggerStatusSummary } from '@/utils/patrolRunPresentation';
|
||||
@@ -146,7 +149,7 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
|
||||
getPatrolAutonomyAvailabilityPresentation({
|
||||
autoFixLocked: state.autoFixLocked(),
|
||||
upgradePromptsHidden: upgradePromptsHidden(),
|
||||
commercialSurfacesHidden: true,
|
||||
commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),
|
||||
runtimeCapabilityBlock: state.autoFixCapabilityBlock(),
|
||||
runtime: state.licenseRuntimeIdentity(),
|
||||
planUpgradeDestination: getUpgradeActionDestination('ai_autofix'),
|
||||
|
||||
@@ -251,7 +251,10 @@ describe('PatrolIntelligenceHeader', () => {
|
||||
external: false,
|
||||
},
|
||||
});
|
||||
expect(headerSource).toContain('commercialSurfacesHidden: true');
|
||||
expect(headerSource).toContain(
|
||||
'commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),',
|
||||
);
|
||||
expect(headerSource).not.toContain('commercialSurfacesHidden: true');
|
||||
expect(headerSource).not.toContain('showAutonomyPlanBillingAction');
|
||||
expect(headerSource).not.toContain('CreditCardIcon');
|
||||
expect(headerSource).not.toContain('Plans & Billing');
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('patrolAutonomyAvailability', () => {
|
||||
|
||||
it.each([
|
||||
[
|
||||
'runtime_locked beats commercialSurfacesHidden',
|
||||
'commercialSurfacesHidden beats runtime_locked',
|
||||
{
|
||||
autoFixLocked: true,
|
||||
runtimeCapabilityBlock: runtimeBlock(),
|
||||
@@ -102,6 +102,17 @@ describe('patrolAutonomyAvailability', () => {
|
||||
upgradePromptsHidden: true,
|
||||
planUpgradeDestination: planDestination,
|
||||
},
|
||||
'plan_locked',
|
||||
],
|
||||
[
|
||||
'runtime_locked when commercial surfaces are visible',
|
||||
{
|
||||
autoFixLocked: true,
|
||||
runtimeCapabilityBlock: runtimeBlock(),
|
||||
runtime: runtime(),
|
||||
upgradePromptsHidden: true,
|
||||
planUpgradeDestination: planDestination,
|
||||
},
|
||||
'runtime_locked',
|
||||
],
|
||||
[
|
||||
@@ -425,6 +436,40 @@ describe('patrolAutonomyAvailability', () => {
|
||||
expect(alertAnalysis.destination).toEqual(patrolMode.destination);
|
||||
});
|
||||
|
||||
it('keeps Pro runtime wording out of hidden-commercial sessions for both capabilities', () => {
|
||||
const input = {
|
||||
commercialSurfacesHidden: true,
|
||||
upgradePromptsHidden: true,
|
||||
runtimeCapabilityBlock: runtimeBlock({ action_url: 'https://pro.example/downloads' }),
|
||||
runtime: runtime({ label: 'Pulse Community runtime' }),
|
||||
planUpgradeDestination: planDestination,
|
||||
};
|
||||
const alertAnalysis = getPatrolAlertAnalysisAvailabilityPresentation({
|
||||
...input,
|
||||
alertAnalysisLocked: true,
|
||||
});
|
||||
const patrolMode = getPatrolAutonomyAvailabilityPresentation({
|
||||
...input,
|
||||
autoFixLocked: true,
|
||||
});
|
||||
|
||||
expect(alertAnalysis).toEqual({
|
||||
kind: 'plan_locked',
|
||||
locked: true,
|
||||
title: 'Not available',
|
||||
body: 'This install does not include container update risk.',
|
||||
});
|
||||
expect(patrolMode).toEqual({
|
||||
kind: 'plan_locked',
|
||||
locked: true,
|
||||
title: 'Watch only',
|
||||
body: 'This install watches infrastructure and shows issues.',
|
||||
});
|
||||
for (const result of [alertAnalysis, patrolMode]) {
|
||||
expect(`${result.title} ${result.body}`).not.toMatch(/\bPro\b|runtime|plan/i);
|
||||
}
|
||||
});
|
||||
|
||||
it('omits the runtime download action when upgrade prompts are hidden', () => {
|
||||
const result = getPatrolAlertAnalysisAvailabilityPresentation({
|
||||
alertAnalysisLocked: true,
|
||||
|
||||
@@ -16,7 +16,13 @@ describe('patrol commercial boundary', () => {
|
||||
expect(patrolIntelligenceHeaderSource).toContain(
|
||||
"autonomyAvailability().kind === 'runtime_locked'",
|
||||
);
|
||||
expect(patrolIntelligenceHeaderSource).toContain('commercialSurfacesHidden: true');
|
||||
expect(patrolIntelligenceHeaderSource).toContain(
|
||||
'commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),',
|
||||
);
|
||||
expect(patrolIntelligenceHeaderSource).not.toContain('commercialSurfacesHidden: true');
|
||||
expect(patrolIntelligenceBannersSource).toContain(
|
||||
'commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),',
|
||||
);
|
||||
expect(patrolIntelligenceHeaderSource).not.toContain('showAutonomyPlanBillingAction');
|
||||
expect(patrolAutonomyAvailabilitySource).toContain('Plans & Billing');
|
||||
expect(patrolAutonomyAvailabilitySource).toContain('input.upgradePromptsHidden');
|
||||
|
||||
@@ -10,8 +10,9 @@ export type PatrolFeatureAvailabilityKind = 'available' | 'plan_locked' | 'runti
|
||||
|
||||
// Presentation policy shared by every plan-gated Patrol capability. Each
|
||||
// capability supplies only its lock state and its own copy; the precedence
|
||||
// (runtime lock, then hidden commercial surfaces, then plan lock) and the
|
||||
// action labels stay canonical here.
|
||||
// (hidden commercial surfaces, then runtime lock, then plan lock) and the
|
||||
// action labels stay canonical here. Hidden commercial surfaces win over the
|
||||
// runtime lock so demo and white-label sessions never see Pro wording.
|
||||
interface PatrolFeatureAvailabilityPolicy {
|
||||
upgradePromptsHidden?: boolean;
|
||||
commercialSurfacesHidden?: boolean;
|
||||
@@ -100,6 +101,14 @@ function getPatrolFeatureAvailabilityPresentation(
|
||||
};
|
||||
}
|
||||
|
||||
if (input.commercialSurfacesHidden) {
|
||||
return {
|
||||
kind: 'plan_locked',
|
||||
locked: true,
|
||||
...(copy.planLockedCommercialHidden ?? copy.planLocked),
|
||||
};
|
||||
}
|
||||
|
||||
if (input.runtimeCapabilityBlock?.reason === PATROL_AUTONOMY_RUNTIME_REQUIRED_REASON) {
|
||||
return {
|
||||
kind: 'runtime_locked',
|
||||
@@ -117,14 +126,6 @@ function getPatrolFeatureAvailabilityPresentation(
|
||||
};
|
||||
}
|
||||
|
||||
if (input.commercialSurfacesHidden) {
|
||||
return {
|
||||
kind: 'plan_locked',
|
||||
locked: true,
|
||||
...(copy.planLockedCommercialHidden ?? copy.planLocked),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'plan_locked',
|
||||
locked: true,
|
||||
|
||||
@@ -1259,6 +1259,64 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
expect(screen.queryByRole('button', { name: /start free trial/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Patrol mode runtime lock', () => {
|
||||
const useLicensedCommunityRuntime = () => {
|
||||
getRuntimeCapabilityBlockMock.mockImplementation((feature: string) =>
|
||||
feature === 'ai_autofix'
|
||||
? {
|
||||
key: 'ai_autofix',
|
||||
reason: 'paid_runtime_required',
|
||||
action_url: 'https://pulserelay.pro/download.html',
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
runtimeCapabilitiesMock.mockReturnValue({
|
||||
runtime: { build: 'community', label: 'Pulse Community runtime' },
|
||||
capabilities: ['ai_intelligence'],
|
||||
blocked_capabilities: [
|
||||
{ key: 'ai_autofix', reason: 'paid_runtime_required' },
|
||||
{ key: 'ai_alerts', reason: 'paid_runtime_required' },
|
||||
],
|
||||
});
|
||||
getPatrolStatusMock.mockResolvedValue(defaultPatrolStatus({ license_required: true }));
|
||||
};
|
||||
|
||||
it('points a licensed community runtime at the Pro runtime download', async () => {
|
||||
useLicensedCommunityRuntime();
|
||||
|
||||
render(() => <AIIntelligence />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText(/Pulse Pro runtime required/).length).toBeGreaterThan(0);
|
||||
});
|
||||
const downloads = screen.getAllByRole('link', { name: 'Open Pro downloads' });
|
||||
expect(downloads.length).toBeGreaterThan(0);
|
||||
for (const link of downloads) {
|
||||
expect(link).toHaveAttribute('href', 'https://pulserelay.pro/download.html');
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps Pro runtime wording off the page when commercial surfaces are hidden', async () => {
|
||||
useLicensedCommunityRuntime();
|
||||
presentationPolicyHidesCommercialSurfacesMock.mockReturnValue(true);
|
||||
presentationPolicyHidesUpgradePromptsMock.mockReturnValue(true);
|
||||
|
||||
render(() => <AIIntelligence />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getPatrolStatusMock).toHaveBeenCalled();
|
||||
});
|
||||
const patrolControlAnchor = document.getElementById(PATROL_CONTROL_ANCHOR);
|
||||
expect(patrolControlAnchor).not.toBeNull();
|
||||
const patrolControl = within(patrolControlAnchor!);
|
||||
expect(patrolControl.getAllByText('Watch only').length).toBeGreaterThan(0);
|
||||
expect(patrolControl.queryByRole('group', { name: 'Patrol mode' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Pulse Pro runtime/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Install the Pulse Pro runtime/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('link', { name: 'Open Pro downloads' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the Patrol model catalog out of the operator page', async () => {
|
||||
apiFetchJSONMock.mockImplementation(async (path: string) => {
|
||||
if (path === '/api/ai/models') {
|
||||
|
||||
@@ -3596,7 +3596,10 @@ describe('frontend resource type boundaries', () => {
|
||||
expect(patrolIntelligenceBannersSource).toContain('state.licenseRequired()');
|
||||
expect(patrolIntelligenceHeaderSource).toContain('presentationPolicyHidesUpgradePrompts');
|
||||
expect(patrolIntelligenceHeaderSource).toContain('!presentationPolicyHidesUpgradePrompts()');
|
||||
expect(patrolIntelligenceHeaderSource).toContain('commercialSurfacesHidden: true');
|
||||
expect(patrolIntelligenceHeaderSource).toContain(
|
||||
'commercialSurfacesHidden: presentationPolicyHidesCommercialSurfaces(),',
|
||||
);
|
||||
expect(patrolIntelligenceHeaderSource).not.toContain('commercialSurfacesHidden: true');
|
||||
expect(patrolIntelligenceHeaderSource).not.toContain('Plans & Billing');
|
||||
expect(patrolIntelligenceHeaderSource).toContain(
|
||||
"from '@/components/shared/FilterButtonGroup'",
|
||||
|
||||
Reference in New Issue
Block a user