diff --git a/package.json b/package.json
index 6995809ab..d0e66a96f 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,18 @@
"main": "./out/extension.js",
"runme": {
"features": {
+ "SignedIn": {
+ "enabled": true,
+ "conditions": {
+ "os": "All",
+ "vsCodeVersion": ">=1.58.0",
+ "statefulAuthRequired": true,
+ "enabledForExtensions": {
+ "stateful.platform": true,
+ "stateful.runme": true
+ }
+ }
+ },
"ForceLogin": {
"enabled": true,
"conditions": {
@@ -88,7 +100,7 @@
"vsCodeVersion": ">=1.58.0",
"enabledForExtensions": {
"stateful.platform": true,
- "stateful.runme": true
+ "stateful.runme": false
}
}
},
diff --git a/src/client/components/terminal/index.ts b/src/client/components/terminal/index.ts
index c3a8c9082..4bc2ce584 100644
--- a/src/client/components/terminal/index.ts
+++ b/src/client/components/terminal/index.ts
@@ -12,7 +12,7 @@ import { ClientMessages, RENDERERS, OutputType, WebViews } from '../../../consta
import { closeOutput, getContext } from '../../utils'
import { onClientMessage, postClientMessage } from '../../../utils/messaging'
import { stripANSI } from '../../../utils/ansi'
-import { APIMethod } from '../../../types'
+import { APIMethod, FeatureObserver, FeatureName } from '../../../types'
import type { TerminalConfiguration } from '../../../utils/configuration'
import '../closeCellButton'
import '../copyButton'
@@ -24,12 +24,7 @@ import {
CreateExtensionCellOutputMutation,
UpdateCellOutputMutation,
} from '../../../extension/__generated-platform__/graphql'
-import {
- isFeatureActive,
- loadFeatureSnapshot,
- FeatureObserver,
- FeatureName,
-} from '../../../features'
+import features from '../../../features'
interface IWindowSize {
width: number
@@ -453,7 +448,7 @@ export class TerminalView extends LitElement {
switch (e.type) {
case ClientMessages.featuresResponse:
case ClientMessages.featuresUpdateAction:
- this.featureState$ = loadFeatureSnapshot(e.output.snapshot)
+ this.featureState$ = features.loadSnapshot(e.output.snapshot)
break
case ClientMessages.activeThemeChanged:
this.#updateTerminalTheme()
@@ -857,7 +852,7 @@ export class TerminalView extends LitElement {
}}"
>
${when(
- this.isSessionOutputsEnabled && isFeatureActive(FeatureName.Gist, this.featureState$),
+ features.isOn(FeatureName.Gist, this.featureState$),
() => {
return html``
},
@@ -865,7 +860,7 @@ export class TerminalView extends LitElement {
)}
${when(
(this.exitCode === undefined || this.exitCode === 0 || !this.platformId) &&
- isFeatureActive(FeatureName.Share, this.featureState$),
+ features.isOn(FeatureName.Share, this.featureState$),
() => {
return html` {
return html` {
- if (!ContextState.getKey(NOTEBOOK_AUTOSAVE_ON)) {
+ const isSignedIn = features.isOnInContextState(FeatureName.SignedIn)
+ const isForceLogin = features.isOnInContextState(FeatureName.ForceLogin)
+
+ const isAutoSaveOn = ContextState.getKey(NOTEBOOK_AUTOSAVE_ON)
+
+ if (!isSignedIn && !isAutoSaveOn) {
+ return Promise.resolve()
+ } else if (!isSignedIn && isForceLogin) {
return Promise.resolve()
}
+
await this.withLock(async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
await this.getExecutionUnsafe(async (exec) => {
@@ -550,7 +559,7 @@ export class NotebookCellOutputManager {
}
}
- if (!GrpcSerializer.sessionOutputsEnabled() || !terminalOutput || !terminalOutputItem) {
+ if (!getSessionOutputs() || !terminalOutput || !terminalOutputItem) {
return
}
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index 63ea7b919..23d40c55f 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -37,7 +37,7 @@ import {
openFileAsRunmeNotebook,
promptUserSession,
} from '../utils'
-import { NotebookToolbarCommand, NotebookUiEvent } from '../../types'
+import { NotebookToolbarCommand, NotebookUiEvent, FeatureName } from '../../types'
import getLogger from '../logger'
import { RecommendExtensionMessage } from '../messaging'
import {
@@ -53,6 +53,7 @@ import { createGist } from '../services/github/gist'
import { InitializeClient } from '../api/client'
import { GetUserEnvironmentsDocument } from '../__generated-platform__/graphql'
import { EnvironmentManager } from '../environment/manager'
+import features from '../features'
const log = getLogger('Commands')
@@ -388,7 +389,9 @@ export async function addToRecommendedExtensions(context: ExtensionContext) {
}
export async function toggleAutosave(autoSaveIsOn: boolean) {
- if (autoSaveIsOn) {
+ const createIfNone = features.isOnInContextState(FeatureName.ForceLogin)
+
+ if (autoSaveIsOn && createIfNone) {
await promptUserSession()
}
return ContextState.addKey(NOTEBOOK_AUTOSAVE_ON, autoSaveIsOn)
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 73deb38a9..daadacc35 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -14,7 +14,7 @@ import {
import { TelemetryReporter } from 'vscode-telemetry'
import Channel from 'tangle/webviews'
-import { NotebookUiEvent, Serializer, SyncSchema } from '../types'
+import { NotebookUiEvent, Serializer, SyncSchema, FeatureName } from '../types'
import {
getDocsUrlFor,
getForceNewWindowConfig,
@@ -22,7 +22,6 @@ import {
getSessionOutputs,
} from '../utils/configuration'
import { AuthenticationProviders, WebViews } from '../constants'
-import { FeatureName } from '../features'
import { Kernel } from './kernel'
import KernelServer from './server/kernelServer'
@@ -385,10 +384,10 @@ export class RunmeExtension {
context.subscriptions.push(new StatefulAuthProvider(context, uriHandler))
getGithubAuthSession(false).then((session) => {
- kernel.updateFeatureState('githubAuth', !!session)
+ kernel.updateFeatureContext('githubAuth', !!session)
})
- const createIfNone = kernel.isFeatureActive(FeatureName.ForceLogin)
+ const createIfNone = kernel.isFeatureOn(FeatureName.ForceLogin)
const silent = createIfNone ? undefined : true
getPlatformAuthSession(createIfNone, silent)
@@ -425,12 +424,12 @@ export class RunmeExtension {
authentication.onDidChangeSessions((e) => {
if (e.provider.id === AuthenticationProviders.Stateful) {
getPlatformAuthSession(false, true).then((session) => {
- kernel.updateFeatureState('statefulAuth', !!session)
+ kernel.updateFeatureContext('statefulAuth', !!session)
})
}
if (e.provider.id === AuthenticationProviders.GitHub) {
getGithubAuthSession(false).then((session) => {
- kernel.updateFeatureState('githubAuth', !!session)
+ kernel.updateFeatureContext('githubAuth', !!session)
})
}
})
diff --git a/src/extension/features.ts b/src/extension/features.ts
new file mode 100644
index 000000000..edb304ff4
--- /dev/null
+++ b/src/extension/features.ts
@@ -0,0 +1,19 @@
+import features, { FEATURES_CONTEXT_STATE_KEY } from '../features'
+import { FeatureName } from '../types'
+
+import ContextState from './contextState'
+
+export function isOnInContextState(featureName: FeatureName): boolean {
+ const snapshot = ContextState.getKey(FEATURES_CONTEXT_STATE_KEY)
+ if (!snapshot) {
+ return false
+ }
+
+ const featureState$ = features.loadSnapshot(snapshot)
+
+ return features.isOn(featureName, featureState$)
+}
+
+export default {
+ isOnInContextState,
+}
diff --git a/src/extension/kernel.ts b/src/extension/kernel.ts
index 9f0c5432a..5278f2bbd 100644
--- a/src/extension/kernel.ts
+++ b/src/extension/kernel.ts
@@ -34,6 +34,9 @@ import {
type ClientMessage,
type RunmeTerminal,
type Serializer,
+ type ExtensionName,
+ type FeatureContext,
+ FeatureName,
} from '../types'
import {
ClientMessages,
@@ -48,16 +51,7 @@ import {
import { API } from '../utils/deno/api'
import { postClientMessage } from '../utils/messaging'
import { getNotebookExecutionOrder, registerExtensionEnvVarsMutation } from '../utils/configuration'
-import {
- isFeatureActive,
- FeatureContext,
- getFeatureSnapshot,
- loadFeaturesState,
- updateFeatureContext,
- FEATURES_CONTEXT_STATE_KEY,
- FeatureName,
- ExtensionName,
-} from '../features'
+import features, { FEATURES_CONTEXT_STATE_KEY } from '../features'
import getLogger from './logger'
import executor, {
@@ -204,21 +198,21 @@ export class Kernel implements Disposable {
extensionId: context?.extension?.id as ExtensionName,
}
- this.featuresState$ = loadFeaturesState(packageJSON, featContext, this.#featuresSettings)
+ this.featuresState$ = features.loadState(packageJSON, featContext, this.#featuresSettings)
if (this.featuresState$) {
- const features = workspace.getConfiguration('runme.features')
+ const runmeFeatures = workspace.getConfiguration('runme.features')
const featureNames = Object.keys(FeatureName).map((f) => f.toLowerCase())
if (features) {
featureNames.forEach((feature) => {
- if (features.has(feature)) {
- this.#featuresSettings.set(feature, features.get(feature, false))
+ if (runmeFeatures.has(feature)) {
+ this.#featuresSettings.set(feature, runmeFeatures.get(feature, false))
}
})
}
const subscription = this.featuresState$
- .pipe(map((_state) => getFeatureSnapshot(this.featuresState$)))
+ .pipe(map((_state) => features.getSnapshot(this.featuresState$)))
.subscribe((snapshot) => {
ContextState.addKey(FEATURES_CONTEXT_STATE_KEY, snapshot)
postClientMessage(this.messaging, ClientMessages.featuresUpdateAction, {
@@ -232,16 +226,16 @@ export class Kernel implements Disposable {
}
}
- isFeatureActive(featureName: FeatureName): boolean {
+ isFeatureOn(featureName: FeatureName): boolean {
if (!this.featuresState$) {
return false
}
- return isFeatureActive(featureName, this.featuresState$)
+ return features.isOn(featureName, this.featuresState$)
}
- updateFeatureState(key: K, value: FeatureContext[K]) {
- updateFeatureContext(this.featuresState$, key, value, this.#featuresSettings)
+ updateFeatureContext(key: K, value: FeatureContext[K]) {
+ features.updateContext(this.featuresState$, key, value, this.#featuresSettings)
}
registerNotebookCell(cell: NotebookCell) {
@@ -631,7 +625,7 @@ export class Kernel implements Disposable {
} else if (message.type.startsWith('terminal:')) {
return
} else if (message.type === ClientMessages.featuresRequest) {
- const snapshot = getFeatureSnapshot(this.featuresState$)
+ const snapshot = features.getSnapshot(this.featuresState$)
postClientMessage(this.messaging, ClientMessages.featuresResponse, {
snapshot: snapshot,
})
diff --git a/src/extension/serializer.ts b/src/extension/serializer.ts
index cccbf8a07..25e6f0a07 100644
--- a/src/extension/serializer.ts
+++ b/src/extension/serializer.ts
@@ -24,7 +24,7 @@ import { ulid } from 'ulidx'
import { maskString } from 'data-guardian'
import YAML from 'yaml'
-import { Serializer } from '../types'
+import { FeatureName, Serializer } from '../types'
import {
NOTEBOOK_AUTOSAVE_ON,
NOTEBOOK_HAS_OUTPUTS,
@@ -37,7 +37,6 @@ import {
ServerLifecycleIdentity,
getServerConfigurationValue,
getSessionOutputs,
- isPlatformAuthEnabled,
} from '../utils/configuration'
import {
@@ -61,6 +60,7 @@ import { getCellById } from './cell'
import { IProcessInfoState } from './terminal/terminalState'
import ContextState from './contextState'
import * as ghost from './ai/ghost'
+import * as features from './features'
declare var globalThis: any
const DEFAULT_LANG_ID = 'text'
@@ -551,7 +551,6 @@ export class GrpcSerializer extends SerializerBase {
}
protected async saveNotebookOutputsByCacheId(cacheId: string): Promise {
- // if session outputs are disabled, we don't write anything
if (!GrpcSerializer.sessionOutputsEnabled()) {
this.togglePreviewButton(false)
return -1
@@ -579,18 +578,20 @@ export class GrpcSerializer extends SerializerBase {
return -1
}
- const sessionFile = GrpcSerializer.getOutputsUri(srcDocUri, sessionId)
- if (!sessionFile) {
+ // Don't write to disk if authenticated and share are disabled
+ if (
+ features.isOnInContextState(FeatureName.SignedIn) &&
+ features.isOnInContextState(FeatureName.Share)
+ ) {
this.togglePreviewButton(false)
- return -1
+ // But still return a valid bytes length so the cache keeps working
+ return bytes.length
}
- // Don't write to disk if platform auth is enabled
- const isPlatform = isPlatformAuthEnabled()
- if (isPlatform) {
+ const sessionFile = GrpcSerializer.getOutputsUri(srcDocUri, sessionId)
+ if (!sessionFile) {
this.togglePreviewButton(false)
- // But still return a valid bytes length so the cache keeps working
- return bytes.length
+ return -1
}
await workspace.fs.writeFile(sessionFile, bytes)
@@ -719,7 +720,10 @@ export class GrpcSerializer extends SerializerBase {
}
static sessionOutputsEnabled() {
- return getSessionOutputs() && ContextState.getKey(NOTEBOOK_AUTOSAVE_ON)
+ const isAutoSaveOn = ContextState.getKey(NOTEBOOK_AUTOSAVE_ON)
+ const isSessionOutputs = getSessionOutputs()
+
+ return isSessionOutputs && isAutoSaveOn
}
private async cacheNotebookOutputs(
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index b48620155..90c4b7a80 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -33,6 +33,7 @@ import {
NotebookAutoSaveSetting,
RunmeTerminal,
Serializer,
+ FeatureName,
} from '../types'
import { SafeCellAnnotationsSchema, CellAnnotationsSchema } from '../schema'
import {
@@ -56,13 +57,8 @@ import {
getTLSEnabled,
isPlatformAuthEnabled,
} from '../utils/configuration'
-import {
- FeatureName,
- FEATURES_CONTEXT_STATE_KEY,
- isFeatureActive,
- loadFeatureSnapshot,
-} from '../features'
+import features from './features'
import CategoryQuickPickItem from './quickPickItems/category'
import getLogger from './logger'
import { Kernel } from './kernel'
@@ -726,17 +722,19 @@ export async function resolveUserSession(
* This only happens once. Subsequent saves will not display the prompt.
* @returns AuthenticationSession
*/
-export async function promptUserSession(): Promise {
- let session = await resolveUserSession(false)
+export async function promptUserSession() {
+ const createIfNone = features.isOnInContextState(FeatureName.ForceLogin)
+ const silent = createIfNone ? undefined : true
- const provider = isPlatformAuthEnabled() ? 'Stateful' : 'GitHub'
- const featureState$ = loadFeatureSnapshot(ContextState.getKey(FEATURES_CONTEXT_STATE_KEY))
- const displayLoginPrompt = getLoginPrompt() && isFeatureActive(FeatureName.Share, featureState$)
+ const session = await getPlatformAuthSession(false, silent)
+
+ const displayLoginPrompt =
+ getLoginPrompt() && createIfNone && features.isOnInContextState(FeatureName.Share)
if (!session && displayLoginPrompt !== false) {
const option = await window.showInformationMessage(
`Securely store your cell outputs.
- Sign in with ${provider} is required, do you want to proceed?`,
+ Sign in with Stateful is required, do you want to proceed?`,
'Yes',
'No',
'Open Settings',
@@ -749,13 +747,28 @@ export async function promptUserSession(): Promise {
+ if (!session) {
+ throw new Error('You must authenticate with your Stateful account')
+ }
+ })
+ .catch((error) => {
+ let message
+ if (error instanceof Error) {
+ message = error.message
+ } else {
+ message = String(error)
+ }
- return session
+ // https://github.com/microsoft/vscode/blob/main/src/vs/workbench/api/browser/mainThreadAuthentication.ts#L238
+ // throw new Error('User did not consent to login.')
+ // Calling again to ensure User Menu Badge
+ if (createIfNone && message === 'User did not consent to login.') {
+ getPlatformAuthSession(false)
+ }
+ })
+ }
}
export async function checkSession(context: ExtensionContext) {
diff --git a/src/features.ts b/src/features.ts
index e730892f7..7a95eddcc 100644
--- a/src/features.ts
+++ b/src/features.ts
@@ -1,73 +1,35 @@
import { BehaviorSubject } from 'rxjs'
import { satisfies } from 'semver'
-export const FEATURES_CONTEXT_STATE_KEY = 'features'
-
-export type FeatureContext = {
- os?: string
- vsCodeVersion?: string
- runmeVersion?: string
- extensionVersion?: string
- githubAuth?: boolean // `true`, `false`, or `undefined`
- statefulAuth?: boolean // `true`, `false`, or `undefined`
- extensionId?: ExtensionName
-}
-
-export enum ExtensionName {
- StatefulRunme = 'stateful.runme',
- StatefulPlatform = 'stateful.platform',
-}
-
-type EnabledForExtensions = Partial>
-
-export type FeatureCondition = {
- os?: string
- vsCodeVersion?: string
- runmeVersion?: string
- extensionVersion?: string
- githubAuthRequired?: boolean
- statefulAuthRequired?: boolean
- enabledForExtensions?: EnabledForExtensions
-}
-
-export enum FeatureName {
- Gist = 'Gist',
- Share = 'Share',
- Escalate = 'Escalate',
- ForceLogin = 'ForceLogin',
-}
+import {
+ EnabledForExtensions,
+ ExtensionName,
+ Feature,
+ FeatureContext,
+ FeatureName,
+ FeatureObserver,
+ Features,
+ FeatureState,
+} from './types'
-export type Feature = {
- enabled: boolean
- activated: boolean
- conditions: FeatureCondition
-}
-
-export type Features = Partial>
-
-export type FeatureState = {
- features: Features
- context?: FeatureContext
-}
-
-export type FeatureObserver = BehaviorSubject
+export const FEATURES_CONTEXT_STATE_KEY = 'features'
-function loadFeaturesFromPackageJson(packageJSON: any): Features {
+function loadFromPackageJson(packageJSON: any): Features {
const features = (packageJSON?.runme?.features || {}) as Features
return features
}
-export function loadFeaturesState(
+function loadState(
packageJSON: any,
context?: FeatureContext,
overrides: Map = new Map(),
): FeatureObserver {
- const initialFeatures = loadFeaturesFromPackageJson(packageJSON)
+ const initialFeatures = loadFromPackageJson(packageJSON)
const state = new BehaviorSubject({
features: initialFeatures,
context,
})
- updateFeatureState(state, context, overrides)
+ updateState(state, context, overrides)
return state
}
@@ -187,7 +149,7 @@ function isActive(
if (!checkExtensionId(enabledForExtensions, context?.extensionId)) {
console.log(
- `Feature "${featureName}" is inactive due to checkExtensionId. Expected: ${enabledForExtensions}, actual: ${context?.extensionId}`,
+ `Feature "${featureName}" is inactive due to checkExtensionId. Expected: ${JSON.stringify(enabledForExtensions)}, actual: ${context?.extensionId}`,
)
return false
}
@@ -196,7 +158,7 @@ function isActive(
return true
}
-export function updateFeatureState(
+function updateState(
featureState$: FeatureObserver,
context?: FeatureContext,
overrides: Map = new Map(),
@@ -208,7 +170,7 @@ export function updateFeatureState(
...acc,
[key]: {
...value,
- activated: isActive(key as FeatureName, value, context, overrides),
+ on: isActive(key as FeatureName, value, context, overrides),
},
}),
{} as Features,
@@ -218,7 +180,7 @@ export function updateFeatureState(
return featureState$
}
-export function updateFeatureContext(
+function updateContext(
featureState$: FeatureObserver | undefined,
key: K,
value: FeatureContext[K],
@@ -231,11 +193,11 @@ export function updateFeatureContext(
const newContext = { ...(currentState.context || {}), [key]: value }
if (newContext[key] !== currentState?.context?.[key]) {
- updateFeatureState(featureState$, newContext, overrides)
+ updateState(featureState$, newContext, overrides)
}
}
-export function getFeatureSnapshot(featureState$: FeatureObserver | undefined): string {
+function getSnapshot(featureState$: FeatureObserver | undefined): string {
if (!featureState$) {
return ''
}
@@ -243,7 +205,7 @@ export function getFeatureSnapshot(featureState$: FeatureObserver | undefined):
return JSON.stringify(featureState$.getValue())
}
-export function loadFeatureSnapshot(snapshot: string): FeatureObserver {
+function loadSnapshot(snapshot: string): FeatureObserver {
const { features, context } = JSON.parse(snapshot)
const featureState$ = new BehaviorSubject({
features,
@@ -254,14 +216,20 @@ export function loadFeatureSnapshot(snapshot: string): FeatureObserver {
return featureState$
}
-export function isFeatureActive(
- featureName: FeatureName,
- featureState$?: FeatureObserver,
-): boolean {
+function isOn(featureName: FeatureName, featureState$?: FeatureObserver): boolean {
if (!featureState$) {
return false
}
const feature = featureState$.getValue().features[featureName]
- return feature ? feature.activated : false
+ return feature?.on ?? false
+}
+
+export default {
+ loadState,
+ updateState,
+ updateContext,
+ getSnapshot,
+ loadSnapshot,
+ isOn,
}
diff --git a/src/types.ts b/src/types.ts
index f2f07c806..6310d7768 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -19,6 +19,7 @@ import {
} from '@aws-sdk/client-ec2'
import { google } from '@google-cloud/run/build/protos/protos'
import { protos } from '@google-cloud/compute'
+import { type BehaviorSubject } from 'rxjs'
import { OutputType, ClientMessages, RUNME_FRONTMATTER_PARSED } from './constants'
import { SafeCellAnnotationsSchema, SafeNotebookAnnotationsSchema } from './schema'
@@ -732,3 +733,53 @@ export type NotebookUiEvent = {
export type Settings = {
docsUrl?: string
}
+
+export type FeatureContext = {
+ os?: string
+ vsCodeVersion?: string
+ runmeVersion?: string
+ extensionVersion?: string
+ githubAuth?: boolean // `true`, `false`, or `undefined`
+ statefulAuth?: boolean // `true`, `false`, or `undefined`
+ extensionId?: ExtensionName
+}
+
+export enum ExtensionName {
+ StatefulRunme = 'stateful.runme',
+ StatefulPlatform = 'stateful.platform',
+}
+
+export type EnabledForExtensions = Partial>
+
+export type FeatureCondition = {
+ os?: string
+ vsCodeVersion?: string
+ runmeVersion?: string
+ extensionVersion?: string
+ githubAuthRequired?: boolean
+ statefulAuthRequired?: boolean
+ enabledForExtensions?: EnabledForExtensions
+}
+
+export enum FeatureName {
+ Gist = 'Gist',
+ Share = 'Share',
+ Escalate = 'Escalate',
+ ForceLogin = 'ForceLogin',
+ SignedIn = 'SignedIn',
+}
+
+export type Feature = {
+ enabled: boolean
+ on: boolean
+ conditions: FeatureCondition
+}
+
+export type Features = Partial>
+
+export type FeatureState = {
+ features: Features
+ context?: FeatureContext
+}
+
+export type FeatureObserver = BehaviorSubject
diff --git a/tests/extension/cell.test.ts b/tests/extension/cell.test.ts
index a91888f79..4ab07fa56 100644
--- a/tests/extension/cell.test.ts
+++ b/tests/extension/cell.test.ts
@@ -26,10 +26,12 @@ vi.mock('vscode', async () => {
vi.mock('vscode-telemetry')
vi.mock('../../src/extension/grpc/client', () => ({}))
-vi.mock('../../../src/extension/grpc/runner/v1', () => ({
+vi.mock('../../src/extension/grpc/runner/v1', () => ({
ResolveProgramRequest_Mode: vi.fn(),
}))
+vi.mock('../../src/extension/features')
+
describe('NotebookCellManager', () => {
it('can register cells', async () => {
const manager = new NotebookCellManager({} as any)
diff --git a/tests/extension/features.test.ts b/tests/extension/features.test.ts
index a80d7029c..9d5c80bf8 100644
--- a/tests/extension/features.test.ts
+++ b/tests/extension/features.test.ts
@@ -1,17 +1,13 @@
-import { describe, it, expect, beforeEach } from 'vitest'
+import { describe, it, expect, beforeEach, vi } from 'vitest'
+import features from '../../src/features'
import {
- updateFeatureState,
- getFeatureSnapshot,
- loadFeaturesState,
- isFeatureActive,
- loadFeatureSnapshot,
+ ExtensionName,
FeatureContext,
- FeatureState,
- FeatureObserver,
FeatureName,
- ExtensionName,
-} from '../../src/features'
+ FeatureObserver,
+ FeatureState,
+} from '../../src/types'
const packageJSON = {
runme: {
@@ -50,11 +46,14 @@ const packageJSON = {
},
}
+vi.mock('vscode')
+vi.mock('../../src/extension/contextState')
+
describe('Feature Store', () => {
let featureState$: FeatureObserver
beforeEach(() => {
- featureState$ = loadFeaturesState(packageJSON, {
+ featureState$ = features.loadState(packageJSON, {
os: 'linux',
})
})
@@ -70,12 +69,12 @@ describe('Feature Store', () => {
extensionId: ExtensionName.StatefulRunme,
}
- updateFeatureState(featureState$, initialContext)
+ features.updateState(featureState$, initialContext)
const currentFeatures = (featureState$.getValue() as FeatureState).features
- expect(currentFeatures.Escalate?.activated).toBe(false)
- expect(currentFeatures.Gist?.activated).toBe(true)
+ expect(currentFeatures.Escalate?.on).toBe(false)
+ expect(currentFeatures.Gist?.on).toBe(true)
})
it('should take a snapshot of the current state', () => {
@@ -86,11 +85,11 @@ describe('Feature Store', () => {
runmeVersion: '1.3.0',
githubAuth: true,
statefulAuth: true,
- extensionId: 'stateful.runme',
+ extensionId: ExtensionName.StatefulRunme,
}
- updateFeatureState(featureState$, initialContext)
- const snapshot = getFeatureSnapshot(featureState$)
+ features.updateState(featureState$, initialContext)
+ const snapshot = features.getSnapshot(featureState$)
expect(snapshot).toBe(JSON.stringify(featureState$.getValue()))
})
@@ -106,15 +105,15 @@ describe('Feature Store', () => {
extensionId: ExtensionName.StatefulRunme,
}
- updateFeatureState(featureState$, initialContext)
- const snapshot = getFeatureSnapshot(featureState$)
+ features.updateState(featureState$, initialContext)
+ const snapshot = features.getSnapshot(featureState$)
featureState$.next({
context: initialContext,
features: {
Escalate: {
enabled: true,
- activated: false,
+ on: false,
conditions: {
os: 'All',
vsCodeVersion: '>=1.58.0',
@@ -125,7 +124,7 @@ describe('Feature Store', () => {
},
Gist: {
enabled: true,
- activated: false,
+ on: false,
conditions: {
os: 'win32',
vsCodeVersion: '>=1.60.0',
@@ -136,7 +135,7 @@ describe('Feature Store', () => {
},
})
- const featureStateCopy$ = loadFeatureSnapshot(snapshot)
+ const featureStateCopy$ = features.loadSnapshot(snapshot)
const restoredFeatures = featureStateCopy$.getValue()
expect(restoredFeatures).toEqual(JSON.parse(snapshot))
@@ -152,11 +151,11 @@ describe('Feature Store', () => {
statefulAuth: false,
extensionId: ExtensionName.StatefulRunme,
}
- updateFeatureState(featureState$, newContext)
+ features.updateState(featureState$, newContext)
const currentFeatures = (featureState$.getValue() as FeatureState).features
- expect(currentFeatures.Escalate?.activated).toBe(false)
- expect(currentFeatures.Gist?.activated).toBe(false)
+ expect(currentFeatures.Escalate?.on).toBe(false)
+ expect(currentFeatures.Gist?.on).toBe(false)
})
it('should correctly identify if a feature is enabled by name', () => {
@@ -169,10 +168,10 @@ describe('Feature Store', () => {
statefulAuth: true,
extensionId: ExtensionName.StatefulPlatform,
}
- updateFeatureState(featureState$, ctx)
+ features.updateState(featureState$, ctx)
- expect(isFeatureActive(FeatureName.Escalate, featureState$)).toBe(true)
- expect(isFeatureActive(FeatureName.Gist, featureState$)).toBe(false)
+ expect(features.isOn(FeatureName.Escalate, featureState$)).toBe(true)
+ expect(features.isOn(FeatureName.Gist, featureState$)).toBe(false)
})
it('should correctly identify if a feature is enabled by extensionId', () => {
@@ -185,9 +184,9 @@ describe('Feature Store', () => {
statefulAuth: true,
extensionId: ExtensionName.StatefulRunme,
}
- updateFeatureState(featureState$, ctx)
+ features.updateState(featureState$, ctx)
- expect(isFeatureActive(FeatureName.Escalate, featureState$)).toBe(false)
- expect(isFeatureActive(FeatureName.Gist, featureState$)).toBe(true)
+ expect(features.isOn(FeatureName.Escalate, featureState$)).toBe(false)
+ expect(features.isOn(FeatureName.Gist, featureState$)).toBe(true)
})
})
diff --git a/tests/extension/serializer.test.ts b/tests/extension/serializer.test.ts
index fb1dade68..5394c3ec7 100644
--- a/tests/extension/serializer.test.ts
+++ b/tests/extension/serializer.test.ts
@@ -67,6 +67,8 @@ vi.mock('../../src/extension/utils', () => ({
initWasm: vi.fn(),
}))
+vi.mock('../../src/extension/features')
+
function newKernel(): Kernel {
return {} as unknown as Kernel
}