diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md index 021459d2ab..6890f22955 100644 --- a/CODE-OF-CONDUCT.md +++ b/CODE-OF-CONDUCT.md @@ -1,27 +1,27 @@ # Contributor Code of Conduct -As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all individuals who contribute by reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and participating in other project-related activities. -We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. +We are committed to making participation in this project a harassment-free experience for everyone, regardless of experience level, gender, gender identity or expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. -Examples of unacceptable behavior by participants include: +Examples of unacceptable behavior include: -- The use of sexualized language or imagery -- Personal attacks -- Trolling or insulting/derogatory comments -- Public or private harassment -- Publishing other's private information, such as physical or electronic addresses, without explicit permission -- Other unethical or unprofessional conduct +* Using sexualized language or imagery +* Personal attacks +* Trolling, insulting, or derogatory comments +* Public or private harassment +* Publishing others’ private information, such as physical or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that do not align with this Code of Conduct, or to temporarily or permanently ban any contributor for behavior they deem inappropriate, threatening, offensive, or harmful. -By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. +By adopting this Code of Conduct, project maintainers commit to applying these principles fairly and consistently across all aspects of project management. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by emailing a project maintainer via [this contact form](https://developer.wordpress.com/contact/?g21-subject=Code%20of%20Conduct), with a subject that includes `Code of Conduct`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer via [this contact form](https://developer.wordpress.com/contact/?g21-subject=Code%20of%20Conduct) with a subject line that includes `Code of Conduct`. All complaints will be reviewed and investigated and will result in a response deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality regarding the reporter of an incident. -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version]. [homepage]: https://contributor-covenant.org -[version]: https://contributor-covenant.org/version/1/3/0/ +[version]: https://contributor-covenant.org/version/1/3/0/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9274062bae..ab9a1bbda2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,18 +1,18 @@ # Contributing Guidelines -We welcome contributions to WordPress Studio, whether they are bug reports, feature requests, or code changes. Please read the following guidelines to ensure that your contributions are accepted quickly and easily. +We welcome contributions to WordPress Studio, whether they are bug reports, feature requests, or code changes. Please read the following guidelines to help ensure your contributions are reviewed efficiently. ## Expectations -- We expect all contributors to follow our [Code of Conduct](./CODE-OF-CONDUCT.md). -- We expect all contributors to follow our [Security Policy](./SECURITY.md). -- By submitting a pull request, you agree to release your code under the project's [License](./LICENSE.md). +* All contributors are expected to follow our [Code of Conduct](./CODE-OF-CONDUCT.md). +* All contributors are expected to follow our [Security Policy](./SECURITY.md). +* By submitting a pull request, you agree to release your code under the project’s [License](./LICENSE.md). ## How to Contribute ### Reporting Security Issues -Please see our [security policy](./SECURITY.md). +Please see our [Security Policy](./SECURITY.md). ### Reporting General Issues @@ -20,13 +20,13 @@ If you find a bug or have a feature request, please [open an issue](https://gith ### Code Contributions -For information on setting up your development environment for contributing code, see the [Code Contributions](./docs/code-contributions.md). +For instructions on setting up your development environment, see the [Code Contributions guide](./docs/code-contributions.md). -We are truly grateful for any pull requests you open, and we assure you of our welcoming and respectful approach. We will review and consider all pull requests, valuing the diverse contributions, but we don’t guarantee that all proposed changes will be merged into the core. +We are truly grateful for all pull requests, and we aim to provide a welcoming and respectful review process. We will review and consider every submission; however, we cannot guarantee that all proposed changes will be merged into the core project. -The most desirable pull requests are: +The most valuable pull requests include: -- Bug fixes for existing features. -- Enhancements that improve compatibility with different system versions, browsers, PHP or WP versions, WordPress plugins, or environments in general. +* Bug fixes for existing features +* Enhancements that improve compatibility across different system versions, browsers, PHP versions, WordPress versions, plugins, or environments -We recommend [adding an issue](https://github.com/Automattic/studio/issues/new/choose) for new features so we can review the plan before you start work on the pull request. +For new features, we recommend first [opening an issue](https://github.com/Automattic/studio/issues/new/choose) so we can review and discuss the proposal before you begin development. \ No newline at end of file diff --git a/README.md b/README.md index 4a3e3d6dbe..cfae338eec 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -![Crash Free Sessions Rate](https://img.shields.io/badge/Crash_Free_Session_Rate-98.31%25-blue) - # WordPress Studio -[WordPress Studio](https://developer.wordpress.com/studio/) is an open source desktop application for creating and managing WordPress sites and testing and building plugins and themes locally. Powered by [WordPress Playground](https://developer.wordpress.org/playground/) and [WordPress.com](https://wordpress.com/), it streamlines modern WordPress development workflows and requires no external dependencies. +[WordPress Studio](https://developer.wordpress.com/studio/) is an open-source desktop application for creating and managing WordPress sites, as well as building and testing plugins and themes locally. Powered by [WordPress Playground](https://developer.wordpress.org/playground/) and [WordPress.com](https://wordpress.com/), it streamlines modern WordPress development workflows and requires no external dependencies. Spin up sites in seconds, sync with WordPress.com or Pressable, or import any WordPress site to work on it locally. Use the Studio CLI to access WordPress Studio features outside the desktop application. Share live preview links with clients, and collaborate with the built-in AI Assistant that runs WP-CLI commands natively. @@ -10,12 +8,12 @@ Spin up sites in seconds, sync with WordPress.com or Pressable, or import any Wo ## Get started -WordPress Studio is free to use for Mac and Windows. Simply download the app to start building and testing — no dependencies required. +WordPress Studio is free to use on Mac and Windows. Simply download the app to start building and testing — no dependencies required. [Download WordPress Studio](https://developer.wordpress.com/studio/) for: -- macOS (Intel or Apple Silicon) -- Windows +* macOS (Intel or Apple Silicon) +* Windows ## Highlights @@ -23,43 +21,43 @@ WordPress Studio is free to use for Mac and Windows. Simply download the app to ![WordPress Studio - Selective Sync](/docs/assets/wordpress-studio-sync.png) -Push updates or pull down a WordPress.com or Pressable production or staging site with just a few clicks. Choose exactly which parts of your site to sync, like specific plugins, standalone themes, or the database, so you’re always in control. [Learn more about Studio Sync →](https://developer.wordpress.com/docs/developer-tools/studio/sync/) +Push updates or pull a WordPress.com or Pressable production or staging site with just a few clicks. Choose exactly which parts of your site to sync—such as specific plugins, standalone themes, or the database—so you’re always in control. [Learn more about Studio Sync →](https://developer.wordpress.com/docs/developer-tools/studio/sync/) ### Free Cloud-hosted Preview Sites ![WordPress Studio - Preview Sites](/docs/assets/wordpress-studio-preview-sites.png) -Preview, polish, then hand it off. Share a stable, cloud-hosted preview link that your clients or team can access at any time. Preview sites expire after seven days without updates. [Learn more about Preview Sites →](https://developer.wordpress.com/docs/developer-tools/studio/preview-sites/) +Preview, polish, and hand off your work. Share a stable, cloud-hosted preview link that your clients or team can access at any time. Preview sites expire after seven days without updates. [Learn more about Preview Sites →](https://developer.wordpress.com/docs/developer-tools/studio/preview-sites/) ### AI Assistant ![WordPress Studio - AI Assistant](/docs/assets/wordpress-studio-ai-assistant.png) -Skip the repetitive setup and ask the assistant to install plugins, create pages, or run WP-CLI commands without leaving the app or searching for syntax. [Learn more about the Studio Assistant →](https://developer.wordpress.com/docs/developer-tools/studio/assistant/) +Skip repetitive setup and ask the assistant to install plugins, create pages, or run WP-CLI commands — all without leaving the app or searching for syntax. [Learn more about the Studio Assistant →](https://developer.wordpress.com/docs/developer-tools/studio/assistant/) ### Powered by WordPress Playground ![WordPress Studio - Powered by Playground](/docs/assets/wordpress-studio-powered-by-playground.png) -Studio stays aligned with the latest innovations in WordPress development, giving you early access to cutting-edge tools, version support, and experimental features without needing to configure anything manually or run any dependencies. +Studio stays aligned with the latest innovations in WordPress development, giving you early access to cutting-edge tools, version support, and experimental features without needing to configure anything manually or install additional dependencies. ## Explore the documentation [Review the documentation](https://developer.wordpress.com/docs/developer-tools/studio/) for: -- Installation instructions -- Feature guides -- Troubleshooting and FAQs +* Installation instructions +* Feature guides +* Troubleshooting and FAQs ## Give feedback and contribute We’d love to hear about your experience using Studio. If you have questions, suggestions, or run into issues, reach out to our [Happiness Engineers](https://developer.wordpress.com/contact/). Because Studio is open source, you can also: -- Open a GitHub Issue to to [suggest ideas](https://github.com/Automattic/studio/issues/new?assignees=&labels=%5BType%5D+Feature+Request&projects=&template=feature_request.yml&title=Feature+Request%3A) or [report bugs](https://github.com/Automattic/studio/issues/new?assignees=&labels=Needs+triage%2C%5BType%5D+Bug&projects=&template=bug_report.yml) -- Submit pull requests for bug fixes and enhancements -- Proposals for new features may require additional review and discussion +* Open a GitHub Issue to [suggest ideas](https://github.com/Automattic/studio/issues/new?assignees=&labels=%5BType%5D+Feature+Request&projects=&template=feature_request.yml&title=Feature+Request%3A) or [report bugs](https://github.com/Automattic/studio/issues/new?assignees=&labels=Needs+triage%2C%5BType%5D+Bug&projects=&template=bug_report.yml) +* Submit pull requests for bug fixes and enhancements +* Propose new features (which may require additional review and discussion) -For details, please see our [Contributing Guidelines](CONTRIBUTING.md) and [Code Contributions](docs/code-contributions.md) guide. +For details, see our [Contributing Guidelines](CONTRIBUTING.md) and [Code Contributions](docs/code-contributions.md) guide. ## License diff --git a/src/modules/user-settings/lib/editor.ts b/src/modules/user-settings/lib/editor.ts index 0140248594..2e8845cef3 100644 --- a/src/modules/user-settings/lib/editor.ts +++ b/src/modules/user-settings/lib/editor.ts @@ -1,6 +1,7 @@ import { __ } from '@wordpress/i18n'; export const SUPPORTED_EDITORS = [ + 'Antigravity', 'cursor', 'vscode', 'phpstorm', @@ -8,20 +9,31 @@ export const SUPPORTED_EDITORS = [ 'webstorm', 'sublime', ] as const; -export type SupportedEditor = ( typeof SUPPORTED_EDITORS )[ number ]; +export type SupportedEditor = (typeof SUPPORTED_EDITORS)[number]; export type SupportedEditorConfig = { label: string; - url: ( path: string ) => string; + url: (path: string) => string; macOSBundleId: string; winPaths: string[]; }; -export const supportedEditorConfig: Record< SupportedEditor, SupportedEditorConfig > = { +export const supportedEditorConfig: Record = { + antigravity: { + // translators: "Antigravity" is the brand name for an IDE and does not need to be translated + label: __('Antigravity'), + url: (path: string) => `antigravity://file/${path}?windowId=_blank`, + macOSBundleId: 'com.google.Aantigravity', + winPaths: [ + '%LOCALAPPDATA%\\Programs\\Antigravity\\Antigravity.exe', + '%PROGRAMFILES%\\Antigravity\\Antigravity.exe', + '%PROGRAMFILES(X86)%\\Antigravity\\Antigravity.exe', + ], + }, vscode: { // translators: "VS Code" is the brand name for an IDE and does not need to be translated - label: __( 'VS Code' ), - url: ( path: string ) => `vscode://file/${ path }?windowId=_blank`, + label: __('VS Code'), + url: (path: string) => `vscode://file/${path}?windowId=_blank`, macOSBundleId: 'com.microsoft.VSCode', winPaths: [ '%LOCALAPPDATA%\\Programs\\Microsoft VS Code\\code.exe', @@ -31,8 +43,8 @@ export const supportedEditorConfig: Record< SupportedEditor, SupportedEditorConf }, phpstorm: { // translators: "PhpStorm" is the brand name for an IDE and does not need to be translated - label: __( 'PhpStorm' ), - url: ( path: string ) => `phpstorm://open?file=${ path }`, + label: __('PhpStorm'), + url: (path: string) => `phpstorm://open?file=${path}`, macOSBundleId: 'com.jetbrains.PhpStorm', winPaths: [ '%LOCALAPPDATA%\\Programs\\PhpStorm\\bin\\phpstorm64.exe', @@ -41,8 +53,8 @@ export const supportedEditorConfig: Record< SupportedEditor, SupportedEditorConf }, webstorm: { // translators: "WebStorm" is the brand name for an IDE and does not need to be translated - label: __( 'WebStorm' ), - url: ( path: string ) => `webstorm://open?file=${ path }`, + label: __('WebStorm'), + url: (path: string) => `webstorm://open?file=${path}`, macOSBundleId: 'com.jetbrains.WebStorm', winPaths: [ '%LOCALAPPDATA%\\Programs\\WebStorm\\bin\\webstorm64.exe', @@ -51,8 +63,8 @@ export const supportedEditorConfig: Record< SupportedEditor, SupportedEditorConf }, windsurf: { // translators: "Windsurf" is the brand name for an IDE and does not need to be translated - label: __( 'Windsurf' ), - url: ( path: string ) => `windsurf://file/${ path }?windowId=_blank`, + label: __('Windsurf'), + url: (path: string) => `windsurf://file/${path}?windowId=_blank`, macOSBundleId: 'com.exafunction.windsurf', winPaths: [ '%LOCALAPPDATA%\\Programs\\Windsurf\\Windsurf.exe', @@ -61,8 +73,8 @@ export const supportedEditorConfig: Record< SupportedEditor, SupportedEditorConf }, cursor: { // translators: "Cursor" is the brand name for an IDE and does not need to be translated - label: __( 'Cursor' ), - url: ( path: string ) => `cursor://file/${ path }?windowId=_blank`, + label: __('Cursor'), + url: (path: string) => `cursor://file/${path}?windowId=_blank`, macOSBundleId: 'com.todesktop.230313mzl4w4u92', winPaths: [ '%LOCALAPPDATA%\\Programs\\cursor\\Cursor.exe', @@ -71,8 +83,8 @@ export const supportedEditorConfig: Record< SupportedEditor, SupportedEditorConf }, sublime: { // translators: "Sublime Text" is the brand name for an IDE and does not need to be translated - label: __( 'Sublime Text' ), - url: ( path: string ) => `subl://open?url=file://${ path }`, + label: __('Sublime Text'), + url: (path: string) => `subl://open?url=file://${path}`, macOSBundleId: 'com.sublimetext.4', winPaths: [ '%PROGRAMFILES%\\Sublime Text\\sublime_text.exe', diff --git a/src/stores/tests/installed-apps-api.test.ts b/src/stores/tests/installed-apps-api.test.ts index ddd6c9d835..921df5f02f 100644 --- a/src/stores/tests/installed-apps-api.test.ts +++ b/src/stores/tests/installed-apps-api.test.ts @@ -8,15 +8,15 @@ import { selectUninstalledTerminals, } from 'src/stores/installed-apps-api'; -jest.mock( 'src/lib/get-ipc-api', () => ( { +jest.mock('src/lib/get-ipc-api', () => ({ getIpcApi: jest.fn(), -} ) ); +})); -jest.mock( 'src/lib/app-globals', () => ( { - getAppGlobals: jest.fn( () => ( { +jest.mock('src/lib/app-globals', () => ({ + getAppGlobals: jest.fn(() => ({ platform: 'darwin', - } ) ), -} ) ); + })), +})); const mockIpcApi = { getInstalledAppsAndTerminals: jest.fn(), @@ -26,21 +26,21 @@ const mockIpcApi = { saveUserTerminal: jest.fn(), }; -( getIpcApi as jest.Mock ).mockReturnValue( mockIpcApi ); +(getIpcApi as jest.Mock).mockReturnValue(mockIpcApi); const createTestStore = () => { - return configureStore( { + return configureStore({ reducer: { - [ installedAppsApi.reducerPath ]: installedAppsApi.reducer, + [installedAppsApi.reducerPath]: installedAppsApi.reducer, }, - middleware: ( getDefaultMiddleware ) => - getDefaultMiddleware().concat( installedAppsApi.middleware ), - } ); + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat(installedAppsApi.middleware), + }); }; const createMockInstalledApps = ( - installedApps: Partial< InstalledApps > = {} -): InstalledApps => ( { + installedApps: Partial = {} +): InstalledApps => ({ vscode: false, phpstorm: false, webstorm: false, @@ -52,210 +52,210 @@ const createMockInstalledApps = ( warp: false, ghostty: false, ...installedApps, -} ); +}); -describe( 'Installed Apps API', () => { - beforeEach( () => { +describe('Installed Apps API', () => { + beforeEach(() => { jest.clearAllMocks(); - } ); + }); - describe( 'getInstalledApps', () => { - it( 'should fetch installed apps and terminals', async () => { - const mockInstalledApps = createMockInstalledApps( { vscode: true, cursor: true } ); - mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce( mockInstalledApps ); + describe('getInstalledApps', () => { + it('should fetch installed apps and terminals', async () => { + const mockInstalledApps = createMockInstalledApps({ vscode: true, cursor: true }); + mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce(mockInstalledApps); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getInstalledApps.initiate( undefined ) + installedAppsApi.endpoints.getInstalledApps.initiate(undefined) ); - expect( mockIpcApi.getInstalledAppsAndTerminals ).toHaveBeenCalledTimes( 1 ); - expect( result.data ).toEqual( mockInstalledApps ); - } ); - } ); + expect(mockIpcApi.getInstalledAppsAndTerminals).toHaveBeenCalledTimes(1); + expect(result.data).toEqual(mockInstalledApps); + }); + }); - describe( 'getUserEditor', () => { - it( 'should return user preference when set', async () => { - mockIpcApi.getUserEditor.mockResolvedValueOnce( 'windsurf' ); + describe('getUserEditor', () => { + it('should return user preference when set', async () => { + mockIpcApi.getUserEditor.mockResolvedValueOnce('windsurf'); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getUserEditor.initiate( undefined ) + installedAppsApi.endpoints.getUserEditor.initiate(undefined) ); - expect( mockIpcApi.getUserEditor ).toHaveBeenCalledTimes( 1 ); - expect( mockIpcApi.getInstalledAppsAndTerminals ).not.toHaveBeenCalled(); - expect( result.data ).toBe( 'windsurf' ); - } ); + expect(mockIpcApi.getUserEditor).toHaveBeenCalledTimes(1); + expect(mockIpcApi.getInstalledAppsAndTerminals).not.toHaveBeenCalled(); + expect(result.data).toBe('windsurf'); + }); - it( 'should respect priority order when multiple editors are installed', async () => { - mockIpcApi.getUserEditor.mockResolvedValueOnce( null ); - const mockInstalledApps = createMockInstalledApps( { + it('should respect priority order when multiple editors are installed', async () => { + mockIpcApi.getUserEditor.mockResolvedValueOnce(null); + const mockInstalledApps = createMockInstalledApps({ webstorm: true, phpstorm: true, windsurf: true, cursor: true, - } ); - mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce( mockInstalledApps ); + }); + mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce(mockInstalledApps); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getUserEditor.initiate( undefined ) + installedAppsApi.endpoints.getUserEditor.initiate(undefined) ); // Should return cursor since it has the highest priority - expect( result.data ).toBe( 'cursor' ); - } ); + expect(result.data).toBe('cursor'); + }); - it( 'should return the installed editor when no preference is set and only one editor is installed', async () => { - mockIpcApi.getUserEditor.mockResolvedValueOnce( null ); - const mockInstalledApps = createMockInstalledApps( { cursor: true } ); - mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce( mockInstalledApps ); + it('should return the installed editor when no preference is set and only one editor is installed', async () => { + mockIpcApi.getUserEditor.mockResolvedValueOnce(null); + const mockInstalledApps = createMockInstalledApps({ cursor: true }); + mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce(mockInstalledApps); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getUserEditor.initiate( undefined ) + installedAppsApi.endpoints.getUserEditor.initiate(undefined) ); - expect( result.data ).toBe( 'cursor' ); - } ); + expect(result.data).toBe('cursor'); + }); - it( 'should return phpstorm when cursor and vscode are not installed but phpstorm is', async () => { - mockIpcApi.getUserEditor.mockResolvedValueOnce( null ); - const mockInstalledApps = createMockInstalledApps( { phpstorm: true, webstorm: true } ); - mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce( mockInstalledApps ); + it('should return phpstorm when cursor and vscode are not installed but phpstorm is', async () => { + mockIpcApi.getUserEditor.mockResolvedValueOnce(null); + const mockInstalledApps = createMockInstalledApps({ phpstorm: true, webstorm: true }); + mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce(mockInstalledApps); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getUserEditor.initiate( undefined ) + installedAppsApi.endpoints.getUserEditor.initiate(undefined) ); - expect( result.data ).toBe( 'phpstorm' ); - } ); + expect(result.data).toBe('phpstorm'); + }); - it( 'should return null when no preference set and no editors are installed', async () => { - mockIpcApi.getUserEditor.mockResolvedValueOnce( null ); + it('should return null when no preference set and no editors are installed', async () => { + mockIpcApi.getUserEditor.mockResolvedValueOnce(null); const mockInstalledApps = createMockInstalledApps(); - mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce( mockInstalledApps ); + mockIpcApi.getInstalledAppsAndTerminals.mockResolvedValueOnce(mockInstalledApps); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getUserEditor.initiate( undefined ) + installedAppsApi.endpoints.getUserEditor.initiate(undefined) ); - expect( result.data ).toBe( null ); - } ); - } ); + expect(result.data).toBe(null); + }); + }); - describe( 'getUserTerminal', () => { - it( 'should fetch user terminal preference', async () => { - mockIpcApi.getUserTerminal.mockResolvedValueOnce( 'iterm' ); + describe('getUserTerminal', () => { + it('should fetch user terminal preference', async () => { + mockIpcApi.getUserTerminal.mockResolvedValueOnce('iterm'); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.getUserTerminal.initiate( undefined ) + installedAppsApi.endpoints.getUserTerminal.initiate(undefined) ); - expect( mockIpcApi.getUserTerminal ).toHaveBeenCalledTimes( 1 ); - expect( result.data ).toBe( 'iterm' ); - } ); - } ); + expect(mockIpcApi.getUserTerminal).toHaveBeenCalledTimes(1); + expect(result.data).toBe('iterm'); + }); + }); - describe( 'saveUserEditor', () => { - it( 'should save user editor preference', async () => { - mockIpcApi.saveUserEditor.mockResolvedValueOnce( undefined ); + describe('saveUserEditor', () => { + it('should save user editor preference', async () => { + mockIpcApi.saveUserEditor.mockResolvedValueOnce(undefined); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.saveUserEditor.initiate( 'cursor' ) + installedAppsApi.endpoints.saveUserEditor.initiate('cursor') ); - expect( mockIpcApi.saveUserEditor ).toHaveBeenCalledTimes( 1 ); - expect( mockIpcApi.saveUserEditor ).toHaveBeenCalledWith( 'cursor' ); - expect( result.data ).toBe( 'cursor' ); - } ); - } ); + expect(mockIpcApi.saveUserEditor).toHaveBeenCalledTimes(1); + expect(mockIpcApi.saveUserEditor).toHaveBeenCalledWith('cursor'); + expect(result.data).toBe('cursor'); + }); + }); - describe( 'saveUserTerminal', () => { - it( 'should save user terminal preference', async () => { - mockIpcApi.saveUserTerminal.mockResolvedValueOnce( undefined ); + describe('saveUserTerminal', () => { + it('should save user terminal preference', async () => { + mockIpcApi.saveUserTerminal.mockResolvedValueOnce(undefined); const store = createTestStore(); const result = await store.dispatch( - installedAppsApi.endpoints.saveUserTerminal.initiate( 'warp' ) + installedAppsApi.endpoints.saveUserTerminal.initiate('warp') ); - expect( mockIpcApi.saveUserTerminal ).toHaveBeenCalledTimes( 1 ); - expect( mockIpcApi.saveUserTerminal ).toHaveBeenCalledWith( 'warp' ); - expect( result.data ).toBe( 'warp' ); - } ); - } ); + expect(mockIpcApi.saveUserTerminal).toHaveBeenCalledTimes(1); + expect(mockIpcApi.saveUserTerminal).toHaveBeenCalledWith('warp'); + expect(result.data).toBe('warp'); + }); + }); - describe( 'selectors', () => { - describe( 'selectInstalledEditors', () => { - it( 'should return only installed editors', () => { - const mockInstalledApps = createMockInstalledApps( { + describe('selectors', () => { + describe('selectInstalledEditors', () => { + it('should return only installed editors', () => { + const mockInstalledApps = createMockInstalledApps({ vscode: true, cursor: true, windsurf: true, - } ); - const result = selectInstalledEditors( mockInstalledApps ); + }); + const result = selectInstalledEditors(mockInstalledApps); - expect( result ).toHaveLength( 3 ); - expect( result.map( ( [ editor ] ) => editor ) ).toEqual( - expect.arrayContaining( [ 'vscode', 'cursor', 'windsurf' ] ) + expect(result).toHaveLength(3); + expect(result.map(([editor]) => editor)).toEqual( + expect.arrayContaining(['antigravity', 'vscode', 'cursor', 'windsurf']) ); - } ); + }); - it( 'should return empty array when no editors are installed', () => { + it('should return empty array when no editors are installed', () => { const mockInstalledApps = createMockInstalledApps(); - const result = selectInstalledEditors( mockInstalledApps ); + const result = selectInstalledEditors(mockInstalledApps); - expect( result ).toHaveLength( 0 ); - } ); - } ); + expect(result).toHaveLength(0); + }); + }); - describe( 'selectUninstalledEditors', () => { - it( 'should return only uninstalled editors', () => { - const mockInstalledApps = createMockInstalledApps( { vscode: true, cursor: true } ); - const result = selectUninstalledEditors( mockInstalledApps ); + describe('selectUninstalledEditors', () => { + it('should return only uninstalled editors', () => { + const mockInstalledApps = createMockInstalledApps({ vscode: true, cursor: true }); + const result = selectUninstalledEditors(mockInstalledApps); - expect( result ).toHaveLength( 4 ); - expect( result.map( ( [ editor ] ) => editor ) ).toEqual( - expect.arrayContaining( [ 'phpstorm', 'windsurf', 'webstorm', 'sublime' ] ) + expect(result).toHaveLength(4); + expect(result.map(([editor]) => editor)).toEqual( + expect.arrayContaining(['phpstorm', 'windsurf', 'webstorm', 'sublime']) ); - } ); + }); - it( 'should return all editors when none are installed', () => { + it('should return all editors when none are installed', () => { const mockInstalledApps = createMockInstalledApps(); - const result = selectUninstalledEditors( mockInstalledApps ); + const result = selectUninstalledEditors(mockInstalledApps); - expect( result ).toHaveLength( 6 ); - } ); - } ); + expect(result).toHaveLength(6); + }); + }); - describe( 'selectInstalledTerminals', () => { - it( 'should return only installed terminals', () => { - const mockInstalledApps = createMockInstalledApps( { terminal: true, iterm: true } ); - const result = selectInstalledTerminals( mockInstalledApps ); + describe('selectInstalledTerminals', () => { + it('should return only installed terminals', () => { + const mockInstalledApps = createMockInstalledApps({ terminal: true, iterm: true }); + const result = selectInstalledTerminals(mockInstalledApps); - expect( result ).toHaveLength( 2 ); - expect( result.map( ( [ terminal ] ) => terminal ) ).toEqual( - expect.arrayContaining( [ 'terminal', 'iterm' ] ) + expect(result).toHaveLength(2); + expect(result.map(([terminal]) => terminal)).toEqual( + expect.arrayContaining(['terminal', 'iterm']) ); - } ); - } ); + }); + }); - describe( 'selectUninstalledTerminals', () => { - it( 'should return only uninstalled terminals', () => { - const mockInstalledApps = createMockInstalledApps( { terminal: true, iterm: true } ); - const result = selectUninstalledTerminals( mockInstalledApps ); + describe('selectUninstalledTerminals', () => { + it('should return only uninstalled terminals', () => { + const mockInstalledApps = createMockInstalledApps({ terminal: true, iterm: true }); + const result = selectUninstalledTerminals(mockInstalledApps); - expect( result ).toHaveLength( 2 ); - expect( result.map( ( [ terminal ] ) => terminal ) ).toEqual( - expect.arrayContaining( [ 'warp', 'ghostty' ] ) + expect(result).toHaveLength(2); + expect(result.map(([terminal]) => terminal)).toEqual( + expect.arrayContaining(['warp', 'ghostty']) ); - } ); - } ); - } ); -} ); + }); + }); + }); +});