fix: remove Update channel from Settings, keep only Release channel
Update channel (Stable/Canary) was redundant since builds are flat. Removed the UI dropdown, canary update logic in useUpdater, and update_channel field from Settings type (TS + Rust). Release channel (Alpha/Beta/Stable) remains as the sole selector. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -551,15 +551,11 @@ Managed by `useSettings` hook and `SettingsPanel` component.
|
||||
|
||||
---
|
||||
|
||||
## Update Channels & Feature Flags
|
||||
|
||||
### Settings
|
||||
- **`update_channel`** — `"stable"` (default/null) or `"canary"`. Stored in `Settings` struct, configurable in Settings panel under "Updates" section.
|
||||
## Updates & Feature Flags
|
||||
|
||||
### Hooks
|
||||
- **`useUpdater(channel?)`** — Checks for updates. For stable: uses Tauri updater plugin. For canary: fetches `latest-canary.json` and opens release page for download.
|
||||
- **`useUpdater()`** — Checks for updates using the Tauri updater plugin. Automatic download and install.
|
||||
- **`useFeatureFlag(flag)`** — Returns boolean for a named feature flag. Checks `localStorage` override (`ff_<name>`), then falls back to compile-time default. Type-safe via `FeatureFlagName` union.
|
||||
|
||||
### CI/CD
|
||||
- **`.github/workflows/release.yml`** — Stable builds from `main`. Produces `latest.json` on GitHub Pages.
|
||||
- **`.github/workflows/release-canary.yml`** — Canary builds from `canary` branch. Produces `latest-canary.json` on GitHub Pages. Releases are marked as prerelease.
|
||||
|
||||
@@ -802,29 +802,13 @@ sequenceDiagram
|
||||
- **Settings:** `telemetry_consent`, `crash_reporting_enabled`, `analytics_enabled`, `anonymous_id` in `Settings` struct
|
||||
- **Consent:** `TelemetryConsentDialog` shown when `telemetry_consent === null`
|
||||
|
||||
### Update Channels (Stable / Canary)
|
||||
### Updates
|
||||
|
||||
Laputa supports two release channels:
|
||||
Laputa uses the Tauri updater plugin for automatic updates:
|
||||
|
||||
- **Stable** (default): builds from `main` branch, published as full GitHub Releases
|
||||
- **Canary**: builds from `canary` branch, published as pre-release GitHub Releases
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
main["main branch"] -->|push| stable["Stable build<br/>latest.json"]
|
||||
canary["canary branch"] -->|push| canaryBuild["Canary build<br/>latest-canary.json"]
|
||||
stable --> ghPages["GitHub Pages"]
|
||||
canaryBuild --> ghPages
|
||||
ghPages -->|"update_channel = stable"| stableUsers["Stable users<br/>(auto-update via plugin)"]
|
||||
ghPages -->|"update_channel = canary"| canaryUsers["Canary users<br/>(fetch + manual download)"]
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- Both channels publish to GitHub Pages: `latest.json` (stable) and `latest-canary.json` (canary)
|
||||
- `update_channel` is stored in `Settings` (`settings.json`), configurable in Settings panel
|
||||
- **Stable**: uses the Tauri updater plugin with automatic download and install
|
||||
- **Canary**: `useUpdater` hook fetches `latest-canary.json` via HTTP, compares versions, and opens the GitHub release page for manual download
|
||||
- Canary versions use semver prerelease: `0.YYYYMMDD.N-canary`
|
||||
- Builds from `main` branch are published as GitHub Releases
|
||||
- `latest.json` is published to GitHub Pages for the updater plugin
|
||||
- `useUpdater()` hook checks for updates automatically and supports download + install
|
||||
|
||||
### Feature Flags (PostHog + Release Channels)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ pub struct Settings {
|
||||
pub crash_reporting_enabled: Option<bool>,
|
||||
pub analytics_enabled: Option<bool>,
|
||||
pub anonymous_id: Option<String>,
|
||||
pub update_channel: Option<String>,
|
||||
pub release_channel: Option<String>,
|
||||
}
|
||||
|
||||
@@ -64,10 +63,6 @@ fn save_settings_at(path: &PathBuf, settings: Settings) -> Result<(), String> {
|
||||
.anonymous_id
|
||||
.map(|k| k.trim().to_string())
|
||||
.filter(|k| !k.is_empty()),
|
||||
update_channel: settings
|
||||
.update_channel
|
||||
.map(|k| k.trim().to_string())
|
||||
.filter(|k| !k.is_empty()),
|
||||
release_channel: settings
|
||||
.release_channel
|
||||
.map(|k| k.trim().to_string())
|
||||
@@ -141,7 +136,6 @@ mod tests {
|
||||
assert!(s.crash_reporting_enabled.is_none());
|
||||
assert!(s.analytics_enabled.is_none());
|
||||
assert!(s.anonymous_id.is_none());
|
||||
assert!(s.update_channel.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -69,7 +69,7 @@ const mockCommandResults: Record<string, unknown> = {
|
||||
get_modified_files: [],
|
||||
get_note_content: mockAllContent['/vault/project/test.md'] || '',
|
||||
get_file_history: [],
|
||||
get_settings: { openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, telemetry_consent: true, crash_reporting_enabled: null, analytics_enabled: null, anonymous_id: null, update_channel: null },
|
||||
get_settings: { openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, telemetry_consent: true, crash_reporting_enabled: null, analytics_enabled: null, anonymous_id: null, release_channel: null },
|
||||
git_pull: { status: 'up_to_date', message: 'Already up to date', updatedFiles: [], conflictFiles: [] },
|
||||
save_settings: null,
|
||||
check_vault_exists: true,
|
||||
|
||||
@@ -412,7 +412,7 @@ function App() {
|
||||
const zoom = useZoom()
|
||||
const buildNumber = useBuildNumber()
|
||||
|
||||
const { status: updateStatus, actions: updateActions } = useUpdater(settings.update_channel)
|
||||
const { status: updateStatus, actions: updateActions } = useUpdater()
|
||||
|
||||
const handleCheckForUpdates = useCallback(async () => {
|
||||
if (updateStatus.state === 'downloading') {
|
||||
|
||||
@@ -28,7 +28,7 @@ const emptySettings: Settings = {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
const populatedSettings: Settings = {
|
||||
@@ -41,7 +41,7 @@ const populatedSettings: Settings = {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
describe('SettingsPanel', () => {
|
||||
@@ -405,56 +405,6 @@ describe('SettingsPanel', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Update channel section', () => {
|
||||
it('renders the update channel dropdown', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
expect(screen.getByTestId('settings-update-channel')).toBeInTheDocument()
|
||||
expect(screen.getByText('Updates')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('defaults to stable when update_channel is null', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
const select = screen.getByTestId('settings-update-channel') as HTMLSelectElement
|
||||
expect(select.value).toBe('stable')
|
||||
})
|
||||
|
||||
it('reflects canary setting', () => {
|
||||
const canarySettings: Settings = { ...emptySettings, update_channel: 'canary' }
|
||||
render(
|
||||
<SettingsPanel open={true} settings={canarySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
const select = screen.getByTestId('settings-update-channel') as HTMLSelectElement
|
||||
expect(select.value).toBe('canary')
|
||||
})
|
||||
|
||||
it('saves update_channel when changed to canary', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
fireEvent.change(screen.getByTestId('settings-update-channel'), { target: { value: 'canary' } })
|
||||
fireEvent.click(screen.getByTestId('settings-save'))
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
update_channel: 'canary',
|
||||
}))
|
||||
})
|
||||
|
||||
it('saves null when channel is stable (default)', () => {
|
||||
render(
|
||||
<SettingsPanel open={true} settings={emptySettings} onSave={onSave} onClose={onClose} />
|
||||
)
|
||||
fireEvent.click(screen.getByTestId('settings-save'))
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
update_channel: null,
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
describe('Privacy & Telemetry section', () => {
|
||||
it('renders crash reporting and analytics toggles', () => {
|
||||
render(
|
||||
|
||||
@@ -125,7 +125,6 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit<SettingsPanelPro
|
||||
const [githubToken, setGithubToken] = useState(settings.github_token)
|
||||
const [githubUsername, setGithubUsername] = useState(settings.github_username)
|
||||
const [pullInterval, setPullInterval] = useState(settings.auto_pull_interval_minutes ?? 5)
|
||||
const [updateChannel, setUpdateChannel] = useState(settings.update_channel ?? 'stable')
|
||||
const [releaseChannel, setReleaseChannel] = useState(settings.release_channel ?? 'stable')
|
||||
const [crashReporting, setCrashReporting] = useState(settings.crash_reporting_enabled ?? false)
|
||||
const [analytics, setAnalytics] = useState(settings.analytics_enabled ?? false)
|
||||
@@ -150,9 +149,8 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit<SettingsPanelPro
|
||||
crash_reporting_enabled: crashReporting,
|
||||
analytics_enabled: analytics,
|
||||
anonymous_id: (crashReporting || analytics) ? (settings.anonymous_id ?? crypto.randomUUID()) : settings.anonymous_id,
|
||||
update_channel: updateChannel === 'stable' ? null : updateChannel,
|
||||
release_channel: releaseChannel === 'stable' ? null : releaseChannel,
|
||||
}), [openaiKey, googleKey, githubToken, githubUsername, pullInterval, updateChannel, releaseChannel, crashReporting, analytics, settings.telemetry_consent, settings.anonymous_id])
|
||||
}), [openaiKey, googleKey, githubToken, githubUsername, pullInterval, releaseChannel, crashReporting, analytics, settings.telemetry_consent, settings.anonymous_id])
|
||||
|
||||
const handleSave = () => {
|
||||
const prevAnalytics = settings.analytics_enabled ?? false
|
||||
@@ -206,7 +204,6 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit<SettingsPanelPro
|
||||
githubToken={githubToken ?? null} githubUsername={githubUsername ?? null}
|
||||
onGitHubConnected={handleGitHubConnected} onGitHubDisconnect={handleGitHubDisconnect}
|
||||
pullInterval={pullInterval} setPullInterval={setPullInterval}
|
||||
updateChannel={updateChannel} setUpdateChannel={setUpdateChannel}
|
||||
releaseChannel={releaseChannel} setReleaseChannel={setReleaseChannel}
|
||||
crashReporting={crashReporting} setCrashReporting={setCrashReporting}
|
||||
analytics={analytics} setAnalytics={setAnalytics}
|
||||
@@ -242,7 +239,6 @@ interface SettingsBodyProps {
|
||||
onGitHubConnected: (token: string, username: string) => void
|
||||
onGitHubDisconnect: () => void
|
||||
pullInterval: number; setPullInterval: (v: number) => void
|
||||
updateChannel: string; setUpdateChannel: (v: string) => void
|
||||
releaseChannel: string; setReleaseChannel: (v: string) => void
|
||||
crashReporting: boolean; setCrashReporting: (v: boolean) => void
|
||||
analytics: boolean; setAnalytics: (v: boolean) => void
|
||||
@@ -307,26 +303,12 @@ function SettingsBody(props: SettingsBodyProps) {
|
||||
<div style={{ height: 1, background: 'var(--border)' }} />
|
||||
|
||||
<div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--foreground)', marginBottom: 4 }}>Updates</div>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: 'var(--foreground)', marginBottom: 4 }}>Release Channel</div>
|
||||
<div style={{ fontSize: 12, color: 'var(--muted-foreground)', lineHeight: 1.5 }}>
|
||||
Canary builds include the latest features but may be less stable. Restart required after changing.
|
||||
Controls which features are visible. Alpha users see all features. Beta/Stable see features as they are promoted.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={{ fontSize: 12, fontWeight: 500, color: 'var(--foreground)' }}>Update channel</label>
|
||||
<select
|
||||
value={props.updateChannel}
|
||||
onChange={(e) => props.setUpdateChannel(e.target.value)}
|
||||
className="border border-border bg-transparent text-foreground rounded"
|
||||
style={{ fontSize: 13, padding: '8px 10px', outline: 'none', fontFamily: 'inherit' }}
|
||||
data-testid="settings-update-channel"
|
||||
>
|
||||
<option value="stable">Stable</option>
|
||||
<option value="canary">Canary (pre-release)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<label style={{ fontSize: 12, fontWeight: 500, color: 'var(--foreground)' }}>Release channel</label>
|
||||
<select
|
||||
@@ -340,9 +322,6 @@ function SettingsBody(props: SettingsBodyProps) {
|
||||
<option value="beta">Beta</option>
|
||||
<option value="alpha">Alpha (bleeding edge)</option>
|
||||
</select>
|
||||
<div style={{ fontSize: 11, color: 'var(--muted-foreground)', lineHeight: 1.4 }}>
|
||||
Alpha users see all features. Beta/Stable see features as they are promoted.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ height: 1, background: 'var(--border)' }} />
|
||||
|
||||
@@ -14,7 +14,6 @@ const defaultSettings: Settings = {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
@@ -28,7 +27,6 @@ const savedSettings: Settings = {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
@@ -93,7 +91,7 @@ describe('useSettings', () => {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -17,7 +17,6 @@ const EMPTY_SETTINGS: Settings = {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const baseSettings: Settings = {
|
||||
openai_key: null, google_key: null,
|
||||
github_token: null, github_username: null, auto_pull_interval_minutes: null,
|
||||
telemetry_consent: null, crash_reporting_enabled: null,
|
||||
analytics_enabled: null, anonymous_id: null, update_channel: null, release_channel: null,
|
||||
analytics_enabled: null, anonymous_id: null, release_channel: null,
|
||||
}
|
||||
|
||||
describe('useTelemetry', () => {
|
||||
|
||||
@@ -25,11 +25,6 @@ vi.mock('@tauri-apps/plugin-process', () => ({
|
||||
relaunch: (...args: unknown[]) => mockRelaunch(...args),
|
||||
}))
|
||||
|
||||
const mockGetVersion = vi.fn().mockResolvedValue('0.20260101.1')
|
||||
vi.mock('@tauri-apps/api/app', () => ({
|
||||
getVersion: () => mockGetVersion(),
|
||||
}))
|
||||
|
||||
import { isTauri } from '../mock-tauri'
|
||||
|
||||
describe('useUpdater', () => {
|
||||
@@ -205,42 +200,6 @@ describe('useUpdater', () => {
|
||||
expect(mockDownload).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('canary channel', () => {
|
||||
it('fetches latest-canary.json when channel is canary', async () => {
|
||||
vi.mocked(isTauri).mockReturnValue(true)
|
||||
mockGetVersion.mockResolvedValue('0.20260101.1')
|
||||
|
||||
const mockFetch = vi.mocked(globalThis.fetch)
|
||||
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({
|
||||
version: '0.20260325.99-canary',
|
||||
notes: 'Canary build',
|
||||
platforms: {
|
||||
'darwin-aarch64': {
|
||||
url: 'https://github.com/refactoringhq/laputa-app/releases/download/v0.20260325.99-canary/laputa.app.tar.gz',
|
||||
signature: 'sig123',
|
||||
},
|
||||
},
|
||||
}), { status: 200 }))
|
||||
|
||||
const { result } = renderHook(() => useUpdater('canary'))
|
||||
|
||||
let checkResult: string | undefined
|
||||
await act(async () => {
|
||||
checkResult = await result.current.actions.checkForUpdates()
|
||||
})
|
||||
|
||||
expect(checkResult).toBe('available')
|
||||
expect(result.current.status).toEqual({
|
||||
state: 'available',
|
||||
version: '0.20260325.99-canary',
|
||||
notes: 'Canary build',
|
||||
})
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
'https://refactoringhq.github.io/laputa-app/latest-canary.json'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkForUpdates (manual)', () => {
|
||||
it('returns up-to-date when no update is available', async () => {
|
||||
vi.mocked(isTauri).mockReturnValue(true)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { isTauri } from '../mock-tauri'
|
||||
import { openExternalUrl } from '../utils/url'
|
||||
|
||||
const RELEASE_NOTES_URL = 'https://refactoringhq.github.io/laputa-app/'
|
||||
const CANARY_ENDPOINT = 'https://refactoringhq.github.io/laputa-app/latest-canary.json'
|
||||
|
||||
export type UpdateStatus =
|
||||
| { state: 'idle' }
|
||||
@@ -21,45 +20,14 @@ export interface UpdateActions {
|
||||
dismiss: () => void
|
||||
}
|
||||
|
||||
interface CanaryRelease {
|
||||
version: string
|
||||
notes: string
|
||||
platforms: Record<string, { url: string; signature: string }>
|
||||
}
|
||||
|
||||
async function checkCanaryUpdate(): Promise<{ version: string; notes: string; downloadUrl: string } | null> {
|
||||
const response = await fetch(CANARY_ENDPOINT)
|
||||
if (!response.ok) return null
|
||||
|
||||
const data = await response.json() as CanaryRelease
|
||||
const { getVersion } = await import('@tauri-apps/api/app')
|
||||
const currentVersion = await getVersion()
|
||||
|
||||
if (data.version === currentVersion) return null
|
||||
|
||||
const platform = data.platforms['darwin-aarch64']
|
||||
const downloadUrl = platform?.url?.replace(/\.tar\.gz$/, '').replace(/\.app$/, '') ?? ''
|
||||
|
||||
return { version: data.version, notes: data.notes, downloadUrl }
|
||||
}
|
||||
|
||||
export function useUpdater(channel: string | null = null): { status: UpdateStatus; actions: UpdateActions } {
|
||||
export function useUpdater(): { status: UpdateStatus; actions: UpdateActions } {
|
||||
const [status, setStatus] = useState<UpdateStatus>({ state: 'idle' })
|
||||
const updateRef = useRef<unknown>(null)
|
||||
const canaryUrlRef = useRef<string | null>(null)
|
||||
|
||||
const checkForUpdates = useCallback(async (): Promise<UpdateCheckResult> => {
|
||||
if (!isTauri()) return 'up-to-date'
|
||||
|
||||
try {
|
||||
if (channel === 'canary') {
|
||||
const canary = await checkCanaryUpdate()
|
||||
if (!canary) return 'up-to-date'
|
||||
canaryUrlRef.current = canary.downloadUrl
|
||||
setStatus({ state: 'available', version: canary.version, notes: canary.notes })
|
||||
return 'available'
|
||||
}
|
||||
|
||||
const { check } = await import('@tauri-apps/plugin-updater')
|
||||
const update = await check()
|
||||
if (!update) return 'up-to-date'
|
||||
@@ -75,7 +43,7 @@ export function useUpdater(channel: string | null = null): { status: UpdateStatu
|
||||
console.warn('[updater] Failed to check for updates')
|
||||
return 'error'
|
||||
}
|
||||
}, [channel])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTauri()) return
|
||||
@@ -84,12 +52,6 @@ export function useUpdater(channel: string | null = null): { status: UpdateStatu
|
||||
}, [checkForUpdates])
|
||||
|
||||
const startDownload = useCallback(async () => {
|
||||
// Canary: open the GitHub release page for manual download
|
||||
if (canaryUrlRef.current) {
|
||||
openExternalUrl(canaryUrlRef.current)
|
||||
return
|
||||
}
|
||||
|
||||
const update = updateRef.current as {
|
||||
version: string
|
||||
downloadAndInstall: (cb: (event: { event: string; data?: { contentLength?: number; chunkLength?: number } }) => void) => Promise<void>
|
||||
|
||||
@@ -84,7 +84,6 @@ let mockSettings: Settings = {
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
update_channel: null,
|
||||
release_channel: null,
|
||||
}
|
||||
|
||||
@@ -210,7 +209,6 @@ export const mockHandlers: Record<string, (args: any) => any> = {
|
||||
crash_reporting_enabled: s.crash_reporting_enabled,
|
||||
analytics_enabled: s.analytics_enabled,
|
||||
anonymous_id: s.anonymous_id,
|
||||
update_channel: s.update_channel,
|
||||
release_channel: s.release_channel,
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -79,7 +79,6 @@ export interface Settings {
|
||||
crash_reporting_enabled: boolean | null
|
||||
analytics_enabled: boolean | null
|
||||
anonymous_id: string | null
|
||||
update_channel: string | null
|
||||
release_channel: string | null
|
||||
}
|
||||
|
||||
|
||||
@@ -9,32 +9,34 @@ async function openSettings(page: import('@playwright/test').Page) {
|
||||
return panel
|
||||
}
|
||||
|
||||
test.describe('Canary release channel + feature flags', () => {
|
||||
test.describe('Release channel settings', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
})
|
||||
|
||||
test('Settings panel shows Update channel dropdown defaulting to Stable', async ({ page }) => {
|
||||
test('Settings panel shows Release channel dropdown defaulting to Stable', async ({ page }) => {
|
||||
await openSettings(page)
|
||||
|
||||
// Check the Updates section exists
|
||||
await expect(page.getByText('Updates')).toBeVisible()
|
||||
await expect(page.getByText('Canary builds include')).toBeVisible()
|
||||
// Check the Release Channel section exists
|
||||
await expect(page.getByText('Release Channel')).toBeVisible()
|
||||
|
||||
// Check the dropdown defaults to stable
|
||||
const select = page.locator('[data-testid="settings-update-channel"]')
|
||||
const select = page.locator('[data-testid="settings-release-channel"]')
|
||||
await expect(select).toBeVisible()
|
||||
await expect(select).toHaveValue('stable')
|
||||
|
||||
// Update channel should NOT be present
|
||||
await expect(page.locator('[data-testid="settings-update-channel"]')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Update channel can be changed to canary and saved', async ({ page }) => {
|
||||
test('Release channel can be changed to alpha and saved', async ({ page }) => {
|
||||
await openSettings(page)
|
||||
|
||||
// Change to canary
|
||||
const select = page.locator('[data-testid="settings-update-channel"]')
|
||||
await select.selectOption('canary')
|
||||
await expect(select).toHaveValue('canary')
|
||||
// Change to alpha
|
||||
const select = page.locator('[data-testid="settings-release-channel"]')
|
||||
await select.selectOption('alpha')
|
||||
await expect(select).toHaveValue('alpha')
|
||||
|
||||
// Save (closes the panel)
|
||||
await page.click('[data-testid="settings-save"]')
|
||||
@@ -42,7 +44,7 @@ test.describe('Canary release channel + feature flags', () => {
|
||||
|
||||
// Reopen settings and verify the value persisted
|
||||
await openSettings(page)
|
||||
const reopenedSelect = page.locator('[data-testid="settings-update-channel"]')
|
||||
await expect(reopenedSelect).toHaveValue('canary')
|
||||
const reopenedSelect = page.locator('[data-testid="settings-release-channel"]')
|
||||
await expect(reopenedSelect).toHaveValue('alpha')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user