Files
calendar/apps/web/playwright/feature-opt-in-banner.e2e.ts
T
Eunjae LeeandGitHub ea15474edf fix: show bookings-v3 only in the settings but not in the banner (#27330)
## What does this PR do?

Adds a `displayLocations` property to the `OptInFeatureConfig` interface to control where opt-in features are displayed (settings page, banner, or both), and applies this filtering at the callers' side.

## How to test locally

```
update "Feature" set enabled = true where slug='bookings-v3';
```

Run it to globally enable the flag, and then go to the settings page to see the "Features" menu.

---

**Changes:**
- Added `OptInFeatureDisplayLocation` type with values `"settings"` | `"banner"`
- Added optional `displayLocations` property to `OptInFeatureConfig` interface
- Added helper functions:
  - `getFeatureDisplayLocations()` - returns display locations with default of `['settings']`
  - `shouldDisplayFeatureAt()` - checks if a feature should display at a specific location
  - `getOptInFeaturesForLocation()` - filters features by location
- Updated `getOptInFeaturesForScope()` to accept an optional `displayLocation` parameter for filtering
- Simplified `HAS_*_OPT_IN_FEATURES` constants to use `getOptInFeaturesForScope(scope, "settings").length > 0`
- Updated `FeatureOptInService.listFeaturesForUser()` to filter by 'settings' location
- Updated `FeatureOptInService.listFeaturesForTeam()` to filter by 'settings' location
- Updated `useFeatureOptInBanner` hook to check for 'banner' location before showing

**Default behavior:** If `displayLocations` is omitted, features default to `['settings']` only.

## Mandatory Tasks (DO NOT REMOVE)

- [x] I have self-reviewed the code (A decent size PR without self-review might be rejected).
- [x] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). N/A - internal config change only.
- [x] I confirm automated tests are in place that prove my fix is effective or that my feature works.

## How should this be tested?

1. Verify the helper functions work as expected:
   - `getFeatureDisplayLocations({ slug: "test", ... })` should return `["settings"]` (default)
   - `getFeatureDisplayLocations({ slug: "test", displayLocations: ["banner"] })` should return `["banner"]`
   - `shouldDisplayFeatureAt(feature, "settings")` should return `true` for features without `displayLocations`
   - `getOptInFeaturesForScope("user", "banner")` should only return user-scoped features with `"banner"` in their `displayLocations`

2. Verify caller-side filtering:
   - `listFeaturesForUser()` and `listFeaturesForTeam()` should only return features with 'settings' in displayLocations
   - Banner hook should only show features with 'banner' in displayLocations
   - `HAS_*_OPT_IN_FEATURES` constants should only be true if there are features with 'settings' location

## Checklist

- [x] My code follows the style guidelines of this project
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] My changes generate no new warnings

---

### Human Review Checklist
- [ ] Verify the default behavior (defaulting to `['settings']`) matches requirements
- [ ] Confirm the filtering logic is applied correctly in all callers (service methods, banner hook, constants)
- [ ] Verify the banner hook correctly prevents showing features without 'banner' in displayLocations
- [ ] Note: The test mock returns all features regardless of `displayLocation` parameter - the filtering logic in `getOptInFeaturesForScope` isn't directly tested. Consider if this is acceptable or if tests should be added.

**Link to Devin run:** https://app.devin.ai/sessions/a064ee43a56d458caf2892b55959f1ea
**Requested by:** @eunjae-lee
2026-01-29 10:17:50 +01:00

66 lines
2.4 KiB
TypeScript

import { getOptInFeatureConfig, shouldDisplayFeatureAt } from "@calcom/features/feature-opt-in/config";
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
test.afterEach(({ users }) => users.deleteAll());
test.describe("Feature Opt-In Banner", () => {
test("shows banner on bookings page and allows user to opt-in to bookings-v3 feature", async ({
page,
users,
prisma,
}) => {
const featureConfig = getOptInFeatureConfig("bookings-v3");
if (!featureConfig || !shouldDisplayFeatureAt(featureConfig, "banner")) {
return;
}
// Enable the bookings-v3 feature flag globally in the database
// This is required for the banner to show (globalEnabled must be true)
// Use upsert to ensure the feature exists and is enabled
await prisma.feature.upsert({
where: { slug: "bookings-v3" },
update: { enabled: true },
create: { slug: "bookings-v3", enabled: true, type: "OPERATIONAL" },
});
// Create a user WITHOUT the bookings-v3 feature flag enabled
// By passing an empty array, we override the default user feature flags
const user = await users.create({
userFeatureFlags: [],
});
await user.apiLogin();
// Navigate to the bookings page
await page.goto("/bookings/upcoming");
// Wait for the banner to appear
const banner = page.getByTestId("feature-opt-in-banner");
await expect(banner).toBeVisible({ timeout: 15000 });
// Click the "Try it" button to open the confirmation dialog
const tryItButton = page.getByTestId("feature-opt-in-banner-try-it");
await tryItButton.click();
// Verify the confirmation dialog appears
const confirmDialog = page.getByTestId("feature-opt-in-confirm-dialog");
await expect(confirmDialog).toBeVisible();
// Click the "Enable" button to opt-in
const enableButton = page.getByTestId("feature-opt-in-confirm-dialog-enable");
await enableButton.click();
// Verify the success dialog appears
const successDialogTitle = page.getByTestId("feature-opt-in-success-dialog-title");
await expect(successDialogTitle).toBeVisible();
// Click "View Settings" button to navigate to the settings page
const viewSettingsButton = page.getByTestId("feature-opt-in-success-dialog-view-settings");
await viewSettingsButton.click();
// Verify we are redirected to the features settings page
await expect(page).toHaveURL(/\/settings\/my-account\/features/);
});
});