diff --git a/packages/app/cypress/e2e/integration/settings.spec.ts b/packages/app/cypress/e2e/integration/settings.spec.ts
index b9ad02baaa90..4a92cc3e5b85 100644
--- a/packages/app/cypress/e2e/integration/settings.spec.ts
+++ b/packages/app/cypress/e2e/integration/settings.spec.ts
@@ -1,4 +1,4 @@
-describe('Settings', { viewportWidth: 1200 }, () => {
+describe('Settings', { viewportWidth: 600 }, () => {
beforeEach(() => {
cy.setupE2E('component-tests')
@@ -38,4 +38,18 @@ describe('Settings', { viewportWidth: 1200 }, () => {
cy.findByText('Reconfigure Project').click()
cy.wait('@ReconfigureProject')
})
+
+ it('selects well known editor', () => {
+ cy.visitApp()
+ cy.get('[href="#/settings"]').click()
+ cy.contains('Device Settings').click()
+ cy.findByPlaceholderText('Custom path...').clear().type('/usr/local/bin/vim')
+
+ cy.intercept('POST', 'mutation-SetPreferredEditorBinary', (req) => {
+ expect(req.body.variables).to.eql({ 'value': '/usr/local/bin/vim' })
+ }).as('SetPreferred')
+
+ cy.get('[data-cy="use-custom-editor"]').click()
+ cy.wait('@SetPreferred')
+ })
})
diff --git a/packages/app/src/settings/SettingsContainer.vue b/packages/app/src/settings/SettingsContainer.vue
index 15ad88923d98..03bcbffce90a 100644
--- a/packages/app/src/settings/SettingsContainer.vue
+++ b/packages/app/src/settings/SettingsContainer.vue
@@ -9,7 +9,9 @@
:icon="IconLaptop"
max-height="800px"
>
-
+
+
+
', () => {
- it('renders', () => {
- cy.mount(() => )
- })
-})
diff --git a/packages/app/src/settings/device/DeviceSettings.vue b/packages/app/src/settings/device/DeviceSettings.vue
deleted file mode 100644
index f95dd2ede27e..000000000000
--- a/packages/app/src/settings/device/DeviceSettings.vue
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/packages/app/src/settings/device/ExternalEditorSettings.spec.tsx b/packages/app/src/settings/device/ExternalEditorSettings.spec.tsx
index e46ec06c7291..828c9bfc449e 100644
--- a/packages/app/src/settings/device/ExternalEditorSettings.spec.tsx
+++ b/packages/app/src/settings/device/ExternalEditorSettings.spec.tsx
@@ -1,34 +1,60 @@
import ExternalEditorSettings from './ExternalEditorSettings.vue'
import { defaultMessages } from '@cy/i18n'
+import { ExternalEditorSettingsFragmentDoc } from '../../generated/graphql-test'
const editorText = defaultMessages.settingsPage.editor
describe('', () => {
- beforeEach(() => {
- cy.mount(() => )
- })
-
it('renders the placeholder by default', () => {
+ cy.mountFragment(ExternalEditorSettingsFragmentDoc, {
+ render: (gqlVal) => {
+ return
+ },
+ })
+
cy.findByText(editorText.noEditorSelectedPlaceholder).should('be.visible')
})
it('renders the title and description', () => {
+ cy.mountFragment(ExternalEditorSettingsFragmentDoc, {
+ render: (gqlVal) => {
+ return
+ },
+ })
+
cy.findByText(editorText.description).should('be.visible')
cy.findByText(editorText.title).should('be.visible')
})
it('can select an editor', () => {
+ cy.mountFragment(ExternalEditorSettingsFragmentDoc, {
+ render: (gqlVal) => {
+ return
+ },
+ })
+
const optionsSelector = '[role=option]'
const inputSelector = '[aria-haspopup=true]'
cy.get(inputSelector).click()
.get(optionsSelector).should('be.visible')
.get(optionsSelector).then(($options) => {
- const text = $options.first().text()
-
cy.wrap($options.first()).click()
- .get(optionsSelector).should('not.exist')
- .get(inputSelector).should('have.text', text)
})
})
+
+ it('can input a custom binary', () => {
+ cy.mountFragment(ExternalEditorSettingsFragmentDoc, {
+ render: (gqlVal) => {
+ return
+ },
+ })
+
+ cy.findByPlaceholderText('Custom path...').type('/usr/bin')
+ cy.get('[data-cy="use-custom-editor"]').as('custom')
+ cy.get('@custom').click()
+
+ cy.get('@custom').should('be.focused')
+ cy.get('[data-cy="use-well-known-editor"]').should('not.be.focused')
+ })
})
diff --git a/packages/app/src/settings/device/ExternalEditorSettings.vue b/packages/app/src/settings/device/ExternalEditorSettings.vue
index 5f479fac986f..540faff1d84c 100644
--- a/packages/app/src/settings/device/ExternalEditorSettings.vue
+++ b/packages/app/src/settings/device/ExternalEditorSettings.vue
@@ -6,42 +6,81 @@
{{ t('settingsPage.editor.description') }}
-
+
+
- {{ pref.title }} pref.mutation.executeMutation({ value })"
/>
@@ -33,15 +35,65 @@
import SettingsSection from '../SettingsSection.vue'
import { useI18n } from '@cy/i18n'
import Switch from '@packages/frontend-shared/src/components/Switch.vue'
+import { gql, useMutation } from '@urql/vue'
+import {
+ SetAutoScrollingEnabledDocument,
+ SetUseDarkSidebarDocument,
+ SetWatchForSpecChangeDocument,
+} from '@packages/data-context/src/gen/all-operations.gen'
+import type { TestingPreferencesFragment } from '../../generated/graphql'
const { t } = useI18n()
+gql`
+fragment TestingPreferences on Query {
+ localSettings {
+ preferences {
+ autoScrollingEnabled
+ useDarkSidebar
+ watchForSpecChange
+ }
+ }
+}
+`
+
+gql`
+mutation SetAutoScrollingEnabled($value: Boolean!) {
+ setAutoScrollingEnabled(value: $value)
+}`
+
+gql`
+mutation SetUseDarkSidebar($value: Boolean!) {
+ setUseDarkSidebar(value: $value)
+}`
+
+gql`
+mutation SetWatchForSpecChange($value: Boolean!) {
+ setWatchForSpecChange(value: $value)
+}`
+
const prefs = [
{
- title: 'Auto-scrolling',
- enabled: true,
- description: 'Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
+ id: 'autoScrollingEnabled',
+ title: t('settingsPage.testingPreferences.autoScrollingEnabled.title'),
+ mutation: useMutation(SetAutoScrollingEnabledDocument),
+ description: t('settingsPage.testingPreferences.autoScrollingEnabled.description'),
+ },
+ {
+ id: 'useDarkSidebar',
+ title: t('settingsPage.testingPreferences.useDarkSidebar.title'),
+ mutation: useMutation(SetUseDarkSidebarDocument),
+ description: t('settingsPage.testingPreferences.useDarkSidebar.description'),
+ },
+ {
+ id: 'watchForSpecChange',
+ title: t('settingsPage.testingPreferences.watchForSpecChange.title'),
+ mutation: useMutation(SetWatchForSpecChangeDocument),
+ description: t('settingsPage.testingPreferences.watchForSpecChange.description'),
},
-]
+] as const
+const props = defineProps<{
+ gql: TestingPreferencesFragment
+}>()
diff --git a/packages/data-context/src/DataActions.ts b/packages/data-context/src/DataActions.ts
index d0386fdade2f..4b6769aeb35d 100644
--- a/packages/data-context/src/DataActions.ts
+++ b/packages/data-context/src/DataActions.ts
@@ -1,5 +1,13 @@
import type { DataContext } from '.'
-import { AppActions, ProjectConfigDataActions, ElectronActions, FileActions, ProjectActions, WizardActions } from './actions'
+import {
+ LocalSettingsActions,
+ AppActions,
+ ProjectConfigDataActions,
+ ElectronActions,
+ FileActions,
+ ProjectActions,
+ WizardActions,
+} from './actions'
import { AuthActions } from './actions/AuthActions'
import { DevActions } from './actions/DevActions'
import { cached } from './util'
@@ -27,6 +35,11 @@ export class DataActions {
return new AuthActions(this.ctx)
}
+ @cached
+ get localSettings () {
+ return new LocalSettingsActions(this.ctx)
+ }
+
@cached
get wizard () {
return new WizardActions(this.ctx)
diff --git a/packages/data-context/src/DataContext.ts b/packages/data-context/src/DataContext.ts
index 0457b29387b5..cce292d10c4b 100644
--- a/packages/data-context/src/DataContext.ts
+++ b/packages/data-context/src/DataContext.ts
@@ -2,7 +2,7 @@ import type { LaunchArgs, OpenProjectLaunchOptions, PlatformName } from '@packag
import fsExtra from 'fs-extra'
import path from 'path'
-import { AppApiShape, DataEmitterActions, ProjectApiShape } from './actions'
+import { AppApiShape, DataEmitterActions, LocalSettingsApiShape, ProjectApiShape } from './actions'
import type { NexusGenAbstractTypeMembers } from '@packages/graphql/src/gen/nxs.gen'
import type { AuthApiShape } from './actions/AuthActions'
import type { ElectronApiShape } from './actions/ElectronActions'
@@ -51,6 +51,7 @@ export interface DataContextConfig {
* Injected from the server
*/
appApi: AppApiShape
+ localSettingsApi: LocalSettingsApiShape
authApi: AuthApiShape
projectApi: ProjectApiShape
electronApi: ElectronApiShape
@@ -80,6 +81,10 @@ export class DataContext {
return this._config.electronApi
}
+ get localSettingsApi () {
+ return this._config.localSettingsApi
+ }
+
get isGlobalMode () {
return !this.currentProject
}
@@ -91,7 +96,8 @@ export class DataContext {
this.actions.app.refreshBrowsers(),
// load the cached user & validate the token on start
this.actions.auth.getUser(),
-
+ // and grab the user device settings
+ this.actions.localSettings.refreshLocalSettings(),
this.actions.app.refreshNodePathAndVersion(),
]
@@ -293,6 +299,7 @@ export class DataContext {
authApi: this._config.authApi,
projectApi: this._config.projectApi,
electronApi: this._config.electronApi,
+ localSettingsApi: this._config.localSettingsApi,
busApi: this._rootBus,
}
}
diff --git a/packages/data-context/src/actions/LocalSettingsActions.ts b/packages/data-context/src/actions/LocalSettingsActions.ts
new file mode 100644
index 000000000000..d09592fc0c7f
--- /dev/null
+++ b/packages/data-context/src/actions/LocalSettingsActions.ts
@@ -0,0 +1,42 @@
+import type { DevicePreferences, Editor } from '@packages/types'
+import pDefer from 'p-defer'
+
+import type { DataContext } from '..'
+
+export interface LocalSettingsApiShape {
+ setPreferredOpener(editor: Editor): Promise
+ getAvailableEditors(): Promise
+
+ getPreferences (): Promise
+ setDevicePreference (key: K, value: DevicePreferences[K]): Promise
+}
+
+export class LocalSettingsActions {
+ constructor (private ctx: DataContext) {}
+
+ setDevicePreference (key: K, value: DevicePreferences[K]) {
+ // update local data
+ this.ctx.coreData.localSettings.preferences[key] = value
+
+ // persist to appData
+ return this.ctx._apis.localSettingsApi.setDevicePreference(key, value)
+ }
+
+ async refreshLocalSettings () {
+ if (this.ctx.coreData.localSettings?.refreshing) {
+ return
+ }
+
+ const dfd = pDefer()
+
+ this.ctx.coreData.localSettings.refreshing = dfd.promise
+
+ // TODO(tim): global unhandled error concept
+ const availableEditors = await this.ctx._apis.localSettingsApi.getAvailableEditors()
+
+ this.ctx.coreData.localSettings.availableEditors = availableEditors
+ this.ctx.coreData.localSettings.preferences = await this.ctx._apis.localSettingsApi.getPreferences()
+
+ dfd.resolve(availableEditors)
+ }
+}
diff --git a/packages/data-context/src/actions/index.ts b/packages/data-context/src/actions/index.ts
index 4fa5984c63cb..07648d70af37 100644
--- a/packages/data-context/src/actions/index.ts
+++ b/packages/data-context/src/actions/index.ts
@@ -7,6 +7,7 @@ export * from './DataEmitterActions'
export * from './DevActions'
export * from './ElectronActions'
export * from './FileActions'
+export * from './LocalSettingsActions'
export * from './ProjectActions'
export * from './ProjectConfigDataActions'
export * from './WizardActions'
diff --git a/packages/data-context/src/data/coreDataShape.ts b/packages/data-context/src/data/coreDataShape.ts
index e1d00e4ef86a..f84c507039c3 100644
--- a/packages/data-context/src/data/coreDataShape.ts
+++ b/packages/data-context/src/data/coreDataShape.ts
@@ -1,4 +1,4 @@
-import { BUNDLERS, FoundBrowser, FoundSpec, FullConfig, Preferences, NodePathAndVersion } from '@packages/types'
+import { BUNDLERS, FoundBrowser, FoundSpec, FullConfig, Preferences, NodePathAndVersion, DevicePreferences, devicePreferenceDefaults, Editor } from '@packages/types'
import type { NexusGenEnums, TestingTypeEnum } from '@packages/graphql/src/gen/nxs.gen'
import type { BrowserWindow } from 'electron'
import type { ChildProcess } from 'child_process'
@@ -19,6 +19,12 @@ export interface DevStateShape {
refreshState: null | string
}
+export interface LocalSettingsDataShape {
+ refreshing: Promise | null
+ availableEditors: Editor[]
+ preferences: DevicePreferences
+}
+
export interface ConfigChildProcessShape {
/**
* Child process executing the config & sourcing plugin events
@@ -43,7 +49,7 @@ export interface ActiveProjectShape extends ProjectShape {
specs?: FoundSpec[]
config: Promise | null
configChildProcess?: ConfigChildProcessShape | null
- preferences?: Preferences| null
+ preferences?: Preferences | null
browsers: FoundBrowser[] | null
}
@@ -82,6 +88,7 @@ export interface BaseErrorDataShape {
export interface CoreDataShape {
baseError: BaseErrorDataShape | null
dev: DevStateShape
+ localSettings: LocalSettingsDataShape
app: AppDataShape
currentProject: ActiveProjectShape | null
wizard: WizardDataShape
@@ -107,6 +114,11 @@ export function makeCoreData (): CoreDataShape {
refreshingNodePathAndVersion: null,
nodePathAndVersion: null,
},
+ localSettings: {
+ availableEditors: [],
+ preferences: devicePreferenceDefaults,
+ refreshing: null,
+ },
isAuthBrowserOpened: false,
currentProject: null,
wizard: {
diff --git a/packages/data-context/src/sources/EnvDataSource.ts b/packages/data-context/src/sources/EnvDataSource.ts
index 55f7c310b49c..00e2f2528a8e 100644
--- a/packages/data-context/src/sources/EnvDataSource.ts
+++ b/packages/data-context/src/sources/EnvDataSource.ts
@@ -6,4 +6,12 @@ import type { DataContext } from '../DataContext'
*/
export class EnvDataSource {
constructor (private ctx: DataContext) {}
+
+ get HTTP_PROXY () {
+ return process.env.HTTPS_PROXY || process.env.HTTP_PROXY
+ }
+
+ get NO_PROXY () {
+ return process.env.NO_PROXY
+ }
}
diff --git a/packages/data-context/src/util/urqlCacheKeys.ts b/packages/data-context/src/util/urqlCacheKeys.ts
index f4f322116f70..4382c8fe3861 100644
--- a/packages/data-context/src/util/urqlCacheKeys.ts
+++ b/packages/data-context/src/util/urqlCacheKeys.ts
@@ -17,5 +17,7 @@ export const urqlCacheKeys: Partial = {
BaseError: () => null,
ProjectPreferences: (data) => data.__typename,
VersionData: () => null,
+ LocalSettings: (data) => data.__typename,
+ LocalSettingsPreferences: () => null,
},
}
diff --git a/packages/desktop-gui/cypress/integration/settings_spec.js b/packages/desktop-gui/cypress/integration/settings_spec.js
index 716b4b151af1..b138152bbb4c 100644
--- a/packages/desktop-gui/cypress/integration/settings_spec.js
+++ b/packages/desktop-gui/cypress/integration/settings_spec.js
@@ -782,11 +782,11 @@ describe('Settings', () => {
describe('file preference panel', () => {
const availableEditors = [
- { id: 'atom', name: 'Atom', isOther: false, openerId: 'atom' },
- { id: 'vim', name: 'Vim', isOther: false, openerId: 'vim' },
- { id: 'sublime', name: 'Sublime Text', isOther: false, openerId: 'sublime' },
- { id: 'vscode', name: 'Visual Studio Code', isOther: false, openerId: 'vscode' },
- { id: 'other', name: 'Other', isOther: true, openerId: '' },
+ { id: 'atom', name: 'Atom', isOther: false, binary: 'atom' },
+ { id: 'vim', name: 'Vim', isOther: false, binary: 'vim' },
+ { id: 'sublime', name: 'Sublime Text', isOther: false, binary: 'sublime' },
+ { id: 'vscode', name: 'Visual Studio Code', isOther: false, binary: 'vscode' },
+ { id: 'other', name: 'Other', isOther: true, binary: '' },
]
beforeEach(function () {
diff --git a/packages/desktop-gui/cypress/integration/specs_list_spec.js b/packages/desktop-gui/cypress/integration/specs_list_spec.js
index 212157271ba9..92fbb4dead23 100644
--- a/packages/desktop-gui/cypress/integration/specs_list_spec.js
+++ b/packages/desktop-gui/cypress/integration/specs_list_spec.js
@@ -904,12 +904,12 @@ describe('Specs List', function () {
describe('opens files', function () {
beforeEach(function () {
this.availableEditors = [
- { id: 'computer', name: 'On Computer', isOther: false, openerId: 'computer' },
- { id: 'atom', name: 'Atom', isOther: false, openerId: 'atom' },
- { id: 'vim', name: 'Vim', isOther: false, openerId: 'vim' },
- { id: 'sublime', name: 'Sublime Text', isOther: false, openerId: 'sublime' },
- { id: 'vscode', name: 'Visual Studio Code', isOther: false, openerId: 'vscode' },
- { id: 'other', name: 'Other', isOther: true, openerId: '' },
+ { id: 'computer', name: 'On Computer', isOther: false, binary: 'computer' },
+ { id: 'atom', name: 'Atom', isOther: false, binary: 'atom' },
+ { id: 'vim', name: 'Vim', isOther: false, binary: 'vim' },
+ { id: 'sublime', name: 'Sublime Text', isOther: false, binary: 'sublime' },
+ { id: 'vscode', name: 'Visual Studio Code', isOther: false, binary: 'vscode' },
+ { id: 'other', name: 'Other', isOther: true, binary: '' },
]
cy.get('@spec').realHover()
diff --git a/packages/desktop-gui/src/settings/file-preference.jsx b/packages/desktop-gui/src/settings/file-preference.jsx
index 066502a1d49c..27a0b535036d 100644
--- a/packages/desktop-gui/src/settings/file-preference.jsx
+++ b/packages/desktop-gui/src/settings/file-preference.jsx
@@ -39,7 +39,7 @@ const FilePreference = observer(() => {
setOtherPath: action((otherPath) => {
const otherOption = _.find(state.editors, { isOther: true })
- otherOption.openerId = otherPath
+ otherOption.binary = otherPath
save(otherOption)
}),
}))
diff --git a/packages/desktop-gui/src/settings/file-preference_spec.jsx b/packages/desktop-gui/src/settings/file-preference_spec.jsx
index eddeb9eeab74..c787cff42198 100644
--- a/packages/desktop-gui/src/settings/file-preference_spec.jsx
+++ b/packages/desktop-gui/src/settings/file-preference_spec.jsx
@@ -8,11 +8,11 @@ import '../main.scss'
/* global cy, Cypress */
describe('FilePreference', () => {
const availableEditors = [
- { id: 'atom', name: 'Atom', isOther: false, openerId: 'atom' },
- { id: 'vim', name: 'Vim', isOther: false, openerId: 'vim' },
- { id: 'sublime', name: 'Sublime Text', isOther: false, openerId: 'sublime' },
- { id: 'vscode', name: 'Visual Studio Code', isOther: false, openerId: 'vscode' },
- { id: 'other', name: 'Other', isOther: true, openerId: '' },
+ { id: 'atom', name: 'Atom', isOther: false, binary: 'atom' },
+ { id: 'vim', name: 'Vim', isOther: false, binary: 'vim' },
+ { id: 'sublime', name: 'Sublime Text', isOther: false, binary: 'sublime' },
+ { id: 'vscode', name: 'Visual Studio Code', isOther: false, binary: 'vscode' },
+ { id: 'other', name: 'Other', isOther: true, binary: '' },
]
it('shows editor choice', () => {
diff --git a/packages/frontend-shared/cypress/support/mock-graphql/clientTestContext.ts b/packages/frontend-shared/cypress/support/mock-graphql/clientTestContext.ts
index 72cc184c011a..3127f5d1e5c2 100644
--- a/packages/frontend-shared/cypress/support/mock-graphql/clientTestContext.ts
+++ b/packages/frontend-shared/cypress/support/mock-graphql/clientTestContext.ts
@@ -1,4 +1,4 @@
-import type { CloudUser } from '../generated/test-cloud-graphql-types.gen'
+import type { AuthenticatedUserShape } from '@packages/data-context/src/data'
import type {
WizardStep,
CurrentProject,
@@ -8,6 +8,7 @@ import type {
TestingTypeEnum,
GlobalProject,
VersionData,
+ LocalSettings,
} from '../generated/test-graphql-types.gen'
import { resetTestNodeIdx } from './clientTestUtils'
import { stubBrowsers } from './stubgql-Browser'
@@ -24,6 +25,7 @@ export interface ClientTestContext {
}
versions: VersionData
isAuthBrowserOpened: boolean
+ localSettings: LocalSettings
wizard: {
step: WizardStep
canNavigateForward: boolean
@@ -37,7 +39,7 @@ export interface ClientTestContext {
chosenBrowser: null
browserErrorMessage: null
}
- user: Partial | null
+ user: AuthenticatedUserShape | null
cloudTypes: typeof cloudTypes
__mockPartial: any
}
@@ -89,6 +91,29 @@ export function makeClientTestContext (): ClientTestContext {
},
user: null,
cloudTypes,
+ localSettings: {
+ __typename: 'LocalSettings',
+ preferences: {
+ __typename: 'LocalSettingsPreferences',
+ autoScrollingEnabled: true,
+ useDarkSidebar: true,
+ watchForSpecChange: true,
+ },
+ availableEditors: [
+ {
+ __typename: 'Editor',
+ id: 'code',
+ name: 'VS Code',
+ binary: 'code',
+ },
+ {
+ __typename: 'Editor',
+ id: 'vim',
+ name: 'Vim',
+ binary: 'vim',
+ },
+ ],
+ },
__mockPartial: {},
}
}
diff --git a/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Query.ts b/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Query.ts
index ceccc21f24cb..e22b62c92770 100644
--- a/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Query.ts
+++ b/packages/frontend-shared/cypress/support/mock-graphql/stubgql-Query.ts
@@ -6,6 +6,9 @@ export const stubQuery: MaybeResolver = {
dev () {
return {}
},
+ localSettings (source, args, ctx) {
+ return ctx.localSettings
+ },
wizard (source, args, ctx) {
return ctx.wizard
},
diff --git a/packages/frontend-shared/src/components/Switch.vue b/packages/frontend-shared/src/components/Switch.vue
index bc1cf6b0c62c..f2cb274e7ca9 100644
--- a/packages/frontend-shared/src/components/Switch.vue
+++ b/packages/frontend-shared/src/components/Switch.vue
@@ -48,5 +48,7 @@ const sizeClasses = {
},
}
-defineEmits(['update'])
+defineEmits<{
+ (e: 'update', value: boolean): void
+}>()
diff --git a/packages/frontend-shared/src/locales/en-US.json b/packages/frontend-shared/src/locales/en-US.json
index 1be8b889b909..36b50c719058 100644
--- a/packages/frontend-shared/src/locales/en-US.json
+++ b/packages/frontend-shared/src/locales/en-US.json
@@ -312,7 +312,19 @@
},
"testingPreferences": {
"title": "Testing Preferences",
- "description": "Configure your testing environment with these flags"
+ "description": "Configure your testing environment with these flags",
+ "autoScrollingEnabled": {
+ "title": "Auto Scrolling Enabled",
+ "description": "Scroll behavior when running tests."
+ },
+ "watchForSpecChange": {
+ "title": "Watch for Spec Change",
+ "description": "Re-run specs when a file changes."
+ },
+ "useDarkSidebar": {
+ "title": "Dark sidebar",
+ "description": "Select the color theme of the app sidebar."
+ }
},
"footer": {
"text": "You can reconfigure the settings for this project if you’re experiencing issues with your Cypress configuration.",
diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql
index c2d86e27a6bf..cc13b042dab9 100644
--- a/packages/graphql/schemas/schema.graphql
+++ b/packages/graphql/schemas/schema.graphql
@@ -344,6 +344,16 @@ type DevState {
needsRelaunch: Boolean
}
+"""Represents an editor on the local machine"""
+type Editor {
+ """Binary that opens the editor"""
+ binary: String!
+ id: String!
+
+ """name of editor"""
+ name: String!
+}
+
"""Represents a spec on the file system"""
type FileParts implements Node {
"""
@@ -420,6 +430,22 @@ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://
"""
scalar JSON
+"""local settings on a device-by-device basis"""
+type LocalSettings {
+ availableEditors: [Editor!]!
+ preferences: LocalSettingsPreferences!
+}
+
+"""local setting preferences"""
+type LocalSettingsPreferences {
+ autoScrollingEnabled: Boolean
+ preferredEditorBinary: String
+ proxyBypass: String
+ proxyServer: String
+ useDarkSidebar: Boolean
+ watchForSpecChange: Boolean
+}
+
type Mutation {
"""Add project to projects array and cache it"""
addProject(
@@ -482,9 +508,13 @@ type Mutation {
"""Set active project to run tests on"""
setActiveProject(path: String!): Boolean
+ setAutoScrollingEnabled(value: Boolean!): Boolean
+ setPreferredEditorBinary(value: String!): Boolean
"""Save the projects preferences to cache"""
setProjectPreferences(browserPath: String!, testingType: TestingTypeEnum!): Query!
+ setUseDarkSidebar(value: Boolean!): Boolean
+ setWatchForSpecChange(value: Boolean!): Boolean
"""show the launchpad at the browser picker step"""
showElectronOnAppExit: Boolean
@@ -604,6 +634,9 @@ type Query {
"""Whether the app is in global mode or not"""
isInGlobalMode: Boolean!
+ """editors on the user local machine"""
+ localSettings: LocalSettings!
+
"""All known projects for the app"""
projects: [ProjectLike!]!
diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Editor.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Editor.ts
new file mode 100644
index 000000000000..ed144ff6f798
--- /dev/null
+++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Editor.ts
@@ -0,0 +1,17 @@
+import { objectType } from 'nexus'
+
+export const Editor = objectType({
+ name: 'Editor',
+ description: 'Represents an editor on the local machine',
+ definition (t) {
+ t.nonNull.string('id')
+
+ t.nonNull.string('name', {
+ description: 'name of editor',
+ })
+
+ t.nonNull.string('binary', {
+ description: 'Binary that opens the editor',
+ })
+ },
+})
diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-LocalSettings.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-LocalSettings.ts
new file mode 100644
index 000000000000..d3fc392a5c35
--- /dev/null
+++ b/packages/graphql/src/schemaTypes/objectTypes/gql-LocalSettings.ts
@@ -0,0 +1,34 @@
+import { objectType } from 'nexus'
+import { Editor } from './gql-Editor'
+
+export const LocalSettingsPreferences = objectType({
+ name: 'LocalSettingsPreferences',
+ description: 'local setting preferences',
+ definition (t) {
+ t.boolean('autoScrollingEnabled')
+ t.boolean('watchForSpecChange')
+ t.boolean('useDarkSidebar')
+ t.string('preferredEditorBinary')
+ t.string('proxyServer', {
+ resolve: (source, args, ctx) => ctx.env.HTTP_PROXY ?? null,
+ })
+
+ t.string('proxyBypass', {
+ resolve: (source, args, ctx) => ctx.env.NO_PROXY ?? null,
+ })
+ },
+})
+
+export const LocalSettings = objectType({
+ name: 'LocalSettings',
+ description: 'local settings on a device-by-device basis',
+ definition (t) {
+ t.nonNull.list.nonNull.field('availableEditors', {
+ type: Editor,
+ })
+
+ t.nonNull.field('preferences', {
+ type: LocalSettingsPreferences,
+ })
+ },
+})
diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts
index 2c23e79e9cf2..2e0996537c45 100644
--- a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts
+++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts
@@ -300,6 +300,54 @@ export const mutation = mutationType({
},
})
+ t.liveMutation('setAutoScrollingEnabled', {
+ type: 'Boolean',
+ args: {
+ value: nonNull(booleanArg()),
+ },
+ resolve: async (_, args, ctx) => {
+ await ctx.actions.localSettings.setDevicePreference('autoScrollingEnabled', args.value)
+
+ return true
+ },
+ })
+
+ t.liveMutation('setUseDarkSidebar', {
+ type: 'Boolean',
+ args: {
+ value: nonNull(booleanArg()),
+ },
+ resolve: async (_, args, ctx) => {
+ await ctx.actions.localSettings.setDevicePreference('useDarkSidebar', args.value)
+
+ return true
+ },
+ })
+
+ t.liveMutation('setWatchForSpecChange', {
+ type: 'Boolean',
+ args: {
+ value: nonNull(booleanArg()),
+ },
+ resolve: async (_, args, ctx) => {
+ await ctx.actions.localSettings.setDevicePreference('watchForSpecChange', args.value)
+
+ return true
+ },
+ })
+
+ t.liveMutation('setPreferredEditorBinary', {
+ type: 'Boolean',
+ args: {
+ value: nonNull(stringArg()),
+ },
+ resolve: async (_, args, ctx) => {
+ await ctx.actions.localSettings.setDevicePreference('preferredEditorBinary', args.value)
+
+ return true
+ },
+ })
+
t.liveMutation('showElectronOnAppExit', {
description: 'show the launchpad at the browser picker step',
resolve: (_, args, ctx) => {
diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
index 8b7f43ca8598..e95ebb5bd722 100644
--- a/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
+++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts
@@ -3,6 +3,7 @@ import { BaseError } from '.'
import { ProjectLike } from '..'
import { CurrentProject } from './gql-CurrentProject'
import { DevState } from './gql-DevState'
+import { LocalSettings } from './gql-LocalSettings'
import { VersionData } from './gql-VersionData'
import { Wizard } from './gql-Wizard'
@@ -63,5 +64,13 @@ export const Query = objectType({
description: 'Whether the browser has been opened for auth or not',
resolve: (source, args, ctx) => ctx.coreData.isAuthBrowserOpened,
})
+
+ t.nonNull.field('localSettings', {
+ type: LocalSettings,
+ description: 'editors on the user local machine',
+ resolve: (source, args, ctx) => {
+ return ctx.coreData.localSettings
+ },
+ })
},
})
diff --git a/packages/graphql/src/schemaTypes/objectTypes/index.ts b/packages/graphql/src/schemaTypes/objectTypes/index.ts
index 5c37816a0aa4..e6ed820b7b63 100644
--- a/packages/graphql/src/schemaTypes/objectTypes/index.ts
+++ b/packages/graphql/src/schemaTypes/objectTypes/index.ts
@@ -6,10 +6,12 @@ export * from './gql-Browser'
export * from './gql-CodeGenResult'
export * from './gql-CurrentProject'
export * from './gql-DevState'
+export * from './gql-Editor'
export * from './gql-FileParts'
export * from './gql-GeneratedSpec'
export * from './gql-GitInfo'
export * from './gql-GlobalProject'
+export * from './gql-LocalSettings'
export * from './gql-Mutation'
export * from './gql-ProjectPreferences'
export * from './gql-Query'
diff --git a/packages/reporter/cypress/support/utils.ts b/packages/reporter/cypress/support/utils.ts
index 758f21600fe3..20a12d51fc47 100644
--- a/packages/reporter/cypress/support/utils.ts
+++ b/packages/reporter/cypress/support/utils.ts
@@ -50,12 +50,12 @@ export const itHandlesFileOpening = ({ getRunner, selector, file, stackTrace = f
describe('when user has not already set opener and opens file', () => {
const availableEditors = [
- { id: 'computer', name: 'On Computer', isOther: false, openerId: 'computer' },
- { id: 'atom', name: 'Atom', isOther: false, openerId: 'atom' },
- { id: 'vim', name: 'Vim', isOther: false, openerId: 'vim' },
- { id: 'sublime', name: 'Sublime Text', isOther: false, openerId: 'sublime' },
- { id: 'vscode', name: 'Visual Studio Code', isOther: false, openerId: 'vscode' },
- { id: 'other', name: 'Other', isOther: true, openerId: '' },
+ { id: 'computer', name: 'On Computer', isOther: false, binary: 'computer' },
+ { id: 'atom', name: 'Atom', isOther: false, binary: 'atom' },
+ { id: 'vim', name: 'Vim', isOther: false, binary: 'vim' },
+ { id: 'sublime', name: 'Sublime Text', isOther: false, binary: 'sublime' },
+ { id: 'vscode', name: 'Visual Studio Code', isOther: false, binary: 'vscode' },
+ { id: 'other', name: 'Other', isOther: true, binary: '' },
]
beforeEach(() => {
diff --git a/packages/runner/cypress/support/verify-failures.js b/packages/runner/cypress/support/verify-failures.js
index d0975113d1e3..6f0c73c054c1 100644
--- a/packages/runner/cypress/support/verify-failures.js
+++ b/packages/runner/cypress/support/verify-failures.js
@@ -37,7 +37,7 @@ const verifyFailure = (options) => {
preferredOpener: {
id: 'foo-editor',
name: 'Foo',
- openerId: 'foo-editor',
+ binary: 'foo-editor',
isOther: false,
},
})
diff --git a/packages/server/lib/gui/events.ts b/packages/server/lib/gui/events.ts
index 341f4fb42da2..70723bdf0292 100644
--- a/packages/server/lib/gui/events.ts
+++ b/packages/server/lib/gui/events.ts
@@ -365,7 +365,24 @@ const handleEvent = function (options, bus, event, id, type, arg) {
case 'get:user:editor':
return editors.getUserEditor(true)
- .then(send)
+ .then((data) => {
+ // todo(lachlan): remove post 10.0
+ // just here to support an assumption in desktop-gui
+ // that there will be a "placeholder" empty editor
+ // where binary is null.
+ // moving forward, `binary` is non nullable (doesn't make sense).
+ data = {
+ ...data,
+ availableEditors: data.availableEditors.concat({
+ id: 'other',
+ name: 'Other',
+ binary: null,
+ isOther: true,
+ }),
+ }
+
+ return send(data)
+ })
.catch(sendErr)
case 'set:user:editor':
diff --git a/packages/server/lib/makeDataContext.ts b/packages/server/lib/makeDataContext.ts
index 0ef8a34c7ce0..4d6816d7c281 100644
--- a/packages/server/lib/makeDataContext.ts
+++ b/packages/server/lib/makeDataContext.ts
@@ -3,7 +3,7 @@ import os from 'os'
import type { App } from 'electron'
import specsUtil from './util/specs'
-import type { FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions, PlatformName, Preferences, SettingsOptions } from '@packages/types'
+import type { Editor, FindSpecs, FoundBrowser, LaunchArgs, LaunchOpts, OpenProjectLaunchOptions, PlatformName, Preferences, SettingsOptions } from '@packages/types'
import browserUtils from './browsers/utils'
import auth from './gui/auth'
import user from './user'
@@ -16,6 +16,8 @@ import findSystemNode from './util/find_system_node'
import { graphqlSchema } from '@packages/graphql/src/schema'
import type { InternalDataContextOptions } from '@packages/data-context/src/DataContext'
import { openExternal } from '@packages/server/lib/gui/links'
+import { getDevicePreferences, setDevicePreference } from './util/device_preferences'
+import { getUserEditor, setUserEditor } from './util/editors'
const { getBrowsers, ensureAndGetByNameOrPath } = browserUtils
@@ -124,6 +126,23 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext {
openExternal(url)
},
},
+ localSettingsApi: {
+ setDevicePreference (key, value) {
+ return setDevicePreference(key, value)
+ },
+
+ async getPreferences () {
+ return getDevicePreferences()
+ },
+ async setPreferredOpener (editor: Editor) {
+ await setUserEditor(editor)
+ },
+ async getAvailableEditors () {
+ const { availableEditors } = await getUserEditor(true)
+
+ return availableEditors
+ },
+ },
})
return ctx
diff --git a/packages/server/lib/saved_state.js b/packages/server/lib/saved_state.js
index bf619f2d1785..ad249721aade 100644
--- a/packages/server/lib/saved_state.js
+++ b/packages/server/lib/saved_state.js
@@ -35,6 +35,10 @@ ctSpecListWidth
firstOpened
lastOpened
promptsShown
+watchForSpecChange
+useDarkSidebar
+preferredEditorBinary
+
`.trim().split(/\s+/)
const formStatePath = (projectRoot) => {
diff --git a/packages/server/lib/server-base.ts b/packages/server/lib/server-base.ts
index 824ee4a15957..9ecc38b646f1 100644
--- a/packages/server/lib/server-base.ts
+++ b/packages/server/lib/server-base.ts
@@ -197,7 +197,7 @@ export abstract class ServerBase {
target: config.baseUrl && testingType === 'component' ? config.baseUrl : undefined,
})
- this._socket = new SocketCtor(config) as TSocket
+ this._socket = new SocketCtor(config, this.ctx) as TSocket
clientCertificates.loadClientCertificateConfig(config)
diff --git a/packages/server/lib/socket-base.ts b/packages/server/lib/socket-base.ts
index d4312b9be239..198048eb3439 100644
--- a/packages/server/lib/socket-base.ts
+++ b/packages/server/lib/socket-base.ts
@@ -11,13 +11,14 @@ import fixture from './fixture'
import task from './task'
import { ensureProp } from './util/class-helpers'
import { getUserEditor, setUserEditor } from './util/editors'
-import { openFile } from './util/file-opener'
+import { openFile, OpenFileDetails } from './util/file-opener'
import open from './util/open'
import type { DestroyableHttpServer } from './util/server_destroy'
import * as session from './session'
// eslint-disable-next-line no-duplicate-imports
import type { Socket } from '@packages/socket'
import path from 'path'
+import type { DataContext } from '@packages/data-context'
type StartListeningCallbacks = {
onSocketConnection: (socket: any) => void
@@ -77,7 +78,7 @@ export class SocketBase {
protected _io?: socketIo.SocketIOServer
protected testsDir: string | null
- constructor (config: Record) {
+ constructor (config: Record, private ctx: DataContext) {
this.ended = false
this.testsDir = null
}
@@ -485,7 +486,23 @@ export class SocketBase {
setUserEditor(editor)
})
- socket.on('open:file', (fileDetails) => {
+ socket.on('open:file', async (fileDetails: OpenFileDetails) => {
+ // todo(lachlan): post 10.0 we should not pass the
+ // editor (in the `fileDetails.where` key) from the
+ // front-end, but rather rely on the server context
+ // to grab the prefered editor, like I'm doing here,
+ // so we do not need to
+ // maintain two sources of truth for the preferred editor
+ // adding this conditional to maintain backwards compat with
+ // existing runner and reporter API.
+ if (process.env.LAUNCHPAD) {
+ fileDetails.where = {
+ binary: this.ctx.coreData.localSettings.preferences.preferredEditorBinary || 'computer',
+ }
+ }
+
+ debug('opening file %o', fileDetails)
+
openFile(fileDetails)
})
diff --git a/packages/server/lib/socket-ct.ts b/packages/server/lib/socket-ct.ts
index 1ce0d17188d5..35eb543abe8e 100644
--- a/packages/server/lib/socket-ct.ts
+++ b/packages/server/lib/socket-ct.ts
@@ -3,12 +3,13 @@ import type * as socketIo from '@packages/socket'
import devServer from '@packages/server/lib/plugins/dev-server'
import { SocketBase } from '@packages/server/lib/socket-base'
import type { DestroyableHttpServer } from '@packages/server/lib/util/server_destroy'
+import type { DataContext } from '@packages/data-context'
const debug = Debug('cypress:server:socket-ct')
export class SocketCt extends SocketBase {
- constructor (config: Record) {
- super(config)
+ constructor (config: Record, ctx: DataContext) {
+ super(config, ctx)
devServer.emitter.on('dev-server:compile:error', (error: string | undefined) => {
this.toRunner('dev-server:hmr:error', error)
diff --git a/packages/server/lib/socket-e2e.ts b/packages/server/lib/socket-e2e.ts
index 2d633831a4c5..505e549e3c7f 100644
--- a/packages/server/lib/socket-e2e.ts
+++ b/packages/server/lib/socket-e2e.ts
@@ -5,6 +5,7 @@ import { SocketBase } from './socket-base'
import { fs } from './util/fs'
import type { DestroyableHttpServer } from './util/server_destroy'
import * as studio from './studio'
+import type { DataContext } from '@packages/data-context'
const debug = Debug('cypress:server:socket-e2e')
@@ -15,8 +16,8 @@ const isSpecialSpec = (name) => {
export class SocketE2E extends SocketBase {
private testFilePath: string | null
- constructor (config: Record) {
- super(config)
+ constructor (config: Record, ctx: DataContext) {
+ super(config, ctx)
this.testFilePath = null
diff --git a/packages/server/lib/util/device_preferences.ts b/packages/server/lib/util/device_preferences.ts
new file mode 100644
index 000000000000..b5834072d11c
--- /dev/null
+++ b/packages/server/lib/util/device_preferences.ts
@@ -0,0 +1,23 @@
+import debugModule from 'debug'
+import savedState from '../saved_state'
+import { DevicePreferences, devicePreferenceDefaults } from '@packages/types/src/devicePreferences'
+
+const debug = debugModule('cypress:server:preferences')
+
+export async function setDevicePreference (key: K, value: DevicePreferences[K]) {
+ debug('set preference: %s: %s', key, value)
+
+ const state = await savedState.create()
+
+ state.set(key, value)
+}
+
+export async function getDevicePreferences (): Promise {
+ const cached = await (await savedState.create()).get()
+
+ const state = { ...devicePreferenceDefaults, ...cached }
+
+ debug('get preferences: %o', state)
+
+ return state
+}
diff --git a/packages/server/lib/util/editors.ts b/packages/server/lib/util/editors.ts
index f262bfb506e7..1771d408d551 100644
--- a/packages/server/lib/util/editors.ts
+++ b/packages/server/lib/util/editors.ts
@@ -2,64 +2,46 @@ import _ from 'lodash'
import Bluebird from 'bluebird'
import debugModule from 'debug'
-import { getEnvEditors, Editor } from './env-editors'
+import type { Editor, EditorsResult } from '@packages/types'
+import { getEnvEditors } from './env-editors'
import shell from './shell'
import savedState from '../saved_state'
-const debug = debugModule('cypress:server:editors')
-
-interface CyEditor {
- id: string
- name: string
- openerId: string
- isOther: boolean
-}
+export const osFileSystemExplorer = {
+ darwin: 'Finder',
+ win32: 'File Explorer',
+ linux: 'File System',
+} as const
-interface EditorsResult {
- preferredOpener?: CyEditor
- availableEditors?: CyEditor[]
-}
+const debug = debugModule('cypress:server:editors')
-const createEditor = (editor: Editor): CyEditor => {
+const createEditor = (editor: Editor): Editor => {
return {
id: editor.id,
name: editor.name,
- openerId: editor.binary,
- isOther: false,
+ binary: editor.binary,
}
}
-const getOtherEditor = (preferredOpener?: CyEditor) => {
+const getOtherEditor = (preferredOpener?: Editor): Editor | undefined => {
// if preferred editor is the 'other' option, use it since it has the
- // path (openerId) saved with it
- if (preferredOpener && preferredOpener.isOther) {
+ // path (binary) saved with it
+ if (preferredOpener && preferredOpener.id === 'other') {
return preferredOpener
}
- return {
- id: 'other',
- name: 'Other',
- openerId: '',
- isOther: true,
- }
+ return
}
-const computerOpener = (): CyEditor => {
- const names = {
- darwin: 'Finder',
- win32: 'File Explorer',
- linux: 'File System',
- }
-
+const computerOpener = (): Editor => {
return {
id: 'computer',
- name: names[process.platform] || names.linux,
- openerId: 'computer',
- isOther: false,
+ name: osFileSystemExplorer[process.platform] || osFileSystemExplorer.linux,
+ binary: 'computer',
}
}
-const getUserEditors = (): Bluebird => {
+const getUserEditors = async (): Promise => {
return Bluebird.filter(getEnvEditors(), (editor) => {
debug('check if user has editor %s with binary %s', editor.name, editor.binary)
@@ -72,18 +54,22 @@ const getUserEditors = (): Bluebird => {
.then((state) => {
return state.get('preferredOpener')
})
- .then((preferredOpener?: CyEditor) => {
+ .then((preferredOpener?: Editor) => {
debug('saved preferred editor: %o', preferredOpener)
const cyEditors = _.map(editors, createEditor)
+ const preferred = getOtherEditor(preferredOpener)
+
+ if (!preferred) {
+ return [computerOpener()].concat(cyEditors)
+ }
- // @ts-ignore
- return [computerOpener()].concat(cyEditors).concat([getOtherEditor(preferredOpener)])
+ return [computerOpener()].concat(cyEditors, preferred)
})
})
}
-export const getUserEditor = (alwaysIncludeEditors = false): Bluebird => {
+export const getUserEditor = async (alwaysIncludeEditors = false): Promise => {
debug('get user editor')
return savedState.create()
@@ -106,11 +92,10 @@ export const getUserEditor = (alwaysIncludeEditors = false): Bluebird {
+export const setUserEditor = async (editor: Editor) => {
debug('set user editor: %o', editor)
- return savedState.create()
- .then((state) => {
- state.set('preferredOpener', editor)
- })
+ const state = await savedState.create()
+
+ state.set('preferredOpener', editor)
}
diff --git a/packages/server/lib/util/env-editors.ts b/packages/server/lib/util/env-editors.ts
index ab29b264f002..805366d3f32d 100644
--- a/packages/server/lib/util/env-editors.ts
+++ b/packages/server/lib/util/env-editors.ts
@@ -1,4 +1,6 @@
-const linuxEditors = [
+import type { Editor } from '@packages/types'
+
+export const linuxEditors = [
{
id: 'atom',
binary: 'atom',
@@ -43,10 +45,14 @@ const linuxEditors = [
id: 'webstorm',
binary: 'webstorm',
name: 'WebStorm',
+ }, {
+ id: 'webstorm64',
+ binary: 'webstorm64.exe',
+ name: 'WebStorm 64-bit',
},
-]
+] as const
-const osxEditors = [
+export const macOSEditors = [
{
id: 'atom',
binary: 'atom',
@@ -120,9 +126,9 @@ const osxEditors = [
binary: 'vim',
name: 'Vim',
},
-]
+] as const
-const windowsEditors = [
+export const windowsEditors = [
{
id: 'brackets',
binary: 'Brackets.exe',
@@ -187,23 +193,13 @@ const windowsEditors = [
id: 'webstorm',
binary: 'webstorm.exe',
name: 'WebStorm',
- }, {
- id: 'webstorm64',
- binary: 'webstorm64.exe',
- name: 'WebStorm (64-bit)',
},
-]
-
-export interface Editor {
- id: string
- binary: string
- name: string
-}
+] as const
-export const getEnvEditors = (): Editor[] => {
+export const getEnvEditors = (): readonly Editor[] => {
switch (process.platform) {
case 'darwin':
- return osxEditors
+ return macOSEditors
case 'win32':
return windowsEditors
default:
diff --git a/packages/server/lib/util/file-opener.ts b/packages/server/lib/util/file-opener.ts
index 7e4fdc183cc2..92f2061eb1fc 100644
--- a/packages/server/lib/util/file-opener.ts
+++ b/packages/server/lib/util/file-opener.ts
@@ -3,12 +3,21 @@ import launchEditor from 'launch-editor'
const debug = debugModule('cypress:server:file-opener')
-export const openFile = (fileDetails) => {
+export interface OpenFileDetails {
+ file: string
+ where: {
+ binary: string
+ }
+ line: number
+ column: number
+}
+
+export const openFile = (fileDetails: OpenFileDetails) => {
debug('open file: %o', fileDetails)
- const openerId = fileDetails.where.openerId
+ const binary = fileDetails.where.binary
- if (openerId === 'computer') {
+ if (binary === 'computer') {
try {
require('electron').shell.showItemInFolder(fileDetails.file)
} catch (err) {
@@ -20,7 +29,7 @@ export const openFile = (fileDetails) => {
const { file, line, column } = fileDetails
- launchEditor(`${file}:${line}:${column}`, `"${openerId}"`, (__, errMsg) => {
+ launchEditor(`${file}:${line}:${column}`, `"${binary}"`, (__, errMsg) => {
debug('error opening file: %s', errMsg)
})
}
diff --git a/packages/server/test/unit/util/editors_spec.ts b/packages/server/test/unit/util/editors_spec.ts
index 7e831aa64914..3e962390757a 100644
--- a/packages/server/test/unit/util/editors_spec.ts
+++ b/packages/server/test/unit/util/editors_spec.ts
@@ -1,4 +1,3 @@
-import _ from 'lodash'
import Bluebird from 'bluebird'
import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'
@@ -67,37 +66,17 @@ describe('lib/util/editors', () => {
sinon.restore()
})
- it('returns a list of editors on the user\'s system with an "On Computer" option prepended and an "Other" option appended', () => {
- return getUserEditor().then(({ availableEditors }) => {
- const names = _.map(availableEditors, 'name')
-
- expect(names).to.eql(['Finder', 'Sublime Text', 'Visual Studio Code', 'Vim', 'Other'])
- expect(availableEditors[0]).to.eql({
- id: 'computer',
- name: 'Finder',
- isOther: false,
- openerId: 'computer',
- })
-
- expect(availableEditors[4]).to.eql({
- id: 'other',
- name: 'Other',
- isOther: true,
- openerId: '',
- })
- })
- })
-
it('includes user-set path for "Other" option if available', () => {
// @ts-ignore
savedState.create.resolves({
get () {
- return { isOther: true, openerId: '/path/to/editor' }
+ return { isOther: true, binary: '/path/to/editor', id: 'other' }
},
})
return getUserEditor().then(({ availableEditors }) => {
- expect(availableEditors[4].openerId).to.equal('/path/to/editor')
+ console.log(availableEditors)
+ expect(availableEditors[4].binary).to.equal('/path/to/editor')
})
})
@@ -143,7 +122,7 @@ describe('lib/util/editors', () => {
})
return getUserEditor(true).then(({ availableEditors, preferredOpener }) => {
- expect(availableEditors).to.have.length(5)
+ expect(availableEditors).to.have.length(4)
expect(preferredOpener).to.equal(preferredOpener)
})
})
@@ -168,14 +147,14 @@ describe('lib/util/editors', () => {
it('returns available editors if preferred opener has not been saved', () => {
return getUserEditor(false).then(({ availableEditors, preferredOpener }) => {
- expect(availableEditors).to.have.length(5)
+ expect(availableEditors).to.have.length(4)
expect(preferredOpener).to.be.undefined
})
})
it('is default', () => {
return getUserEditor().then(({ availableEditors, preferredOpener }) => {
- expect(availableEditors).to.have.length(5)
+ expect(availableEditors).to.have.length(4)
expect(preferredOpener).to.be.undefined
})
})
diff --git a/packages/types/src/devicePreferences.ts b/packages/types/src/devicePreferences.ts
new file mode 100644
index 000000000000..53342f53dcaf
--- /dev/null
+++ b/packages/types/src/devicePreferences.ts
@@ -0,0 +1,13 @@
+export interface DevicePreferences {
+ watchForSpecChange?: boolean
+ useDarkSidebar?: boolean
+ autoScrollingEnabled?: boolean
+ preferredEditorBinary?: string | undefined
+}
+
+export const devicePreferenceDefaults: DevicePreferences = {
+ watchForSpecChange: true,
+ useDarkSidebar: true,
+ autoScrollingEnabled: true,
+ preferredEditorBinary: undefined,
+}
diff --git a/packages/types/src/editors.ts b/packages/types/src/editors.ts
new file mode 100644
index 000000000000..718f8d4ddee7
--- /dev/null
+++ b/packages/types/src/editors.ts
@@ -0,0 +1,10 @@
+export interface Editor {
+ id: string
+ binary: string
+ name: string
+}
+
+export interface EditorsResult {
+ preferredOpener?: Editor
+ availableEditors: Editor[]
+}
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 370c059a5f47..82cb43d5ac5c 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -2,10 +2,14 @@ export * from './cache'
export * from './constants'
+export * from './devicePreferences'
+
export * from './driver'
export * from './spec'
+export * from './editors'
+
export type {
AllPackages,
AllPackageTypes,
diff --git a/packages/ui-components/cypress/integration/editor-picker_spec.jsx b/packages/ui-components/cypress/integration/editor-picker_spec.jsx
index 78f944babd20..dfea08ece06a 100644
--- a/packages/ui-components/cypress/integration/editor-picker_spec.jsx
+++ b/packages/ui-components/cypress/integration/editor-picker_spec.jsx
@@ -15,13 +15,13 @@ describe('', () => {
beforeEach(() => {
defaultProps = {
- chosen: { id: 'vscode', name: 'VS Code', openerId: 'vscode', isOther: false },
+ chosen: { id: 'vscode', name: 'VS Code', binary: 'vscode', isOther: false },
editors: [
- { id: 'computer', name: 'On Computer', openerId: 'computer', isOther: false, description: 'Opens on computer etc etc' },
- { id: 'atom', name: 'Atom', openerId: 'atom', isOther: false },
- { id: 'sublime', name: 'Sublime Text', openerId: 'sublime', isOther: false },
- { id: 'vscode', name: 'VS Code', openerId: 'vscode', isOther: false },
- { id: 'other', name: 'Other', openerId: '', isOther: true, description: 'Enter the full path etc etc' },
+ { id: 'computer', name: 'On Computer', binary: 'computer', isOther: false, description: 'Opens on computer etc etc' },
+ { id: 'atom', name: 'Atom', binary: 'atom', isOther: false },
+ { id: 'sublime', name: 'Sublime Text', binary: 'sublime', isOther: false },
+ { id: 'vscode', name: 'VS Code', binary: 'vscode', isOther: false },
+ { id: 'other', name: 'Other', binary: '', isOther: true, description: 'Enter the full path etc etc' },
],
onSelect: () => {},
}
@@ -89,7 +89,7 @@ describe('', () => {
})
it('populates path if specified', () => {
- defaultProps.editors[4].openerId = '/path/to/my/editor'
+ defaultProps.editors[4].binary = '/path/to/my/editor'
cy.render(render, )
cy.contains('Other').find('input[type="text"]').should('have.value', '/path/to/my/editor')
@@ -106,7 +106,7 @@ describe('', () => {
setOtherPath: action((otherPath) => {
const otherOption = _.find(state.editors, { isOther: true })
- otherOption.openerId = otherPath
+ otherOption.binary = otherPath
}),
}))
@@ -133,7 +133,7 @@ describe('', () => {
cy.contains('Other').find('input[type="text"]').type(` ${path} `, { delay: 0 })
.should(() => {
- expect(onSelect.lastCall.args[0].openerId).to.equal(path)
+ expect(onSelect.lastCall.args[0].binary).to.equal(path)
})
})
@@ -148,7 +148,7 @@ describe('', () => {
cy.contains('Other').find('input[type="text"]').type(letter, { delay: 0 })
.should(() => {
expect(onSelect.lastCall.args[0].id).to.equal('other')
- expect(onSelect.lastCall.args[0].openerId).to.equal(pathSoFar)
+ expect(onSelect.lastCall.args[0].binary).to.equal(pathSoFar)
})
})
})
diff --git a/packages/ui-components/cypress/integration/file-opener_spec.jsx b/packages/ui-components/cypress/integration/file-opener_spec.jsx
index 302c78611ca2..43bad4fe1b68 100644
--- a/packages/ui-components/cypress/integration/file-opener_spec.jsx
+++ b/packages/ui-components/cypress/integration/file-opener_spec.jsx
@@ -16,16 +16,16 @@ const fileDetails = {
const preferredOpener = {
id: 'vscode',
name: 'VS Code',
- openerId: 'vscode',
+ binary: 'vscode',
isOther: false,
}
const availableEditors = [
- { id: 'computer', name: 'On Computer', openerId: 'computer', isOther: false, description: 'Opens on computer etc etc' },
- { id: 'atom', name: 'Atom', openerId: 'atom', isOther: false },
- { id: 'sublime', name: 'Sublime Text', openerId: 'sublime', isOther: false },
- { id: 'vscode', name: 'VS Code', openerId: 'vscode', isOther: false },
- { id: 'other', name: 'Other', openerId: '', isOther: true, description: 'Enter the full path etc etc' },
+ { id: 'computer', name: 'On Computer', binary: 'computer', isOther: false, description: 'Opens on computer etc etc' },
+ { id: 'atom', name: 'Atom', binary: 'atom', isOther: false },
+ { id: 'sublime', name: 'Sublime Text', binary: 'sublime', isOther: false },
+ { id: 'vscode', name: 'VS Code', binary: 'vscode', isOther: false },
+ { id: 'other', name: 'Other', binary: '', isOther: true, description: 'Enter the full path etc etc' },
]
describe('', () => {
diff --git a/packages/ui-components/src/file-opener/editor-picker-modal.tsx b/packages/ui-components/src/file-opener/editor-picker-modal.tsx
index 7707519c95ba..4bdcc64e5a0c 100644
--- a/packages/ui-components/src/file-opener/editor-picker-modal.tsx
+++ b/packages/ui-components/src/file-opener/editor-picker-modal.tsx
@@ -25,7 +25,7 @@ const validate = (chosenEditor: Editor) => {
let isValid = !!chosenEditor && !!chosenEditor.id
let validationMessage = 'Please select a preference'
- if (isValid && chosenEditor.isOther && !chosenEditor.openerId) {
+ if (isValid && chosenEditor.isOther && !chosenEditor.binary) {
isValid = false
validationMessage = 'Please enter the path for the "Other" editor'
}
@@ -42,7 +42,7 @@ const EditorPickerModal = observer(({ chosenEditor, editors, isOpen, onClose, on
const otherOption = _.find(external.editors, { isOther: true })
if (otherOption) {
- otherOption.openerId = otherPath
+ otherOption.binary = otherPath
}
}),
}), { editors })
diff --git a/packages/ui-components/src/file-opener/editor-picker.tsx b/packages/ui-components/src/file-opener/editor-picker.tsx
index 541e70450920..0dd1b78e3ba1 100644
--- a/packages/ui-components/src/file-opener/editor-picker.tsx
+++ b/packages/ui-components/src/file-opener/editor-picker.tsx
@@ -30,7 +30,7 @@ const EditorPicker = observer(({ chosen = {}, editors, onSelect, onUpdateOtherPa
diff --git a/packages/ui-components/src/file-opener/file-model.ts b/packages/ui-components/src/file-opener/file-model.ts
index 9e6d1b6734cf..7041f66784d9 100644
--- a/packages/ui-components/src/file-opener/file-model.ts
+++ b/packages/ui-components/src/file-opener/file-model.ts
@@ -12,7 +12,7 @@ export interface FileDetails {
export interface Editor {
id: string
name: string
- openerId: string
+ binary: string
isOther: boolean
description?: string
}
diff --git a/system-tests/projects/e2e/cypress/support/util.js b/system-tests/projects/e2e/cypress/support/util.js
index da5ebf6b0ccd..6ec50668203c 100644
--- a/system-tests/projects/e2e/cypress/support/util.js
+++ b/system-tests/projects/e2e/cypress/support/util.js
@@ -35,7 +35,7 @@ export const verify = (ctx, options) => {
preferredOpener: {
id: 'foo-editor',
name: 'Foo',
- openerId: 'foo-editor',
+ binary: 'foo-editor',
isOther: false,
},
})