├── .circleci └── config.yml ├── .eslintrc.js ├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml ├── images │ ├── linux.png │ ├── macos.png │ └── windows.png └── workflows │ ├── add-to-project.yml │ ├── check-download-links.yml │ ├── coveralls.yml │ └── semantic.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .markdownlint.json ├── .prettierrc.js ├── .stylelintrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── assets ├── icons │ ├── fiddle.icns │ ├── fiddle.ico │ ├── fiddle.png │ └── fiddle.svg ├── loading.gif └── screenshots │ └── fiddle-screenshot.png ├── forge.config.ts ├── jest.config.ts ├── package.json ├── rtl-spec ├── components │ ├── commands-address-bar.spec.tsx │ ├── commands-bisect.spec.tsx │ ├── commands-publish-button.spec.tsx │ ├── commands-version-chooser.spec.tsx │ ├── editor.spec.tsx │ ├── editors-non-ideal-state.spec.tsx │ ├── editors-toolbar-button.spec.tsx │ ├── tour-welcome.spec.tsx │ ├── tour.spec.tsx │ └── version-select.spec.tsx └── test-utils │ ├── renderClassComponentWithInstanceRef.ts │ └── versions.ts ├── src ├── ambient.d.ts ├── constants.ts ├── interfaces.ts ├── ipc-events.ts ├── less │ ├── blueprint.less │ ├── components │ │ ├── commands.less │ │ ├── dialogs.less │ │ ├── editors.less │ │ ├── mosaic.less │ │ ├── output.less │ │ ├── settings.less │ │ ├── show-me.less │ │ ├── sidebar.less │ │ ├── tour.less │ │ └── version-select.less │ ├── container.less │ ├── main.less │ ├── mosaic-vendor.less │ ├── root.less │ └── variables.less ├── main │ ├── about-panel.ts │ ├── command-line.ts │ ├── constants.ts │ ├── content.ts │ ├── context-menu.ts │ ├── devtools.ts │ ├── dialogs.ts │ ├── electron-types.ts │ ├── fiddle-core.ts │ ├── files.ts │ ├── first-run.ts │ ├── ipc.ts │ ├── main.ts │ ├── menu.ts │ ├── npm.ts │ ├── protocol.ts │ ├── sentry.ts │ ├── squirrel.ts │ ├── templates.ts │ ├── themes.ts │ ├── update.ts │ ├── utils │ │ ├── check-first-run.ts │ │ ├── devmode.ts │ │ ├── exec.ts │ │ ├── get-files.ts │ │ ├── get-project-name.ts │ │ ├── get-username.ts │ │ └── read-fiddle.ts │ ├── versions.ts │ └── windows.ts ├── preload │ └── preload.ts ├── renderer │ ├── app.tsx │ ├── bisect.ts │ ├── components │ │ ├── commands-action-button.tsx │ │ ├── commands-address-bar.tsx │ │ ├── commands-bisect.tsx │ │ ├── commands-runner.tsx │ │ ├── commands-version-chooser.tsx │ │ ├── commands.tsx │ │ ├── dialog-add-theme.tsx │ │ ├── dialog-add-version.tsx │ │ ├── dialog-bisect.tsx │ │ ├── dialog-generic.tsx │ │ ├── dialog-token.tsx │ │ ├── dialogs.tsx │ │ ├── editor.tsx │ │ ├── editors-non-ideal-state.tsx │ │ ├── editors-toolbar-button.tsx │ │ ├── editors.tsx │ │ ├── header.tsx │ │ ├── output-editors-wrapper.tsx │ │ ├── output.tsx │ │ ├── outputs.tsx │ │ ├── settings-credits.tsx │ │ ├── settings-electron.tsx │ │ ├── settings-execution.tsx │ │ ├── settings-general-appearance.tsx │ │ ├── settings-general-block-accelerators.tsx │ │ ├── settings-general-console.tsx │ │ ├── settings-general-font.tsx │ │ ├── settings-general-github.tsx │ │ ├── settings-general-mirror.tsx │ │ ├── settings-general-package-author.tsx │ │ ├── settings-general.tsx │ │ ├── settings.tsx │ │ ├── sidebar-file-tree.tsx │ │ ├── sidebar-package-manager.tsx │ │ ├── sidebar.tsx │ │ ├── tour-welcome.tsx │ │ ├── tour.tsx │ │ └── version-select.tsx │ ├── constants.ts │ ├── editor-mosaic.ts │ ├── electron-types.ts │ ├── file-manager.ts │ ├── main.tsx │ ├── mirror-constants.ts │ ├── npm-search.tsx │ ├── remote-loader.ts │ ├── runner.ts │ ├── sentry.ts │ ├── state.ts │ ├── task-runner.ts │ ├── themes.ts │ ├── transforms │ │ ├── dotfiles.ts │ │ └── forge.ts │ ├── utils │ │ ├── disable-download.ts │ │ ├── editor-utils.ts │ │ ├── electron-name.ts │ │ ├── get-package.ts │ │ ├── get-version-range.ts │ │ ├── highlight-text.tsx │ │ ├── js-path.ts │ │ ├── normalize-version.ts │ │ ├── octokit.ts │ │ ├── plural-maybe.ts │ │ ├── position-for-rect.ts │ │ ├── sort-versions.ts │ │ └── toggle-monaco.ts │ └── versions.ts ├── templates.ts ├── themes-defaults.ts └── utils │ ├── editor-utils.ts │ └── gist.ts ├── static ├── css │ └── .gitkeep ├── electron-quick-start │ ├── index.html │ ├── main.js │ ├── preload.js │ └── renderer.js ├── entitlements.plist ├── index.html └── show-me │ ├── app │ └── main.js │ ├── autoupdater │ └── main.js │ ├── browserview │ └── main.js │ ├── browserwindow │ ├── index.html │ └── main.js │ ├── clipboard │ ├── index.html │ ├── main.js │ ├── preload.js │ └── renderer.js │ ├── contenttracing │ └── main.js │ ├── cookies │ └── main.js │ ├── crashreporter │ ├── index.html │ ├── main.js │ └── preload.js │ ├── debugger │ ├── index.html │ └── main.js │ ├── desktopcapturer │ ├── index.html │ ├── main.js │ └── preload.js │ ├── dialog │ └── main.js │ ├── globalshortcut │ └── main.js │ ├── inapppurchase │ └── main.js │ ├── ipc │ ├── index.html │ ├── main.js │ └── preload.js │ ├── menu │ ├── index.html │ └── main.js │ ├── nativeimage │ └── main.js │ ├── net │ └── main.js │ ├── notification │ ├── index.html │ ├── main.js │ └── preload.js │ ├── powermonitor │ └── main.js │ ├── powersaveblocker │ └── main.js │ ├── screen │ ├── index.html │ ├── main.js │ └── preload.js │ ├── session │ └── main.js │ ├── shell │ ├── index.html │ ├── main.js │ └── preload.js │ ├── systempreferences │ └── main.js │ ├── touchbar │ ├── index.html │ └── main.js │ ├── tray │ └── main.js │ ├── utilityprocess │ ├── child.js │ └── main.js │ ├── webcontents │ ├── index.html │ └── main.js │ ├── webcontentsview │ └── main.js │ └── webframe │ ├── index.html │ ├── main.js │ └── preload.js ├── tests ├── fixtures │ ├── node-types.json │ └── templates │ │ ├── 11-x-y.zip │ │ └── master.zip ├── globalSetup.ts ├── install-state-spec.ts ├── main │ ├── command-line-spec.ts │ ├── content-spec.ts │ ├── context-menu-spec.ts │ ├── devtools-spec.ts │ ├── dialogs-spec.ts │ ├── electron-types-spec.ts │ ├── fiddle-core-spec.ts │ ├── files-spec.ts │ ├── first-run-spec.ts │ ├── ipc-spec.ts │ ├── main-spec.ts │ ├── menu-spec.ts │ ├── npm-spec.ts │ ├── protocol-spec.ts │ ├── squirrel-spec.ts │ ├── templates-spec.ts │ ├── themes-spec.ts │ ├── update-spec.ts │ ├── utils │ │ ├── check-first-run-spec.ts │ │ ├── devmode-spec.ts │ │ ├── exec-spec.ts │ │ ├── get-project-name-spec.ts │ │ ├── get-username-spec.ts │ │ └── read-fiddle-spec.ts │ ├── versions-spec.ts │ └── windows-spec.ts ├── mocks │ ├── app.ts │ ├── bisector.ts │ ├── browser-window.ts │ ├── child-process.ts │ ├── editor-values.ts │ ├── electron-fiddle.ts │ ├── electron-types.ts │ ├── electron-versions.ts │ ├── electron.ts │ ├── fiddle-core.ts │ ├── file-manager.ts │ ├── mocks.ts │ ├── monaco.ts │ ├── npm-response-main.json │ ├── npm-response-nightlies.json │ ├── remote-loader.ts │ ├── runner.ts │ ├── state.ts │ ├── styles.ts │ ├── versions-mock.json │ └── web-contents.ts ├── preload │ └── preload-spec.ts ├── renderer │ ├── app-spec.tsx │ ├── bisect-spec.ts │ ├── components │ │ ├── __snapshots__ │ │ │ ├── commands-runner-spec.tsx.snap │ │ │ ├── commands-spec.tsx.snap │ │ │ ├── dialog-add-theme-spec.tsx.snap │ │ │ ├── dialog-add-version-spec.tsx.snap │ │ │ ├── dialog-bisect-spec.tsx.snap │ │ │ ├── dialog-generic-spec.tsx.snap │ │ │ ├── dialog-token-spec.tsx.snap │ │ │ ├── editors-spec.tsx.snap │ │ │ ├── header-spec.tsx.snap │ │ │ ├── settings-credits-spec.tsx.snap │ │ │ ├── settings-electron-spec.tsx.snap │ │ │ ├── settings-execution-spec.tsx.snap │ │ │ ├── settings-general-appearance-spec.tsx.snap │ │ │ ├── settings-general-block-accelerators-spec.tsx.snap │ │ │ ├── settings-general-console-spec.tsx.snap │ │ │ ├── settings-general-font-spec.tsx.snap │ │ │ ├── settings-general-github-spec.tsx.snap │ │ │ ├── settings-general-mirror-spec.tsx.snap │ │ │ ├── settings-general-package-author-spec.tsx.snap │ │ │ ├── settings-general-spec.tsx.snap │ │ │ ├── settings-spec.tsx.snap │ │ │ ├── sidebar-file-tree-spec.tsx.snap │ │ │ └── sidebar-package-manager-spec.tsx.snap │ │ ├── commands-runner-spec.tsx │ │ ├── commands-spec.tsx │ │ ├── dialog-add-theme-spec.tsx │ │ ├── dialog-add-version-spec.tsx │ │ ├── dialog-bisect-spec.tsx │ │ ├── dialog-generic-spec.tsx │ │ ├── dialog-token-spec.tsx │ │ ├── dialogs-spec.tsx │ │ ├── editors-spec.tsx │ │ ├── header-spec.tsx │ │ ├── output-spec.tsx │ │ ├── settings-credits-spec.tsx │ │ ├── settings-electron-spec.tsx │ │ ├── settings-execution-spec.tsx │ │ ├── settings-general-appearance-spec.tsx │ │ ├── settings-general-block-accelerators-spec.tsx │ │ ├── settings-general-console-spec.tsx │ │ ├── settings-general-font-spec.tsx │ │ ├── settings-general-github-spec.tsx │ │ ├── settings-general-mirror-spec.tsx │ │ ├── settings-general-package-author-spec.tsx │ │ ├── settings-general-spec.tsx │ │ ├── settings-spec.tsx │ │ ├── sidebar-file-tree-spec.tsx │ │ └── sidebar-package-manager-spec.tsx │ ├── editor-mosaic-spec.ts │ ├── electron-types-spec.ts │ ├── file-manager-spec.ts │ ├── npm-search-spec.ts │ ├── remote-loader-spec.ts │ ├── runner-spec.tsx │ ├── state-spec.ts │ ├── task-runner-spec.tsx │ ├── themes-spec.tsx │ ├── utils │ │ ├── disable-download-spec.ts │ │ ├── editor-utils-spec.ts │ │ ├── electron-name-spec.ts │ │ ├── get-package-spec.ts │ │ ├── get-version-range-spec.ts │ │ ├── highlight-text-spec.ts │ │ ├── js-path-spec.ts │ │ ├── normalize-version-spec.ts │ │ ├── plural-maybe-spec.ts │ │ ├── position-for-rect-spec.ts │ │ ├── sort-versions-spec.ts │ │ └── toggle-monaco-spec.ts │ └── versions-spec.ts ├── setup.ts ├── themes-defaults-spec.ts ├── transforms │ ├── dotfiles-spec.ts │ └── forge-spec.ts ├── utils.ts └── utils │ ├── editor-utils-spec.ts │ └── gist-spec.ts ├── tools ├── add-macos-cert.sh ├── certs │ ├── apple.cer │ ├── dac.cer │ └── requirements.txt ├── clean-webpack.ts ├── contributors.ts ├── fetch-releases.ts └── webpack │ ├── common │ ├── webpack.plugins.ts │ └── webpack.rules.ts │ ├── webpack.main.config.ts │ └── webpack.renderer.config.ts ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | # Source code should always use LF as line ending 4 | *.d.ts text eol=lf 5 | *.js text eol=lf 6 | *.ts text eol=lf 7 | *.tsx text eol=lf 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @electron/wg-ecosystem @codebytere 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/images/linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/.github/images/linux.png -------------------------------------------------------------------------------- /.github/images/macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/.github/images/macos.png -------------------------------------------------------------------------------- /.github/images/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/.github/images/windows.png -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Add to Ecosystem WG Project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request_target: 8 | types: 9 | - opened 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | add-to-project: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Generate GitHub App token 18 | uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1 19 | id: generate-token 20 | with: 21 | creds: ${{ secrets.ECOSYSTEM_ISSUE_TRIAGE_GH_APP_CREDS }} 22 | org: electron 23 | - name: Add to Project 24 | uses: dsanders11/project-actions/add-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0 25 | with: 26 | field: Opened 27 | field-value: ${{ github.event.pull_request.created_at || github.event.issue.created_at }} 28 | project-number: 89 29 | token: ${{ steps.generate-token.outputs.token }} 30 | -------------------------------------------------------------------------------- /.github/workflows/check-download-links.yml: -------------------------------------------------------------------------------- 1 | name: Check Download Links 2 | 'on': 3 | workflow_dispatch: null 4 | schedule: 5 | - cron: 0 1 * * * 6 | permissions: {} 7 | jobs: 8 | check-download-links: 9 | name: Check Download Links 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Install dependencies 13 | run: npm install hast-util-from-html@^2.0.1 hast-util-select@^6.0.2 14 | - name: Check website download links 15 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea 16 | with: 17 | script: | 18 | const { fromHtml } = await import('${{ github.workspace }}/node_modules/hast-util-from-html/index.js'); 19 | const { selectAll } = await import('${{ github.workspace }}/node_modules/hast-util-select/index.js'); 20 | const tree = fromHtml(await fetch('https://www.electronjs.org/fiddle').then(resp => resp.text())); 21 | const links = selectAll('#downloads a[href^="https://github.com/electron/fiddle/releases/download/"]', tree); 22 | const statusCodes = new Map(); 23 | for (const { properties: { href } } of links) { 24 | const resp = await fetch(href, { method: 'HEAD' }); 25 | statusCodes.set(href, resp.status); 26 | } 27 | if (Array.from(statusCodes.values()).some(code => code === 404)) { 28 | process.exitCode = 1; 29 | core.summary.addHeading('🚨 Broken Download Links'); 30 | core.summary.addTable([ 31 | [ 32 | { data: 'Artifact', header: true }, 33 | { data: 'Status', header: true }, 34 | ], 35 | ...Array.from(statusCodes.entries()) 36 | .map(([href, code]) => [ 37 | `${href.split('/').pop()}`, 38 | `

${code === 404 ? '❌' : '✅'}

`, 39 | ]), 40 | ]); 41 | } else { 42 | core.summary.addRaw('🎉 No broken links'); 43 | } 44 | await core.summary.write(); -------------------------------------------------------------------------------- /.github/workflows/coveralls.yml: -------------------------------------------------------------------------------- 1 | name: Coveralls 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | test: 13 | name: Check Coverage 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - name: Setup Node.js 18 | uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 19 | with: 20 | node-version: 20.x 21 | cache: yarn 22 | - name: Install 23 | run: yarn --frozen-lockfile --network-timeout 100000 || yarn --frozen-lockfile --network-timeout 100000 || yarn --frozen-lockfile --network-timeout 100000 24 | - run: yarn run contributors 25 | - run: yarn run electron-releases 26 | - name: test 27 | run: yarn test:ci 28 | - name: Coveralls 29 | uses: coverallsapp/github-action@648a8eb78e6d50909eff900e4ec85cab4524a45b # v2.3.6 30 | with: 31 | github-token: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/semantic.yml: -------------------------------------------------------------------------------- 1 | name: "Check Semantic Commit" 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | permissions: 16 | pull-requests: read # for amannn/action-semantic-pull-request to analyze PRs 17 | statuses: write # for amannn/action-semantic-pull-request to mark status of analyzed PR 18 | name: Validate PR Title 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: semantic-pull-request 22 | uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | validateSingleCommit: false 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .gclient_done 4 | **/.npmrc 5 | .tags* 6 | .vs/ 7 | .vscode/ 8 | *.log 9 | *.pyc 10 | *.sln 11 | *.swp 12 | *.VC.db 13 | *.VC.VC.opendb 14 | *.vcxproj 15 | *.vcxproj.filters 16 | *.vcxproj.user 17 | *.xcodeproj 18 | /.idea/ 19 | /external_binaries/ 20 | /out/ 21 | node_modules/ 22 | SHASUMS256.txt 23 | compile_commands.json 24 | .envrc 25 | .cache 26 | yarn-error.log 27 | *.p12 28 | 29 | # Coverage 30 | coverage 31 | 32 | # Test report 33 | report.json 34 | 35 | # npm package 36 | /npm/dist 37 | /npm/path.txt 38 | 39 | # Electron Fiddle-Specific 40 | /static/contributors.json 41 | /static/css/root.css 42 | /static/releases.json 43 | 44 | # Webpack configurations 45 | .webpack 46 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | yarn run lint-staged 2 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron/lint-roller/configs/markdownlint.json" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: "all", 3 | singleQuote: true, 4 | }; 5 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "customSyntax": "postcss-less", 4 | "ignoreFiles": [ 5 | "./src/less/*-vendor.less" 6 | ], 7 | "rules": { 8 | "alpha-value-notation": null, 9 | "color-function-notation": null, 10 | "import-notation": "string" 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2020 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | The Electron team and community take security bugs in Electron seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, email [security@electronjs.org](mailto:security@electronjs.org) and include the word "SECURITY" in the subject line. 6 | 7 | The Electron team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 8 | 9 | Report security bugs in third-party modules to the person or team maintaining the module. 10 | 11 | ## Learning More About Security 12 | 13 | To learn more about securing an Electron application, please see the [security tutorial](https://github.com/electron/electron/blob/main/docs/tutorial/security.md). 14 | -------------------------------------------------------------------------------- /assets/icons/fiddle.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/assets/icons/fiddle.icns -------------------------------------------------------------------------------- /assets/icons/fiddle.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/assets/icons/fiddle.ico -------------------------------------------------------------------------------- /assets/icons/fiddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/assets/icons/fiddle.png -------------------------------------------------------------------------------- /assets/icons/fiddle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/assets/loading.gif -------------------------------------------------------------------------------- /assets/screenshots/fiddle-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/assets/screenshots/fiddle-screenshot.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'jest'; 2 | 3 | const config: Config = { 4 | transform: { 5 | '\\.(ts|tsx)$': 'ts-jest', 6 | }, 7 | testEnvironment: 'jsdom', 8 | testEnvironmentOptions: { 9 | url: 'http://localhost', 10 | }, 11 | testMatch: ['**/rtl-spec/**/*.spec.*', '**/tests/**/*-spec.{ts,tsx}'], 12 | resetMocks: true, 13 | bail: true, 14 | resetModules: true, 15 | snapshotSerializers: ['enzyme-to-json/serializer'], 16 | globalSetup: '/tests/globalSetup.ts', 17 | setupFilesAfterEnv: ['/tests/setup.ts'], 18 | moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'], 19 | testPathIgnorePatterns: ['/.tmp/'], 20 | coverageReporters: ['json', 'html', 'lcov'], 21 | collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/*.d.ts', '!**/*constants.ts'], 22 | coveragePathIgnorePatterns: ['/node_modules/', '/tests/.*\\.(ts|js)$'], 23 | moduleNameMapper: { 24 | '.*releases.json$': '/static/releases.json', 25 | '\\.(css|less|scss)$': '/tests/mocks/styles.ts', 26 | }, 27 | }; 28 | 29 | export default config; 30 | -------------------------------------------------------------------------------- /rtl-spec/components/commands-version-chooser.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { render, screen, waitFor } from '@testing-library/react'; 4 | import { userEvent } from '@testing-library/user-event'; 5 | 6 | import { VersionChooser } from '../../src/renderer/components/commands-version-chooser'; 7 | import { AppState } from '../../src/renderer/state'; 8 | import { mockVersion1, prepareAppState } from '../test-utils/versions'; 9 | 10 | describe('VersionSelect component', () => { 11 | let appState: AppState; 12 | 13 | beforeEach(() => { 14 | appState = prepareAppState(); 15 | 16 | // the version selector is disabled when bisecting 17 | appState.Bisector = undefined; 18 | }); 19 | 20 | it('selects a new version', async () => { 21 | const { getByRole } = render(); 22 | 23 | const btnOpenVersionSelector = getByRole('button'); 24 | 25 | await userEvent.click(btnOpenVersionSelector); 26 | 27 | const versionButton = screen.getByText(mockVersion1.version); 28 | 29 | await userEvent.click(versionButton); 30 | 31 | waitFor(() => 32 | expect(btnOpenVersionSelector.textContent).toContain( 33 | mockVersion1.version, 34 | ), 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /rtl-spec/components/editors-non-ideal-state.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from '@testing-library/react'; 4 | import { userEvent } from '@testing-library/user-event'; 5 | 6 | import { RenderNonIdealState } from '../../src/renderer/components/editors-non-ideal-state'; 7 | import { EditorMosaic } from '../../src/renderer/editor-mosaic'; 8 | 9 | describe('RenderNonIdealState component', () => { 10 | let editorMosaic: EditorMosaic; 11 | 12 | beforeEach(() => { 13 | ({ editorMosaic } = window.app.state); 14 | }); 15 | 16 | it('renders a non-ideal state', () => { 17 | const { getByText } = render( 18 | , 19 | ); 20 | 21 | expect(getByText('Reset editors')).toBeInTheDocument(); 22 | }); 23 | 24 | it('handles a click', async () => { 25 | const resetLayoutSpy = jest.spyOn(editorMosaic, 'resetLayout'); 26 | const { getByRole } = render( 27 | , 28 | ); 29 | await userEvent.click(getByRole('button')); 30 | 31 | expect(resetLayoutSpy).toHaveBeenCalledTimes(1); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /rtl-spec/test-utils/renderClassComponentWithInstanceRef.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentClass, createElement, createRef } from 'react'; 2 | 3 | import { render } from '@testing-library/react'; 4 | 5 | type ComponentConstructor

= new ( 6 | props: P, 7 | ) => Component; 8 | 9 | /** 10 | * Renders a class component and returns the render result alongside the 11 | * component's instance. 12 | */ 13 | export function renderClassComponentWithInstanceRef< 14 | C extends ComponentConstructor = ComponentConstructor, 15 | P extends C extends ComponentClass 16 | ? Props 17 | : never = C extends ComponentClass ? Props : never, 18 | I = InstanceType, 19 | >( 20 | ClassComponent: C, 21 | props: P, 22 | ): { 23 | instance: I; 24 | renderResult: ReturnType; 25 | } { 26 | // Hack: unlike Enzyme, RTL doesn't expose class components' instances, so we 27 | // need to improvise and pass a `ref` to get access to this instance. 28 | const ref = createRef(); 29 | 30 | const renderResult = render( 31 | createElement(ClassComponent, { 32 | ...props, 33 | ref, 34 | }), 35 | ); 36 | 37 | return { 38 | instance: ref.current!, 39 | renderResult, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /rtl-spec/test-utils/versions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ElectronReleaseChannel, 3 | InstallState, 4 | VersionSource, 5 | } from '../../src/interfaces'; 6 | import { VersionsMock } from '../../tests/mocks/electron-versions'; 7 | import { StateMock } from '../../tests/mocks/state'; 8 | 9 | const { missing } = InstallState; 10 | const { remote } = VersionSource; 11 | 12 | export const mockVersion1 = { 13 | source: remote, 14 | state: missing, 15 | version: '26.0.0', 16 | }; 17 | 18 | export const mockVersion2 = { 19 | source: remote, 20 | state: missing, 21 | version: '28.0.0-unsupported', 22 | }; 23 | 24 | /** 25 | * Initializes the app state with our mock versions. 26 | */ 27 | export function prepareAppState() { 28 | const { state: appState } = window.app; 29 | 30 | const { mockVersions } = new VersionsMock(); 31 | 32 | (appState as unknown as StateMock).initVersions('2.0.2', { 33 | ...mockVersions, 34 | [mockVersion1.version]: { ...mockVersion1 }, 35 | [mockVersion2.version]: { ...mockVersion2 }, 36 | }); 37 | 38 | appState.channelsToShow = [ 39 | ElectronReleaseChannel.stable, 40 | ElectronReleaseChannel.beta, 41 | ]; 42 | 43 | return appState; 44 | } 45 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SENTRY_DSN = 2 | 'https://966a5b01ac8d4941b81e4ebd0ab4c991@sentry.io/1882540'; 3 | 4 | export const ELECTRON_DTS = 'electron.d.ts'; 5 | -------------------------------------------------------------------------------- /src/less/blueprint.less: -------------------------------------------------------------------------------- 1 | @import (inline) "~@blueprintjs/core/lib/css/blueprint.css"; 2 | @import (inline) "~@blueprintjs/select/lib/css/blueprint-select.css"; 3 | @import (inline) "~@blueprintjs/icons/lib/css/blueprint-icons.css"; 4 | @import (inline) "~@blueprintjs/popover2/lib/css/blueprint-popover2.css"; 5 | @import "~@blueprintjs/core/lib/less/variables.less"; 6 | 7 | // Override some of the colors 8 | .fiddle.bp3-dark { 9 | .bp3-control input:focus ~ .bp3-control-indicator { 10 | outline: @blue3 solid 2px; 11 | } 12 | 13 | .bp3-menu, 14 | .bp3-popover .bp3-popover-content, .bp3-popover2 .bp3-popover2-content { 15 | background-color: @background-1; 16 | } 17 | 18 | .bp3-popover .bp3-popover-arrow-fill, .bp3-popover2 .bp3-popover2-arrow-fill { 19 | fill: @background-1; 20 | } 21 | 22 | .bp3-button:not([class*="bp3-intent-"]) { 23 | background-color: @background-1; 24 | } 25 | 26 | .bp3-button.bp3-minimal { 27 | background-color: unset; 28 | } 29 | 30 | .bp3-button:hover, 31 | .bp3-button.bp3-minimal:hover { 32 | background-color: rgba(138, 155, 168, 0.15); 33 | } 34 | 35 | .bp3-button:focus{ 36 | outline: @blue3 solid 2px; 37 | } 38 | 39 | .bp3-menu-item.bp3-active.bp3-intent-primary { 40 | background-color: @foreground-3; 41 | } 42 | 43 | .bp3-dialog { 44 | background-color: @background-4; 45 | 46 | .bp3-dialog-header { 47 | background-color: @background-3; 48 | } 49 | } 50 | 51 | .bp3-running-text { 52 | font-size: 14px; 53 | } 54 | 55 | .bp3-alert-contents { 56 | width: 100%; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/less/components/editors.less: -------------------------------------------------------------------------------- 1 | .mosaic-tile { 2 | animation: fadein 0.15s; 3 | } 4 | 5 | .mosaic-window-toolbar { 6 | & > div { 7 | display: flex; 8 | justify-content: space-between; 9 | width: 100%; 10 | padding: 0 5px 0 5px; 11 | 12 | .mosaic-controls { 13 | transform: scale(0.75); 14 | transform-origin: right; 15 | } 16 | 17 | h5 { 18 | margin: 4px 0 0 0; 19 | } 20 | 21 | button { 22 | margin-left: 8px; 23 | } 24 | } 25 | } 26 | 27 | // TODO: support new file 28 | // update if list of Editor ID changes 29 | @editor-ids: main\.js, renderer\.js, index\.html, preload\.js, styles\.css; 30 | 31 | // takes each editor ID and increase its z-index if the parent Mosaic root 32 | // has focused__id class for that specific id. 33 | each(@editor-ids, { 34 | .focused__@{value}.mosaic .mosaic-window.@{value} { 35 | z-index: 2; 36 | } 37 | }); 38 | 39 | .mosaic-window { 40 | z-index: 1; 41 | } 42 | 43 | @keyframes fadein { 44 | from { opacity: 0; } 45 | to { opacity: 1; } 46 | } 47 | -------------------------------------------------------------------------------- /src/less/components/mosaic.less: -------------------------------------------------------------------------------- 1 | @import "../variables.less"; 2 | 3 | .fiddle { 4 | .mosaic-window-toolbar { 5 | background: var(--background-1); 6 | box-shadow: inset 0 0 100px 100px rgba(53, 53, 53, 0.1); 7 | border-top: 1px solid rgba(255, 255, 255, 0.1); 8 | } 9 | 10 | .mosaic-window-toolbar.draggable:hover { 11 | box-shadow: inset 0 0 100px 100px rgba(255, 255, 255, 0.1); 12 | } 13 | 14 | .mosaic-window-title { 15 | color: var(--text-color-3); 16 | } 17 | 18 | button.mosaic-default-control { 19 | padding: 0; 20 | height: 20px; 21 | width: 20px; 22 | margin: 5px; 23 | background-color: #314246; 24 | border: 0.1rem solid #222b35; 25 | } 26 | 27 | button.mosaic-default-control:hover { 28 | background-color: #131d1f; 29 | } 30 | 31 | .mosaic-window-body { 32 | display: flex; 33 | flex-direction: row; 34 | flex-wrap: nowrap; 35 | align-items: stretch; 36 | align-content: stretch; 37 | overflow: visible; 38 | z-index: 4; 39 | background-color: @background-4; 40 | } 41 | 42 | .mosaic.mosaic-blueprint-theme { 43 | background: #bdbdbd15; 44 | } 45 | 46 | .editorContainer, 47 | .monaco-editor { 48 | width: 100%; 49 | height: 100%; 50 | } 51 | 52 | .editorContainer { 53 | background: @background-1; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/less/components/output.less: -------------------------------------------------------------------------------- 1 | @import '../variables.less'; 2 | 3 | .output { 4 | font-size: 12px; 5 | font-family: @fonts-common; 6 | font-weight: 600; 7 | top: 10vh; 8 | padding: 0; 9 | height: 100%; 10 | -webkit-app-region: no-drag; 11 | 12 | .monaco-editor { 13 | color: var(--text-color-1); 14 | box-shadow: inset 0 0 100px 100px rgba(0, 0, 0, 0.15); 15 | border-top: @border; 16 | 17 | .margin, 18 | .monaco-editor-background { 19 | background: var(--background-2); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/less/components/show-me.less: -------------------------------------------------------------------------------- 1 | .show-me-panel { 2 | padding: 10px; 3 | overflow: scroll; 4 | 5 | & > *:first-child { 6 | margin-top: 0; 7 | }; 8 | 9 | .bp3-callout { 10 | margin-bottom: 10px; 11 | } 12 | } 13 | 14 | .show-me-list { 15 | display: flex; 16 | flex-direction: column; 17 | flex-wrap: wrap; 18 | list-style: none; 19 | padding: 0; 20 | max-height: 400px; 21 | } 22 | -------------------------------------------------------------------------------- /src/less/components/sidebar.less: -------------------------------------------------------------------------------- 1 | #new-file-input { 2 | box-shadow: 0 0 0 1600px rgba(0, 0, 0, 0.1); 3 | border-radius: 0; 4 | border-left: 4px solid white; 5 | font-size: 14px; 6 | } 7 | 8 | .add-file-input .bp3-tree-node-label { 9 | overflow: visible; 10 | } 11 | 12 | .bp3-tree-node-caret-none { 13 | min-width: 8px; 14 | } 15 | 16 | .bp3-tree-node-content-1 { 17 | padding-left: 0; 18 | } 19 | 20 | .pointer { 21 | cursor: pointer; 22 | } 23 | 24 | .package-manager-result { 25 | em { 26 | font-weight: bold; 27 | } 28 | } 29 | 30 | .package-tree { 31 | .bp3-tree-node-content { 32 | .bp3-tree-node-secondary-label { 33 | min-width: 100px; 34 | } 35 | } 36 | 37 | .bp3-tree-node-list { 38 | margin: 5px 0; 39 | } 40 | } 41 | 42 | .package-tree-version-select { 43 | font-size: 10px; 44 | width: 60px; 45 | text-overflow: 'ellipsis'; 46 | background: @background-1; 47 | color: @dark-gray1; 48 | 49 | .bp3-dark & { 50 | color: @white; 51 | } 52 | } 53 | 54 | .fiddle-scrollbar { 55 | overflow: auto; 56 | } 57 | 58 | .fiddle-scrollbar::-webkit-scrollbar { 59 | width: 8px; 60 | } 61 | 62 | .fiddle-scrollbar::-webkit-scrollbar-thumb { 63 | border-radius: 4px; 64 | background-color: rgba(172, 172, 172, 0.6); 65 | 66 | .bp3-dark & { 67 | background-color: rgba(172, 172, 172, 0.4); 68 | } 69 | } 70 | 71 | .fiddle-scrollbar::-webkit-scrollbar-track { 72 | background-color: transparent; 73 | } 74 | -------------------------------------------------------------------------------- /src/less/components/tour.less: -------------------------------------------------------------------------------- 1 | .tour { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | height: 100vh; 6 | width: 100vw; 7 | z-index: 200; 8 | } 9 | 10 | .tour-portal { 11 | z-index: 1000; 12 | } -------------------------------------------------------------------------------- /src/less/components/version-select.less: -------------------------------------------------------------------------------- 1 | .bp3-fill { 2 | #version-chooser { 3 | width: 100%; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/less/container.less: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | width: 100vw; 6 | margin: 0; 7 | padding: 0; 8 | max-width: unset; 9 | } 10 | -------------------------------------------------------------------------------- /src/less/main.less: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | } 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | background: @background-1; 9 | overflow: hidden; 10 | display: flex; 11 | flex-direction: column; 12 | height: 100vh; 13 | } 14 | 15 | .fiddle { 16 | .drag { 17 | -webkit-app-region: drag; 18 | } 19 | 20 | .shadow { 21 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 22 | } 23 | 24 | .timestamp { 25 | margin-right: 5px; 26 | color: var(--text-color-1); 27 | opacity: 0.7; 28 | user-select: none; 29 | } 30 | } 31 | 32 | #editors { 33 | display: flex; 34 | flex-flow: row nowrap; 35 | align-items: stretch; 36 | align-content: stretch; 37 | height: 100%; 38 | } 39 | 40 | .editor { 41 | flex-grow: 1; 42 | width: 30%; 43 | } 44 | 45 | .resize { 46 | width: 6px; 47 | background: rgba(0, 0, 0, 0.2); 48 | margin-left: 9px; 49 | margin-right: 0; 50 | cursor: col-resize; 51 | flex-grow: 0; 52 | flex-shrink: 0; 53 | } 54 | 55 | #runner { 56 | height: 0; 57 | } 58 | 59 | #runner:not(:empty) { 60 | height: 200px; 61 | } 62 | 63 | /* stylelint-disable-next-line selector-class-pattern */ 64 | .editorTitle span { 65 | width: 33%; 66 | display: inline-block; 67 | text-align: center; 68 | font-size: 12px; 69 | } 70 | 71 | .tabbing-hidden { 72 | visibility: hidden; 73 | } 74 | -------------------------------------------------------------------------------- /src/less/root.less: -------------------------------------------------------------------------------- 1 | // Blueprint 2 | @import "blueprint.less"; 3 | 4 | // Variables, also imported in each file 5 | @import "variables.less"; 6 | @import "main.less"; 7 | @import "container.less"; 8 | @import "mosaic-vendor.less"; 9 | 10 | // Components 11 | @import "components/commands.less"; 12 | @import "components/output.less"; 13 | @import "components/dialogs.less"; 14 | @import "components/mosaic.less"; 15 | @import "components/settings.less"; 16 | @import "components/editors.less"; 17 | @import "components/tour.less"; 18 | @import "components/show-me.less"; 19 | @import "components/version-select.less"; 20 | @import "components/sidebar.less"; 21 | -------------------------------------------------------------------------------- /src/less/variables.less: -------------------------------------------------------------------------------- 1 | // We'll take these values from the theme. 2 | 3 | // Colors 4 | @foreground-1: var(--foreground-1); 5 | @foreground-2: var(--foreground-2); 6 | @foreground-3: var(--foreground-3); 7 | @background-4: var(--background-4); 8 | @background-3: var(--background-3); 9 | @background-2: var(--background-2); 10 | @background-1: var(--background-1); 11 | @border-color-2: var(--border-color-2); 12 | @border-color-1: var(--border-color-1); 13 | @border: var(--border); 14 | @text-color-1: var(--text-color-1); 15 | @text-color-2: var(--text-color-2); 16 | @text-color-3: var(--text-color-3); 17 | 18 | // Dynamically created 19 | @button-text-color: var(--button-text-color); 20 | 21 | // Fonts 22 | @fonts-common: var(--fonts-common); 23 | 24 | // Lengths 25 | @header-height: 50px; 26 | -------------------------------------------------------------------------------- /src/main/about-panel.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import { app } from 'electron'; 4 | 5 | import { Contributor } from 'src/interfaces'; 6 | 7 | import contributorsJSON from '../../static/contributors.json'; 8 | 9 | /** 10 | * Sets Fiddle's About panel options on Linux and macOS 11 | */ 12 | export function setupAboutPanel(): void { 13 | const contributors: Array = []; 14 | contributorsJSON.forEach((userData: Contributor) => { 15 | if (userData.name !== null && userData.name !== undefined) { 16 | contributors.push(userData.name); 17 | } 18 | }); 19 | 20 | const iconPath = path.resolve(__dirname, '../assets/icons/fiddle.png'); 21 | 22 | app.setAboutPanelOptions({ 23 | applicationName: 'Electron Fiddle', 24 | applicationVersion: app.getVersion(), 25 | authors: contributors, 26 | copyright: '© Electron Authors', 27 | credits: 'https://github.com/electron/fiddle/graphs/contributors', 28 | iconPath, 29 | version: process.versions.electron, 30 | website: 'https://electronjs.org/fiddle', 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/constants.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import { app } from 'electron'; 4 | 5 | export const STATIC_DIR = path.resolve(__dirname, '../static'); 6 | 7 | export const ELECTRON_DOWNLOAD_PATH = path.join( 8 | app.getPath('userData'), 9 | 'electron-bin', 10 | ); 11 | export const ELECTRON_INSTALL_PATH = path.join( 12 | ELECTRON_DOWNLOAD_PATH, 13 | 'current', 14 | ); 15 | -------------------------------------------------------------------------------- /src/main/devtools.ts: -------------------------------------------------------------------------------- 1 | import { isDevMode } from './utils/devmode'; 2 | 3 | /** 4 | * Installs developer tools if we're in dev mode. 5 | */ 6 | export async function setupDevTools(): Promise { 7 | if (!isDevMode()) return; 8 | 9 | const { 10 | default: installExtension, 11 | REACT_DEVELOPER_TOOLS, 12 | } = require('electron-devtools-installer'); 13 | 14 | try { 15 | const react = await installExtension(REACT_DEVELOPER_TOOLS, { 16 | loadExtensionOptions: { allowFileAccess: true }, 17 | }); 18 | console.log(`installDevTools: Installed ${react}`); 19 | } catch (error) { 20 | console.warn(`installDevTools: Error occurred:`, error); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/first-run.ts: -------------------------------------------------------------------------------- 1 | import { app, dialog } from 'electron'; 2 | 3 | import { isFirstRun } from './utils/check-first-run'; 4 | import { isDevMode } from './utils/devmode'; 5 | 6 | /** 7 | * Is this the first run of Fiddle? If so, perform 8 | * tasks that we only want to do in this case. 9 | */ 10 | export async function onFirstRunMaybe() { 11 | if (isFirstRun()) { 12 | await promptMoveToApplicationsFolder(); 13 | } 14 | } 15 | 16 | /** 17 | * Ask the user if the app should be moved to the 18 | * applications folder. 19 | */ 20 | async function promptMoveToApplicationsFolder(): Promise { 21 | if (process.platform !== 'darwin') return; 22 | if (isDevMode() || app.isInApplicationsFolder()) return; 23 | 24 | const { response } = await dialog.showMessageBox({ 25 | type: 'question', 26 | buttons: ['Move to Applications Folder', 'Do Not Move'], 27 | defaultId: 0, 28 | message: 'Move to Applications Folder?', 29 | }); 30 | 31 | if (response === 0) { 32 | app.moveToApplicationsFolder(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/sentry.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/electron/main'; 2 | 3 | import { isDevMode } from './utils/devmode'; 4 | import { SENTRY_DSN } from '../constants'; 5 | 6 | export function initSentry() { 7 | if (!isDevMode()) { 8 | Sentry.init({ dsn: SENTRY_DSN }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/squirrel.ts: -------------------------------------------------------------------------------- 1 | export function shouldQuit() { 2 | return require('electron-squirrel-startup'); 3 | } 4 | -------------------------------------------------------------------------------- /src/main/templates.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import { IpcMainEvent } from 'electron'; 4 | 5 | import { STATIC_DIR } from './constants'; 6 | import { ipcMainManager } from './ipc'; 7 | import { readFiddle } from './utils/read-fiddle'; 8 | import { EditorValues } from '../interfaces'; 9 | import { IpcEvents } from '../ipc-events'; 10 | 11 | /** 12 | * Returns expected content for a given name. 13 | */ 14 | export function getTemplateValues(name: string): Promise { 15 | const templatePath = path.join(STATIC_DIR, 'show-me', name.toLowerCase()); 16 | 17 | return readFiddle(templatePath); 18 | } 19 | 20 | export function setupTemplates() { 21 | ipcMainManager.handle( 22 | IpcEvents.GET_TEMPLATE_VALUES, 23 | (_: IpcMainEvent, name: string) => getTemplateValues(name), 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/update.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets up the update service 3 | */ 4 | export function setupUpdates() { 5 | // We delay this work by 10s to ensure that the 6 | // app doesn't have to worry about updating during launch 7 | setTimeout(() => { 8 | const { updateElectronApp } = require('update-electron-app'); 9 | 10 | updateElectronApp({ 11 | repo: 'electron/fiddle', 12 | updateInterval: '1 hour', 13 | }); 14 | }, 10000); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/utils/check-first-run.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import { app } from 'electron'; 4 | import * as fs from 'fs-extra'; 5 | 6 | const getConfigPath = () => { 7 | const userDataPath = app.getPath('userData'); 8 | return path.join(userDataPath, 'FirstRun', 'electron-app-first-run'); 9 | }; 10 | 11 | /** 12 | * Whether or not the app is being run for 13 | * the first time 14 | */ 15 | export function isFirstRun(): boolean { 16 | const configPath = getConfigPath(); 17 | 18 | try { 19 | if (fs.existsSync(configPath)) { 20 | return false; 21 | } 22 | 23 | fs.outputFileSync(configPath, ''); 24 | } catch (error) { 25 | console.warn(`First run: Unable to write firstRun file`, error); 26 | } 27 | 28 | return true; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/utils/devmode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Are we currently running in development mode? 3 | */ 4 | export function isDevMode(): boolean { 5 | return !!process.defaultApp; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/utils/exec.ts: -------------------------------------------------------------------------------- 1 | import { exec as cp_exec } from 'node:child_process'; 2 | import { promisify } from 'node:util'; 3 | 4 | import shellEnv from 'shell-env'; 5 | 6 | /** 7 | * On macOS & Linux, we need to fix the $PATH environment variable 8 | * so that we can call `npm`. 9 | */ 10 | export const maybeFixPath = (() => { 11 | // Singleton: We don't want to do this more than once. 12 | let _shellPathCalled = false; 13 | 14 | return async (): Promise => { 15 | if (_shellPathCalled) { 16 | return; 17 | } 18 | 19 | if (process.platform !== 'win32') { 20 | const { PATH } = await shellEnv(); 21 | if (PATH) { 22 | process.env.PATH = PATH; 23 | } 24 | } 25 | 26 | _shellPathCalled = true; 27 | }; 28 | })(); 29 | 30 | /** 31 | * Execute a command in a directory. 32 | */ 33 | export async function exec(dir: string, cliArgs: string): Promise { 34 | await maybeFixPath(); 35 | 36 | const { stdout } = await promisify(cp_exec)(cliArgs, { 37 | cwd: dir, 38 | maxBuffer: 200 * 1024 * 100, // 100 times the default 39 | }); 40 | 41 | return stdout.trim(); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/utils/get-files.ts: -------------------------------------------------------------------------------- 1 | import { MessageChannelMain } from 'electron'; 2 | 3 | import { FileTransformOperation, Files } from '../../interfaces'; 4 | import { IpcEvents } from '../../ipc-events'; 5 | import { ipcMainManager } from '../ipc'; 6 | 7 | /** 8 | * Gets file content from the renderer 9 | */ 10 | export function getFiles( 11 | window: Electron.BrowserWindow, 12 | transforms: Array, 13 | ): Promise<{ localPath?: string; files: Files }> { 14 | return new Promise((resolve) => { 15 | const { port1, port2 } = new MessageChannelMain(); 16 | ipcMainManager.postMessage( 17 | IpcEvents.GET_FILES, 18 | { options: undefined, transforms }, 19 | [port1], 20 | window.webContents, 21 | ); 22 | port2.once('message', (event) => { 23 | resolve(event.data); 24 | port2.close(); 25 | }); 26 | port2.start(); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/utils/get-project-name.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import * as namor from 'namor'; 4 | 5 | /** 6 | * Returns a name for this project 7 | */ 8 | export function getProjectName(localPath?: string): string { 9 | if (localPath) { 10 | return path.basename(localPath); 11 | } 12 | 13 | return namor.generate({ words: 3, numbers: 0 }); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/utils/get-username.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'node:os'; 2 | 3 | /** 4 | * Returns the current username 5 | */ 6 | export const getUsername = (() => { 7 | let username = ''; 8 | 9 | return (): string => { 10 | if (!username) { 11 | username = os.userInfo().username; 12 | } 13 | 14 | return username; 15 | }; 16 | })(); 17 | -------------------------------------------------------------------------------- /src/main/utils/read-fiddle.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import * as fs from 'fs-extra'; 4 | 5 | import { EditorId, EditorValues, PACKAGE_NAME } from '../../interfaces'; 6 | import { ensureRequiredFiles, isSupportedFile } from '../../utils/editor-utils'; 7 | 8 | /** 9 | * Reads a Fiddle from a directory. 10 | * 11 | * @returns the loaded Fiddle 12 | */ 13 | export async function readFiddle( 14 | folder: string, 15 | includePackageJson = false, 16 | ): Promise { 17 | let got: EditorValues = {}; 18 | 19 | try { 20 | // TODO(dsanders11): Remove options once issue fixed: 21 | // https://github.com/isaacs/node-graceful-fs/issues/223 22 | const files = await fs.readdir(folder, { encoding: 'utf8' }); 23 | const names = files.filter((f) => { 24 | if (f === 'package-lock.json') { 25 | return false; 26 | } 27 | 28 | if (f === PACKAGE_NAME) { 29 | return includePackageJson; 30 | } 31 | 32 | return isSupportedFile(f); 33 | }); 34 | 35 | const values = await Promise.allSettled( 36 | names.map((name) => fs.readFile(path.join(folder, name), 'utf8')), 37 | ); 38 | 39 | for (let i = 0; i < names.length; ++i) { 40 | const name = names[i] as EditorId; 41 | const value = values[i]; 42 | 43 | if (value.status === 'fulfilled') { 44 | got[name] = value.value || ''; 45 | } else { 46 | console.warn(`Could not read file ${name}:`, value.reason); 47 | got[name] = ''; 48 | } 49 | } 50 | } catch (err) { 51 | console.warn(`Unable to read "${folder}": ${err.toString()}`); 52 | } 53 | 54 | got = ensureRequiredFiles(got); 55 | console.log(`Got Fiddle from "${folder}". Found:`, Object.keys(got).sort()); 56 | return got; 57 | } 58 | -------------------------------------------------------------------------------- /src/renderer/bisect.ts: -------------------------------------------------------------------------------- 1 | import { RunnableVersion } from '../interfaces'; 2 | 3 | export class Bisector { 4 | public revList: Array; 5 | public minRev: number; 6 | public maxRev: number; 7 | private pivot: number; 8 | 9 | constructor(revList: Array) { 10 | this.getCurrentVersion = this.getCurrentVersion.bind(this); 11 | this.continue = this.continue.bind(this); 12 | this.calculatePivot = this.calculatePivot.bind(this); 13 | 14 | this.revList = revList; 15 | this.minRev = 0; 16 | this.maxRev = revList.length - 1; 17 | this.calculatePivot(); 18 | } 19 | 20 | public getCurrentVersion() { 21 | return this.revList[this.pivot]; 22 | } 23 | 24 | public continue(isGoodVersion: boolean) { 25 | let isBisectOver = false; 26 | if (this.maxRev - this.minRev <= 1) { 27 | isBisectOver = true; 28 | } 29 | 30 | if (isGoodVersion) { 31 | const upPivot = Math.floor((this.maxRev - this.pivot) / 2) + this.pivot; 32 | this.minRev = this.pivot; 33 | if (upPivot !== this.maxRev && upPivot !== this.pivot) { 34 | this.pivot = upPivot; 35 | } else { 36 | isBisectOver = true; 37 | } 38 | } else { 39 | const downPivot = 40 | Math.floor((this.pivot - this.minRev) / 2) + this.minRev; 41 | this.maxRev = this.pivot; 42 | if (downPivot !== this.minRev && downPivot !== this.pivot) { 43 | this.pivot = downPivot; 44 | } else { 45 | isBisectOver = true; 46 | } 47 | } 48 | 49 | if (isBisectOver) { 50 | return [this.revList[this.minRev], this.revList[this.maxRev]]; 51 | } else { 52 | return this.revList[this.pivot]; 53 | } 54 | } 55 | 56 | private calculatePivot() { 57 | this.pivot = Math.floor((this.maxRev - this.minRev) / 2); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/renderer/components/commands-version-chooser.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { ButtonGroup } from '@blueprintjs/core'; 4 | import { observer } from 'mobx-react'; 5 | 6 | import { VersionSelect } from './version-select'; 7 | import { AppState } from '../state'; 8 | 9 | interface VersionChooserProps { 10 | appState: AppState; 11 | } 12 | 13 | /** 14 | * A dropdown allowing the selection of Electron versions. The actual 15 | * download is managed in the state. 16 | */ 17 | export const VersionChooser = observer((props: VersionChooserProps) => { 18 | const { 19 | Bisector, 20 | currentElectronVersion, 21 | isAutoBisecting, 22 | isRunning, 23 | isSettingsShowing, 24 | setVersion, 25 | } = props.appState; 26 | 27 | return ( 28 | 29 | setVersion(version)} 32 | currentVersion={currentElectronVersion} 33 | disabled={ 34 | !!Bisector || isAutoBisecting || isSettingsShowing || isRunning 35 | } 36 | /> 37 | 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /src/renderer/components/dialogs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { observer } from 'mobx-react'; 4 | 5 | import { AddThemeDialog } from './dialog-add-theme'; 6 | import { AddVersionDialog } from './dialog-add-version'; 7 | import { BisectDialog } from './dialog-bisect'; 8 | import { GenericDialog } from './dialog-generic'; 9 | import { TokenDialog } from './dialog-token'; 10 | import { Settings } from './settings'; 11 | import { AppState } from '../state'; 12 | 13 | interface DialogsProps { 14 | appState: AppState; 15 | } 16 | 17 | /** 18 | * Dialogs (like the GitHub PAT input). 19 | */ 20 | export const Dialogs = observer( 21 | class Dialogs extends React.Component { 22 | public render() { 23 | const { appState } = this.props; 24 | const { 25 | isTokenDialogShowing, 26 | isSettingsShowing, 27 | isAddVersionDialogShowing, 28 | isThemeDialogShowing, 29 | isBisectDialogShowing, 30 | isGenericDialogShowing, 31 | } = appState; 32 | const maybeToken = isTokenDialogShowing ? ( 33 | 34 | ) : null; 35 | const maybeSettings = isSettingsShowing ? ( 36 | 37 | ) : null; 38 | const maybeAddLocalVersion = isAddVersionDialogShowing ? ( 39 | 40 | ) : null; 41 | const maybeMonaco = isThemeDialogShowing ? ( 42 | 43 | ) : null; 44 | const maybeBisect = isBisectDialogShowing ? ( 45 | 46 | ) : null; 47 | const genericDialog = isGenericDialogShowing ? ( 48 | 49 | ) : null; 50 | 51 | return ( 52 |

53 | {maybeToken} 54 | {maybeSettings} 55 | {maybeAddLocalVersion} 56 | {maybeMonaco} 57 | {maybeBisect} 58 | {genericDialog} 59 |
60 | ); 61 | } 62 | }, 63 | ); 64 | -------------------------------------------------------------------------------- /src/renderer/components/editors-non-ideal-state.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Button, NonIdealState } from '@blueprintjs/core'; 4 | 5 | import { EditorMosaic } from '../editor-mosaic'; 6 | 7 | type RenderNonIdealStateProps = { 8 | editorMosaic: EditorMosaic; 9 | }; 10 | 11 | export function RenderNonIdealState({ 12 | editorMosaic, 13 | }: RenderNonIdealStateProps) { 14 | const resolveButton = ( 15 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /static/show-me/clipboard/main.js: -------------------------------------------------------------------------------- 1 | // Perform copy and paste operations on the system clipboard. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/clipboard 5 | 6 | const { app, BrowserWindow, ipcMain, clipboard } = require('electron/main') 7 | const path = require('node:path') 8 | 9 | ipcMain.handle('clipboard:readText', () => { 10 | return clipboard.readText() 11 | }) 12 | 13 | ipcMain.handle('clipboard:writeText', (event, text) => { 14 | clipboard.writeText(text) 15 | }) 16 | 17 | app.whenReady().then(() => { 18 | const mainWindow = new BrowserWindow({ 19 | height: 600, 20 | width: 600, 21 | webPreferences: { 22 | preload: path.join(__dirname, 'preload.js') 23 | } 24 | }) 25 | 26 | mainWindow.loadFile('index.html') 27 | }) 28 | -------------------------------------------------------------------------------- /static/show-me/clipboard/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron/renderer') 2 | 3 | contextBridge.exposeInMainWorld('clipboard', { 4 | readText: () => ipcRenderer.invoke('clipboard:readText'), 5 | writeText: (text) => ipcRenderer.invoke('clipboard:writeText', text) 6 | }) 7 | -------------------------------------------------------------------------------- /static/show-me/clipboard/renderer.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('DOMContentLoaded', () => { 2 | const copyButton = document.querySelector('#copy') 3 | const pasteButton = document.querySelector('#paste') 4 | const textarea = document.querySelector('textarea') 5 | copyButton.onclick = () => { 6 | window.clipboard.writeText('Hello from Electron!') 7 | } 8 | pasteButton.onclick = async () => { 9 | textarea.value = await window.clipboard.readText() 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /static/show-me/contenttracing/main.js: -------------------------------------------------------------------------------- 1 | // Collect tracing data from Chromium's content module for finding performance 2 | // bottlenecks and slow operations. 3 | // 4 | // This module does not include a web interface so you need to open 5 | // chrome://tracing/ in a Chrome browser and load the generated file to view 6 | // the result. 7 | // 8 | // For more info, see: 9 | // https://electronjs.org/docs/api/content-tracing 10 | 11 | const { app, contentTracing } = require('electron/main') 12 | 13 | app.whenReady().then(() => { 14 | const options = { 15 | categoryFilter: '*', 16 | traceOptions: 'record-until-full,enable-sampling' 17 | } 18 | 19 | contentTracing.startRecording(options).then(() => { 20 | console.log('Tracing started') 21 | }) 22 | 23 | setTimeout(() => { 24 | contentTracing.stopRecording().then((path) => { 25 | console.log('Tracing data recorded to ' + path) 26 | }) 27 | }, 5000) 28 | }) 29 | -------------------------------------------------------------------------------- /static/show-me/cookies/main.js: -------------------------------------------------------------------------------- 1 | // Query and modify a session's cookies. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/cookies 5 | 6 | const { app, BrowserWindow, session } = require('electron/main') 7 | 8 | app.whenReady().then(() => { 9 | const mainWindow = new BrowserWindow({ 10 | height: 600, 11 | width: 600 12 | }) 13 | 14 | // Once the window has finished loading, let's check out 15 | // the cookies 16 | mainWindow.webContents.on('did-finish-load', async () => { 17 | // Query all cookies. 18 | try { 19 | const cookies = await session.defaultSession.cookies.get({}) 20 | console.log(cookies) 21 | } catch (error) { 22 | console.error(error) 23 | } 24 | 25 | // Query all cookies associated with a specific url. 26 | try { 27 | const cookies = await session.defaultSession.cookies.get({ url: 'http://www.github.com' }) 28 | console.log(cookies) 29 | } catch (error) { 30 | console.error(error) 31 | } 32 | 33 | // Set a cookie with the given cookie data; 34 | // may overwrite equivalent cookies if they exist. 35 | try { 36 | const cookie = { url: 'http://www.github.com', name: 'dummy_name', value: 'dummy' } 37 | await session.defaultSession.cookies.set(cookie) 38 | } catch (error) { 39 | console.error(error) 40 | } 41 | }) 42 | 43 | mainWindow.loadURL('https://electronjs.org') 44 | }) 45 | -------------------------------------------------------------------------------- /static/show-me/crashreporter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CrashReporter 5 | 6 | 7 |

CrashReporter Demo

8 | 9 | -------------------------------------------------------------------------------- /static/show-me/crashreporter/main.js: -------------------------------------------------------------------------------- 1 | // Submit crash reports to a remote server. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/crash-reporter 5 | 6 | const { app, BrowserWindow, crashReporter } = require('electron/main') 7 | const path = require('node:path') 8 | 9 | crashReporter.start({ 10 | productName: 'YourName', 11 | globalExtra: { 12 | _companyName: 'YourCompany' 13 | }, 14 | submitURL: 'https://your-domain.com/url-to-submit', 15 | uploadToServer: true 16 | }) 17 | 18 | app.whenReady().then(() => { 19 | const mainWindow = new BrowserWindow({ 20 | height: 600, 21 | width: 600, 22 | webPreferences: { 23 | preload: path.join(__dirname, 'preload.js') 24 | } 25 | }) 26 | mainWindow.loadFile('index.html') 27 | 28 | mainWindow.webContents.on('render-process-gone', () => { 29 | console.log('Window crashed!') 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /static/show-me/crashreporter/preload.js: -------------------------------------------------------------------------------- 1 | setTimeout(() => { 2 | process.crash() 3 | }, 2000) 4 | -------------------------------------------------------------------------------- /static/show-me/debugger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /static/show-me/debugger/main.js: -------------------------------------------------------------------------------- 1 | // An alternate transport for Chrome's remote debugging protocol. 2 | // 3 | // Chrome Developer Tools has a special binding available at JavaScript 4 | // runtime that allows interacting with pages and instrumenting them. 5 | // 6 | // For more info, see: 7 | // https://electronjs.org/docs/api/debugger 8 | 9 | const { app, BrowserWindow } = require('electron/main') 10 | 11 | app.whenReady().then(() => { 12 | const mainWindow = new BrowserWindow({ height: 600, width: 600 }) 13 | 14 | mainWindow.loadFile('index.html') 15 | 16 | try { 17 | mainWindow.webContents.debugger.attach('1.1') 18 | } catch (err) { 19 | console.log('Debugger attach failed: ', err) 20 | } 21 | 22 | mainWindow.webContents.debugger.on('detach', (event, reason) => { 23 | console.log('Debugger detached due to: ', reason) 24 | }) 25 | 26 | mainWindow.webContents.debugger.on('message', (event, method, params) => { 27 | if (method === 'Network.requestWillBeSent') { 28 | if (params.request.url === 'https://www.github.com') { 29 | mainWindow.webContents.debugger.detach() 30 | } 31 | } 32 | }) 33 | 34 | mainWindow.webContents.debugger.sendCommand('Network.enable') 35 | }) 36 | -------------------------------------------------------------------------------- /static/show-me/desktopcapturer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /static/show-me/desktopcapturer/main.js: -------------------------------------------------------------------------------- 1 | // Access information about media sources that can be used to capture audio 2 | // and video from the desktop using the navigator.mediaDevices.getUserMedia API. 3 | // 4 | // For more info, see: 5 | // https://electronjs.org/docs/api/desktop-capturer 6 | 7 | const { app, BrowserWindow, desktopCapturer } = require('electron/main') 8 | const path = require('node:path') 9 | 10 | app.whenReady().then(() => { 11 | const mainWindow = new BrowserWindow({ 12 | height: 600, 13 | width: 600, 14 | webPreferences: { 15 | preload: path.join(__dirname, 'preload.js') 16 | } 17 | }) 18 | 19 | mainWindow.loadFile('index.html') 20 | if (parseInt(process.versions.electron) >= 17) { 21 | desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => { 22 | for (const source of sources) { 23 | if (source.id.startsWith('screen')) { 24 | mainWindow.webContents.send('SET_SOURCE', source.id) 25 | return 26 | } 27 | } 28 | }) 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /static/show-me/desktopcapturer/preload.js: -------------------------------------------------------------------------------- 1 | const { desktopCapturer, ipcRenderer } = require('electron/renderer') 2 | 3 | // The following example shows how to capture video from 4 | // the screen. It also grabs each window, so you could 5 | // just grab video from a single window. 6 | // 7 | 8 | function startCapture () { 9 | desktopCapturer.getSources({ 10 | types: ['window', 'screen'] 11 | }).then(async sources => { 12 | for (let i = 0; i < sources.length; ++i) { 13 | console.log(sources[i]) 14 | if (sources[i].id.startsWith('screen')) { 15 | showStream(sources[i].id) 16 | } 17 | } 18 | }) 19 | } 20 | 21 | async function showStream (sourceId) { 22 | try { 23 | const stream = await navigator.mediaDevices.getUserMedia({ 24 | audio: false, 25 | video: { 26 | mandatory: { 27 | chromeMediaSource: 'desktop', 28 | chromeMediaSourceId: sourceId, 29 | minWidth: 1280, 30 | maxWidth: 1280, 31 | minHeight: 720, 32 | maxHeight: 720 33 | } 34 | } 35 | }) 36 | handleStream(stream) 37 | } catch (e) { 38 | handleError(e) 39 | } 40 | } 41 | 42 | function handleStream (stream) { 43 | const video = document.querySelector('video') 44 | video.srcObject = stream 45 | video.onloadedmetadata = (e) => video.play() 46 | } 47 | 48 | function handleError (e) { 49 | console.log(e) 50 | } 51 | 52 | if (parseInt(process.versions.electron) >= 17) { 53 | ipcRenderer.on('SET_SOURCE', async (event, sourceId) => { 54 | showStream(sourceId) 55 | }) 56 | } else { 57 | window.addEventListener('DOMContentLoaded', () => { 58 | startCapture() 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /static/show-me/dialog/main.js: -------------------------------------------------------------------------------- 1 | // Display native system dialogs for opening and saving files, alerting, etc. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/dialog 5 | 6 | const { app, BrowserWindow, dialog } = require('electron/main') 7 | 8 | app.whenReady().then(async () => { 9 | const mainWindow = new BrowserWindow({ height: 600, width: 600 }) 10 | 11 | // Show an "Open File" dialog and attempt to open 12 | // the chosen file in our window. 13 | try { 14 | const { filePaths, canceled } = await dialog.showOpenDialog(mainWindow, { 15 | properties: ['openFile'] 16 | }) 17 | if (canceled) { 18 | console.log('Dialog was canceled') 19 | } else { 20 | const file = filePaths[0] 21 | mainWindow.loadFile(file) 22 | } 23 | } catch (err) { 24 | console.log(err) 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /static/show-me/globalshortcut/main.js: -------------------------------------------------------------------------------- 1 | // The globalShortcut module can register/unregister a global keyboard shortcut 2 | // with the operating system so that you can customize the operations for various 3 | // shortcuts. 4 | // 5 | // Note: The shortcut is global; it will work even if the app does not have the 6 | // keyboard focus. You should not use this module until the ready event of the 7 | // app module is emitted. In this example, we're using "Accelerators": 8 | // Accelerators are Strings that can contain multiple modifiers and key codes, 9 | // combined by the + character, and are used to define keyboard shortcuts 10 | // throughout your application. 11 | // 12 | // For more info, see: 13 | // https://electronjs.org/docs/api/accelerator 14 | // https://electronjs.org/docs/api/global-shortcut 15 | 16 | const { app, globalShortcut } = require('electron/main') 17 | 18 | app.whenReady().then(() => { 19 | // Register a 'CommandOrControl+Y' shortcut listener. 20 | // 21 | // On Linux and Windows, the Command key does not have any effect, so use 22 | // CommandOrControl which represents Command on macOS and Control on 23 | // Linux and Windows to define some accelerators. 24 | globalShortcut.register('CommandOrControl+Y', () => { 25 | console.log('The global shortkey was pressed!') 26 | }) 27 | 28 | // It supports "special names". Check out the API documentation for a full 29 | // list. 30 | globalShortcut.register('VolumeUp', () => console.log('Turn it up!')) 31 | globalShortcut.register('VolumeDown', () => console.log('Turn it down!')) 32 | }) 33 | 34 | app.on('will-quit', () => { 35 | // Unregister a shortcut. 36 | globalShortcut.unregister('CommandOrControl+Y') 37 | 38 | // Unregister all shortcuts. 39 | globalShortcut.unregisterAll() 40 | }) 41 | -------------------------------------------------------------------------------- /static/show-me/inapppurchase/main.js: -------------------------------------------------------------------------------- 1 | // The inAppPurchase module enables in-app purchases in the Mac App Store. 2 | // 3 | // This example will not work when run with Electron Fiddle, since the 4 | // needs to be packaged for and published in the Mac App Store. 5 | // 6 | // For more info, see: 7 | // https://electronjs.org/docs/api/in-app-purchase 8 | 9 | const { app, inAppPurchase } = require('electron/main') 10 | 11 | app.whenReady().then(() => { 12 | // Can the user can make a payment? 13 | if (inAppPurchase.canMakePayments()) { 14 | const productID = 'myProductId' 15 | const quantity = 1 16 | 17 | // Let's go shopping! 18 | inAppPurchase.purchaseProduct(productID, quantity) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /static/show-me/ipc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | IPC Example 5 | 6 | 7 |

IPC Example

8 |

Open the developer tools to see the IPC in action.

9 | 10 | 11 | -------------------------------------------------------------------------------- /static/show-me/ipc/main.js: -------------------------------------------------------------------------------- 1 | // The ipcMain and ipcRenderer modules allow communication between the main 2 | // process and the renderer processes. 3 | // 4 | // For more info, see: 5 | // https://electronjs.org/docs/api/ipc-main 6 | // https://electronjs.org/docs/api/ipc-renderer 7 | 8 | const { app, BrowserWindow, ipcMain } = require('electron/main') 9 | const path = require('node:path') 10 | 11 | app.whenReady().then(() => { 12 | const mainWindow = new BrowserWindow({ 13 | height: 600, 14 | width: 600, 15 | webPreferences: { 16 | preload: path.join(__dirname, 'preload.js') 17 | } 18 | }) 19 | 20 | ipcMain.on('asynchronous-message', (event, arg) => { 21 | console.log(arg) // prints "ping" 22 | event.sender.send('asynchronous-reply', 'pong') 23 | }) 24 | 25 | ipcMain.on('synchronous-message', (event, arg) => { 26 | console.log(arg) // prints "ping" 27 | event.returnValue = 'pong' 28 | }) 29 | 30 | ipcMain.handle('invoke-handle-message', (event, arg) => { 31 | console.log(arg) 32 | return 'pong' 33 | }) 34 | 35 | mainWindow.loadFile('index.html') 36 | }) 37 | -------------------------------------------------------------------------------- /static/show-me/ipc/preload.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron/renderer') 2 | 3 | // prints "pong" 4 | console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) 5 | 6 | // prints "pong" 7 | ipcRenderer.on('asynchronous-reply', (_, ...args) => console.log(...args)) 8 | 9 | ipcRenderer.send('asynchronous-message', 'ping') 10 | 11 | ipcRenderer 12 | .invoke('invoke-handle-message', 'ping') 13 | .then((reply) => console.log(reply)) 14 | -------------------------------------------------------------------------------- /static/show-me/menu/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Menu Example 5 | 6 | 7 |

Menu Example

8 | 9 | -------------------------------------------------------------------------------- /static/show-me/menu/main.js: -------------------------------------------------------------------------------- 1 | // Create native application menus and context menus. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/menu 5 | 6 | const { app, BrowserWindow, Menu } = require('electron/main') 7 | 8 | app.whenReady().then(() => { 9 | const mainWindow = new BrowserWindow({ height: 600, width: 600 }) 10 | mainWindow.loadFile('index.html') 11 | 12 | const template = [ 13 | { 14 | label: 'Edit', 15 | submenu: [ 16 | { role: 'undo' }, 17 | { role: 'redo' }, 18 | { type: 'separator' }, 19 | { role: 'cut' }, 20 | { role: 'copy' }, 21 | { role: 'paste' } 22 | ] 23 | }, 24 | { 25 | label: 'Hello From Electron!', 26 | submenu: [ 27 | { 28 | label: 'I have a custom handler', 29 | click () { 30 | console.log('👋') 31 | } 32 | }, 33 | { type: 'separator' }, 34 | { role: 'reload' }, 35 | { role: 'forcereload' }, 36 | { role: 'toggledevtools' }, 37 | { type: 'separator' }, 38 | { role: 'resetzoom' }, 39 | { role: 'zoomin' }, 40 | { role: 'zoomout' }, 41 | { type: 'separator' } 42 | ] 43 | } 44 | ] 45 | 46 | const menu = Menu.buildFromTemplate(template) 47 | Menu.setApplicationMenu(menu) 48 | }) 49 | -------------------------------------------------------------------------------- /static/show-me/net/main.js: -------------------------------------------------------------------------------- 1 | // Issue HTTP/HTTPS requests using Chromium's native networking library. 2 | // 3 | // The net module is a client-side API for issuing HTTP(S) requests. It 4 | // is similar to the HTTP and HTTPS modules of Node.js but uses Chromium's 5 | // native networking library instead of the Node.js implementation, offering 6 | // better support for web proxies. 7 | // 8 | // For more info, see: 9 | // https://electronjs.org/docs/api/net 10 | 11 | const { app, net } = require('electron/main') 12 | 13 | app.whenReady().then(() => { 14 | const request = net.request('https://github.com') 15 | 16 | request.on('response', (response) => { 17 | console.log(`STATUS: ${response.statusCode}`) 18 | console.log(`HEADERS: ${JSON.stringify(response.headers)}`) 19 | 20 | response.on('data', (chunk) => { 21 | console.log(`BODY: ${chunk}`) 22 | }) 23 | 24 | response.on('end', () => { 25 | console.log('No more data in the response.') 26 | }) 27 | }) 28 | 29 | request.end() 30 | }) 31 | -------------------------------------------------------------------------------- /static/show-me/notification/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Document 5 | 6 | 7 |

Notifications Example

8 |

9 | All three operating systems provide means for applications to send notifications 10 | to the user. Electron conveniently allows developers to send notifications with 11 | the HTML5 Notification API, using the currently running operating system's native 12 | notification APIs to display it. 13 |

14 | 15 | 16 | -------------------------------------------------------------------------------- /static/show-me/notification/main.js: -------------------------------------------------------------------------------- 1 | // Create OS desktop notifications 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/notification 5 | // https://electronjs.org/docs/tutorial/notifications 6 | 7 | const { app, BrowserWindow, Notification } = require('electron/main') 8 | const path = require('node:path') 9 | 10 | app.whenReady().then(() => { 11 | const mainWindow = new BrowserWindow({ 12 | height: 600, 13 | width: 600, 14 | webPreferences: { 15 | preload: path.join(__dirname, 'preload.js') 16 | } 17 | }) 18 | 19 | if (Notification.isSupported()) { 20 | const notification = new Notification({ 21 | title: 'Hello World!', 22 | subtitle: 'Nice to see you', 23 | body: 'Are you having a good day?', 24 | hasReply: true 25 | }) 26 | 27 | notification.on('show', () => console.log('Notification shown')) 28 | notification.on('click', () => console.log('Notification clicked')) 29 | notification.on('close', () => console.log('Notification closed')) 30 | notification.on('reply', (event, reply) => { 31 | console.log(`Reply: ${reply}`) 32 | }) 33 | 34 | notification.show() 35 | } else { 36 | console.log('Hm, are notifications supported on this system?') 37 | } 38 | 39 | mainWindow.loadFile('index.html') 40 | }) 41 | -------------------------------------------------------------------------------- /static/show-me/notification/preload.js: -------------------------------------------------------------------------------- 1 | function notifyMe () { 2 | const notification = new window.Notification('Hello World', { 3 | body: 'How is your day?' 4 | }) 5 | 6 | notification.onclick = () => console.log('Clicked') 7 | notification.onclose = () => console.log('Closed') 8 | } 9 | 10 | window.addEventListener('DOMContentLoaded', () => { 11 | document.querySelector('button').onclick = notifyMe 12 | }) 13 | -------------------------------------------------------------------------------- /static/show-me/powermonitor/main.js: -------------------------------------------------------------------------------- 1 | // Monitor power state changes. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/power-monitor 5 | 6 | const { app, powerMonitor } = require('electron/main') 7 | 8 | app.whenReady().then(() => { 9 | powerMonitor.on('suspend', () => { 10 | console.log('The system is going to sleep') 11 | }) 12 | 13 | powerMonitor.on('resume', () => { 14 | console.log('The system is waking up') 15 | }) 16 | 17 | powerMonitor.on('on-ac', () => { 18 | console.log('We\'re on AC power') 19 | }) 20 | 21 | powerMonitor.on('on-battery', () => { 22 | console.log('We\'re on battery power') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /static/show-me/powersaveblocker/main.js: -------------------------------------------------------------------------------- 1 | // Block the system from entering low-power (sleep) mode. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/power-save-blocker 5 | 6 | const { app, powerSaveBlocker } = require('electron/main') 7 | 8 | app.whenReady().then(() => { 9 | const id = powerSaveBlocker.start('prevent-display-sleep') 10 | 11 | console.log(powerSaveBlocker.isStarted(id)) 12 | 13 | // Let's allow power save again in 60000ms 14 | setTimeout(() => { 15 | powerSaveBlocker.stop(id) 16 | }, 60000) 17 | }) 18 | -------------------------------------------------------------------------------- /static/show-me/screen/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Screen Example 5 | 6 | 7 |

Screen Example

8 |

 9 | 
10 | 


--------------------------------------------------------------------------------
/static/show-me/screen/main.js:
--------------------------------------------------------------------------------
 1 | // Retrieve information about screen size, displays, cursor position, etc.
 2 | //
 3 | // For more info, see:
 4 | // https://electronjs.org/docs/api/screen
 5 | 
 6 | const { app, BrowserWindow, ipcMain, screen } = require('electron/main')
 7 | const path = require('node:path')
 8 | 
 9 | app.whenReady().then(() => {
10 |   // Create a window that fills the screen's available
11 |   // work area.
12 |   const primaryDisplay = screen.getPrimaryDisplay()
13 |   const { width, height } = primaryDisplay.workAreaSize
14 | 
15 |   const mainWindow = new BrowserWindow({
16 |     width,
17 |     height,
18 |     webPreferences: {
19 |       preload: path.join(__dirname, 'preload.js')
20 |     }
21 |   })
22 | 
23 |   ipcMain.handle('get-displays', () => {
24 |     return screen.getAllDisplays()
25 |   })
26 |   mainWindow.loadFile('index.html')
27 | })
28 | 


--------------------------------------------------------------------------------
/static/show-me/screen/preload.js:
--------------------------------------------------------------------------------
 1 | const { ipcRenderer } = require('electron/renderer')
 2 | 
 3 | window.addEventListener('DOMContentLoaded', () => {
 4 |   ipcRenderer
 5 |     .invoke('get-displays')
 6 |     .then((screens) => {
 7 |       document.querySelector('pre').innerText = JSON.stringify(screens, undefined, 2)
 8 |     })
 9 | })
10 | 


--------------------------------------------------------------------------------
/static/show-me/session/main.js:
--------------------------------------------------------------------------------
 1 | // Manage browser sessions, cookies, cache, proxy settings, etc.
 2 | //
 3 | // For more info, see:
 4 | // https://electronjs.org/docs/api/session
 5 | 
 6 | const { app, session } = require('electron/main')
 7 | 
 8 | app.whenReady().then(() => {
 9 |   const { defaultSession } = session
10 | 
11 |   // There are quite a few methods available
12 |   // on the session object, here are just two
13 |   // examples:
14 | 
15 |   // Current user agent
16 |   console.log(defaultSession.getUserAgent())
17 | 
18 |   // Cache Size
19 |   defaultSession.getCacheSize().then((result) => {
20 |     console.log(result)
21 |   })
22 | })
23 | 


--------------------------------------------------------------------------------
/static/show-me/shell/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 |   Shell Example
 6 | 
 7 | 
 8 | 
 9 |   

Shell Example

10 | 11 | 12 | 13 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /static/show-me/shell/main.js: -------------------------------------------------------------------------------- 1 | // Manage files and URLs using their default applications. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/shell 5 | 6 | const { app, BrowserWindow, ipcMain, shell } = require('electron/main') 7 | const path = require('node:path') 8 | 9 | app.whenReady().then(() => { 10 | const mainWindow = new BrowserWindow({ 11 | width: 600, 12 | height: 600, 13 | webPreferences: { 14 | preload: path.join(__dirname, 'preload.js') 15 | } 16 | }) 17 | 18 | mainWindow.loadFile('index.html') 19 | 20 | ipcMain.on('open-github', () => { 21 | shell.openExternal('https://github.com') 22 | }) 23 | 24 | ipcMain.on('open-folder', () => { 25 | shell.showItemInFolder(__dirname) 26 | }) 27 | 28 | ipcMain.on('beep', () => { 29 | shell.beep() 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /static/show-me/shell/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron/renderer') 2 | 3 | contextBridge.exposeInMainWorld( 4 | 'electron', 5 | { 6 | openGitHub: () => ipcRenderer.send('open-github'), 7 | openFolder: () => ipcRenderer.send('open-folder'), 8 | beep: () => ipcRenderer.send('beep') 9 | } 10 | ) 11 | -------------------------------------------------------------------------------- /static/show-me/systempreferences/main.js: -------------------------------------------------------------------------------- 1 | // Get system preferences. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/system-preferences#systempreferences 5 | 6 | const { app, systemPreferences } = require('electron/main') 7 | 8 | app.whenReady().then(() => { 9 | // This module let's us access various system preferences. 10 | // Let's start with macOS: 11 | 12 | if (process.platform === 'darwin') { 13 | // Let's get the recent places 14 | const places = systemPreferences.getUserDefault('NSNavRecentPlaces', 'array') 15 | console.log(places) 16 | } 17 | 18 | if (process.platform === 'win32') { 19 | // As an example, let's get the accent color of 20 | // the users current theme 21 | const accentColor = systemPreferences.getAccentColor() 22 | console.log(accentColor) 23 | 24 | // Did the user select a high contrast theme? 25 | const isHighContrast = systemPreferences.isInvertedColorScheme() 26 | console.log(isHighContrast) 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /static/show-me/touchbar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TouchBar Example 5 | 6 | 7 |

TouchBar Example

8 |

Check out your touch bar!

9 | 10 | -------------------------------------------------------------------------------- /static/show-me/utilityprocess/child.js: -------------------------------------------------------------------------------- 1 | process.parentPort.on('message', (e) => { 2 | if (e.data === 'Hello from parent!') { 3 | process.parentPort.postMessage('Hello from child!') 4 | } 5 | }) 6 | -------------------------------------------------------------------------------- /static/show-me/utilityprocess/main.js: -------------------------------------------------------------------------------- 1 | // The "utilityProcess" module allows creating a child process with 2 | // Node.js and Message ports enabled.It provides the equivalent of[`child_process.fork`][] API from Node.js 3 | // but instead uses[Services API][] from Chromium to launch the child process. 4 | // 5 | // For more info, see: 6 | // https://electronjs.org/docs/api/utility-process 7 | 8 | const { app, utilityProcess } = require('electron/main') 9 | const path = require('node:path') 10 | 11 | app.whenReady().then(() => { 12 | const child = utilityProcess.fork(path.join(__dirname, 'child.js')) 13 | 14 | child.on('spawn', () => { 15 | console.log(`Child process spawned with PID: ${child.pid}`) 16 | child.postMessage('Hello from parent!') 17 | }) 18 | 19 | child.on('message', (message) => { 20 | console.log(`Received message from child: ${message}`) 21 | }) 22 | 23 | child.on('exit', (code) => { 24 | console.log(`Child exited with code: ${code}`) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /static/show-me/webcontents/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Load URL Example 5 | 6 | 7 |

Load URL Example

8 |

9 | Almost immediately after opening the page, the Electron 10 | homepage should load. 11 |

12 | 13 | -------------------------------------------------------------------------------- /static/show-me/webcontents/main.js: -------------------------------------------------------------------------------- 1 | // Render and control web pages. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/web-contents 5 | 6 | const { app, BrowserWindow, webContents } = require('electron/main') 7 | 8 | app.whenReady().then(() => { 9 | const mainWindow = new BrowserWindow({ height: 600, width: 600 }) 10 | mainWindow.loadFile('index.html') 11 | 12 | // This setTimeout is to demonstrate the method firing 13 | // for the demo, and is not needed in production. 14 | setTimeout(() => { 15 | const contents = webContents.getAllWebContents()[0] 16 | 17 | // The WebContents class has dozens of methods and 18 | // events available. As an example, we'll call one 19 | // of them here: loadURL, which loads Electron's 20 | // home page. 21 | const options = { extraHeaders: 'pragma: no-cache\n' } 22 | contents.loadURL('https://electronjs.org', options) 23 | }, 1000) 24 | }) 25 | -------------------------------------------------------------------------------- /static/show-me/webcontentsview/main.js: -------------------------------------------------------------------------------- 1 | // A View that displays a WebContents. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/web-contents-view 5 | 6 | // In the main process. 7 | const { app, BaseWindow, WebContentsView } = require('electron') 8 | 9 | app.whenReady().then(() => { 10 | const win = new BaseWindow({ width: 800, height: 400 }) 11 | 12 | const view1 = new WebContentsView() 13 | win.contentView.addChildView(view1) 14 | view1.setBounds({ x: 0, y: 0, width: 400, height: 400 }) 15 | view1.webContents.loadURL('https://www.electronjs.org') 16 | 17 | const view2 = new WebContentsView() 18 | win.contentView.addChildView(view2) 19 | view2.setBounds({ x: 400, y: 0, width: 400, height: 400 }) 20 | view2.webContents.loadURL('https://www.electronjs.org/fiddle') 21 | }) 22 | -------------------------------------------------------------------------------- /static/show-me/webframe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebFrame Example 5 | 6 | 7 |

WebFrame Example

8 |

Every 750ms, this page will change the zoom level.

9 | 10 | -------------------------------------------------------------------------------- /static/show-me/webframe/main.js: -------------------------------------------------------------------------------- 1 | // Customize the rendering of the current web page. 2 | // 3 | // For more info, see: 4 | // https://electronjs.org/docs/api/web-frame 5 | 6 | const { app, BrowserWindow } = require('electron/main') 7 | const path = require('node:path') 8 | 9 | app.whenReady().then(() => { 10 | const mainWindow = new BrowserWindow({ 11 | width: 600, 12 | height: 600, 13 | webPreferences: { 14 | preload: path.join(__dirname, 'preload.js') 15 | } 16 | }) 17 | mainWindow.loadFile('index.html') 18 | }) 19 | -------------------------------------------------------------------------------- /static/show-me/webframe/preload.js: -------------------------------------------------------------------------------- 1 | const { webFrame } = require('electron/renderer') 2 | 3 | setInterval(() => { 4 | // A random number 5 | const level = Math.floor(Math.random() * 10) 6 | 7 | // Let's set a random zoom level 8 | webFrame.setZoomLevel(level) 9 | }, 750) 10 | -------------------------------------------------------------------------------- /tests/fixtures/templates/11-x-y.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/tests/fixtures/templates/11-x-y.zip -------------------------------------------------------------------------------- /tests/fixtures/templates/master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron/fiddle/f5a59c8028369459d9c4290a65fb2b7e2528251d/tests/fixtures/templates/master.zip -------------------------------------------------------------------------------- /tests/globalSetup.ts: -------------------------------------------------------------------------------- 1 | import { maybeFetchContributors } from '../tools/contributors'; 2 | import { populateReleases } from '../tools/fetch-releases'; 3 | 4 | export default async function globalSetup() { 5 | await Promise.all([maybeFetchContributors(true), populateReleases()]); 6 | } 7 | -------------------------------------------------------------------------------- /tests/install-state-spec.ts: -------------------------------------------------------------------------------- 1 | import { InstallState as FiddleCoreInstallState } from '@electron/fiddle-core'; 2 | 3 | import { InstallState } from '../src/interfaces'; 4 | 5 | describe('InstallState', () => { 6 | it('is in-sync with @electron/fiddle-core', async () => { 7 | expect(InstallState).toStrictEqual(FiddleCoreInstallState); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/main/devtools-spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import { mocked } from 'jest-mock'; 6 | 7 | import { setupDevTools } from '../../src/main/devtools'; 8 | import { isDevMode } from '../../src/main/utils/devmode'; 9 | 10 | jest.mock('../../src/main/utils/devmode'); 11 | 12 | jest.mock('electron-devtools-installer', () => ({ 13 | default: jest.fn(), 14 | REACT_DEVELOPER_TOOLS: 'REACT_DEVELOPER_TOOLS', 15 | REACT_PERF: 'REACT_PERF', 16 | })); 17 | 18 | describe('devtools', () => { 19 | it('does not set up developer tools if not in dev mode', () => { 20 | const devtools = require('electron-devtools-installer'); 21 | mocked(isDevMode).mockReturnValue(false); 22 | setupDevTools(); 23 | 24 | expect(devtools.default).toHaveBeenCalledTimes(0); 25 | }); 26 | 27 | it('sets up developer tools if in dev mode', () => { 28 | const devtools = require('electron-devtools-installer'); 29 | mocked(isDevMode).mockReturnValue(true); 30 | setupDevTools(); 31 | 32 | expect(devtools.default).toHaveBeenCalledTimes(1); 33 | }); 34 | 35 | it('catch error in setting up developer tools', async () => { 36 | const devtools = require('electron-devtools-installer'); 37 | // throw devtool error 38 | devtools.default.mockRejectedValue(new Error('devtool error')); 39 | mocked(isDevMode).mockReturnValue(true); 40 | 41 | try { 42 | await setupDevTools(); 43 | } catch (e) { 44 | expect(e).toMatch('error'); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/main/squirrel-spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import { shouldQuit } from '../../src/main/squirrel'; 6 | 7 | jest.mock('electron-squirrel-startup', () => ({ mock: true })); 8 | 9 | describe('shouldQuit', () => { 10 | it('returns simply electron-squirrel-startup', () => { 11 | expect(shouldQuit()).toEqual({ mock: true }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/main/templates-spec.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'node:path'; 2 | 3 | import { mocked } from 'jest-mock'; 4 | 5 | import { MAIN_JS } from '../../src/interfaces'; 6 | import { getTemplateValues } from '../../src/main/templates'; 7 | import { getEmptyContent } from '../../src/utils/editor-utils'; 8 | 9 | jest.unmock('fs-extra'); 10 | jest.mock('../../src/main/constants', () => ({ 11 | STATIC_DIR: path.join(__dirname, '../../static'), 12 | })); 13 | 14 | describe('templates', () => { 15 | const KNOWN_GOOD_TEMPLATE = 'clipboard'; 16 | const KNOWN_BAD_TEMPLATE = 'not-a-real-show-me'; 17 | 18 | describe('getTemplateValues()', () => { 19 | it('loads templates', async () => { 20 | const values = await getTemplateValues(KNOWN_GOOD_TEMPLATE); 21 | expect(values[MAIN_JS].length).toBeGreaterThan(0); 22 | }); 23 | 24 | it('handles errors', async () => { 25 | const values = await getTemplateValues(KNOWN_BAD_TEMPLATE); 26 | expect(values[MAIN_JS]).toBe(getEmptyContent(MAIN_JS)); 27 | }); 28 | 29 | it('reports missing files', async () => { 30 | console.log = jest.fn(); 31 | 32 | await getTemplateValues(KNOWN_BAD_TEMPLATE); 33 | 34 | expect(console.log).toHaveBeenCalledWith( 35 | expect.stringMatching('Got Fiddle from'), 36 | [MAIN_JS], 37 | ); 38 | 39 | mocked(console.log).mockClear(); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/main/update-spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | import { mocked } from 'jest-mock'; 5 | 6 | jest.useFakeTimers(); 7 | jest.spyOn(global, 'setTimeout'); 8 | 9 | const mockUpdateApp = jest.fn(); 10 | jest.mock('update-electron-app', () => ({ updateElectronApp: mockUpdateApp })); 11 | 12 | describe('update', () => { 13 | const { setupUpdates } = require('../../src/main/update'); 14 | const { updateElectronApp } = require('update-electron-app'); 15 | 16 | it('schedules an update check', () => { 17 | setupUpdates(); 18 | 19 | expect(setTimeout).toHaveBeenCalledTimes(1); 20 | mocked(setTimeout).mock.calls[0][0](); 21 | expect(updateElectronApp).toHaveBeenCalled(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/main/utils/check-first-run-spec.ts: -------------------------------------------------------------------------------- 1 | import * as electron from 'electron'; 2 | import * as fs from 'fs-extra'; 3 | import { mocked } from 'jest-mock'; 4 | 5 | import { isFirstRun } from '../../../src/main/utils/check-first-run'; 6 | 7 | jest.mock('fs-extra', () => ({ 8 | existsSync: jest.fn(), 9 | outputFileSync: jest.fn(), 10 | })); 11 | 12 | describe('isFirstRun', () => { 13 | beforeEach(() => { 14 | mocked(electron.app.getPath).mockReturnValue('path'); 15 | }); 16 | 17 | it('reports a non-first run', () => { 18 | mocked(fs.existsSync).mockReturnValueOnce(true); 19 | expect(isFirstRun()).toBe(false); 20 | }); 21 | 22 | it('reports a first run', () => { 23 | mocked(fs.existsSync).mockReturnValueOnce(false); 24 | expect(isFirstRun()).toBe(true); 25 | expect(fs.outputFileSync).toHaveBeenCalledTimes(1); 26 | }); 27 | 28 | it('handles an error', () => { 29 | mocked(fs.existsSync).mockImplementationOnce(() => { 30 | throw new Error('bwap bwap'); 31 | }); 32 | 33 | expect(isFirstRun()).toBe(true); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/main/utils/devmode-spec.ts: -------------------------------------------------------------------------------- 1 | import { isDevMode } from '../../../src/main/utils/devmode'; 2 | 3 | describe('devMode', () => { 4 | const old = (process as any).defaultApp; // for tsconfig error 5 | 6 | afterEach(() => { 7 | Object.defineProperty(process, 'defaultApp', { value: old }); 8 | }); 9 | 10 | it('correctly returns true if defaultApp', () => { 11 | Object.defineProperty(process, 'defaultApp', { 12 | value: true, 13 | writable: true, 14 | }); 15 | 16 | expect(isDevMode()).toBe(true); 17 | }); 18 | 19 | it('correctly returns false if not defaultApp', () => { 20 | Object.defineProperty(process, 'defaultApp', { 21 | value: undefined, 22 | writable: true, 23 | }); 24 | 25 | expect(isDevMode()).toBe(false); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/main/utils/get-project-name-spec.ts: -------------------------------------------------------------------------------- 1 | import { getProjectName } from '../../../src/main/utils/get-project-name'; 2 | 3 | describe('get-project-name', () => { 4 | describe('getProjectName()', () => { 5 | it('returns a random name', async () => { 6 | const result = getProjectName(); 7 | expect(result).toBeTruthy(); 8 | }); 9 | 10 | it('returns a name from the local path if saved', async () => { 11 | const result = getProjectName('a/b/myFiddle'); 12 | expect(result).toBe('myFiddle'); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/main/utils/get-username-spec.ts: -------------------------------------------------------------------------------- 1 | import { getUsername } from '../../../src/main/utils/get-username'; 2 | 3 | jest.mock('node:os', () => ({ 4 | userInfo: () => ({ 5 | username: 'test-user', 6 | }), 7 | })); 8 | 9 | describe('get-username', () => { 10 | it('returns the OS username', () => { 11 | expect(getUsername()).toBe('test-user'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/main/versions-spec.ts: -------------------------------------------------------------------------------- 1 | import { ElectronVersions } from '@electron/fiddle-core'; 2 | import * as semver from 'semver'; 3 | 4 | import { 5 | getLatestStable, 6 | getOldestSupportedMajor, 7 | getReleasedVersions, 8 | isReleasedMajor, 9 | setupVersions, 10 | } from '../../src/main/versions'; 11 | 12 | describe('versions', () => { 13 | let knownVersions: ElectronVersions; 14 | 15 | beforeAll(async () => { 16 | knownVersions = await setupVersions(); 17 | }); 18 | 19 | describe('getOldestSupportedMajor()', () => { 20 | function getExpectedOldestSupportedVersion() { 21 | const NUM_BRANCHES = parseInt(process.env.NUM_STABLE_BRANCHES || '') || 3; 22 | return getLatestStable()!.major + 1 - NUM_BRANCHES; 23 | } 24 | 25 | it('matches expected oldest supported version', () => { 26 | const expected = getExpectedOldestSupportedVersion(); 27 | expect(getOldestSupportedMajor()).toBe(expected); 28 | }); 29 | 30 | it('honors process.env.NUM_STABLE_BRANCHES', () => { 31 | process.env.NUM_STABLE_BRANCHES = '2'; 32 | const expected = getExpectedOldestSupportedVersion(); 33 | expect(getOldestSupportedMajor()).toBe(expected); 34 | }); 35 | }); 36 | 37 | describe('isReleasedMajor()', () => { 38 | it('returns true for recognized releases', () => { 39 | expect(isReleasedMajor(3)).toBe(true); 40 | }); 41 | 42 | it('returns false for unrecognized releases', () => { 43 | expect(isReleasedMajor(1000)).toBe(false); 44 | }); 45 | }); 46 | 47 | describe('getReleasedVersions()', () => { 48 | it('includes versions >= 0.24.0', () => { 49 | const expected = [ 50 | { version: '10.0.0-nightly.20200303' }, 51 | { version: '9.0.0-beta.5' }, 52 | { version: '4.2.0' }, 53 | ]; 54 | const spy = jest 55 | .spyOn(knownVersions, 'versions', 'get') 56 | .mockReturnValue(expected.map(({ version }) => semver.parse(version)!)); 57 | 58 | const result = getReleasedVersions(); 59 | 60 | expect(result).toEqual(expected); 61 | spy.mockRestore(); 62 | }); 63 | 64 | it('does not fetch versions < 0.24.0', () => { 65 | const spy = jest 66 | .spyOn(knownVersions, 'versions', 'get') 67 | .mockReturnValue([semver.parse('0.23.0')!]); 68 | 69 | const result = getReleasedVersions(); 70 | 71 | expect(result).toHaveLength(0); 72 | spy.mockRestore(); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /tests/mocks/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ElectronTypesMock, 3 | FileManagerMock, 4 | RemoteLoaderMock, 5 | RunnerMock, 6 | StateMock, 7 | createEditorValues, 8 | } from './mocks'; 9 | 10 | export class AppMock { 11 | public electronTypes = new ElectronTypesMock(); 12 | public fileManager = new FileManagerMock(); 13 | public getEditorValues = jest.fn().mockResolvedValue(createEditorValues()); 14 | public loadTheme = jest.fn(); 15 | public openFiddle = jest.fn(); 16 | public remoteLoader = new RemoteLoaderMock(); 17 | public replaceFiddle = jest.fn(); 18 | public runner = new RunnerMock(); 19 | public setup = jest.fn(); 20 | public state = new StateMock(); 21 | public taskRunner = {}; 22 | } 23 | -------------------------------------------------------------------------------- /tests/mocks/bisector.ts: -------------------------------------------------------------------------------- 1 | import { RunnableVersion } from '../../src/interfaces'; 2 | 3 | export class BisectorMock { 4 | public revList: Array; 5 | public minRev: number; 6 | public maxRev: number; 7 | public pivot: number; 8 | 9 | public getCurrentVersion = jest.fn(); 10 | public continue = jest.fn(); 11 | public calculatePivot = jest.fn(); 12 | } 13 | -------------------------------------------------------------------------------- /tests/mocks/child-process.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | export class ChildProcessMock extends EventEmitter { 4 | public stdout = new EventEmitter(); 5 | public stderr = new EventEmitter(); 6 | public kill = jest.fn(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/mocks/editor-values.ts: -------------------------------------------------------------------------------- 1 | import { EditorValues, MAIN_JS } from '../../src/interfaces'; 2 | 3 | export function createEditorValues(): EditorValues { 4 | return { 5 | [MAIN_JS]: '// main.js', 6 | 'renderer.js': '// renderer.js', 7 | 'preload.js': '// preload.js', 8 | 'index.html': '', 9 | 'styles.css': '/* style.css */', 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /tests/mocks/electron-fiddle.ts: -------------------------------------------------------------------------------- 1 | export class ElectronFiddleMock { 2 | public addEventListener = jest.fn(); 3 | public addModules = jest.fn(); 4 | public arch = process.arch; 5 | public blockAccelerators = jest.fn(); 6 | public cleanupDirectory = jest.fn(); 7 | public confirmQuit = jest.fn(); 8 | public createThemeFile = jest.fn(); 9 | public deleteUserData = jest.fn(); 10 | public downloadVersion = jest.fn(); 11 | public fetchVersions = jest.fn(); 12 | public getAvailableThemes = jest.fn(); 13 | public getElectronTypes = jest.fn(); 14 | public getIsPackageManagerInstalled = jest.fn(); 15 | public getLatestStable = jest.fn(); 16 | public getLocalVersionState = jest.fn(); 17 | public getNodeTypes = jest.fn(); 18 | public getOldestSupportedMajor = jest.fn(); 19 | public getReleaseInfo = jest.fn(); 20 | public getReleasedVersions = jest.fn(); 21 | public getTemplate = jest.fn(); 22 | public getTemplateValues = jest.fn(); 23 | public getTestTemplate = jest.fn(); 24 | public isReleasedMajor = jest.fn(); 25 | public getProjectName = jest.fn(); 26 | public getUsername = jest.fn(); 27 | public getVersionState = jest.fn(); 28 | public macTitlebarClicked = jest.fn(); 29 | public onGetFiles = jest.fn(); 30 | public openThemeFolder = jest.fn(); 31 | public packageRun = jest.fn(); 32 | public platform = process.platform; 33 | public pushOutputEntry = jest.fn(); 34 | public reloadWindows = jest.fn(); 35 | public removeAllListeners = jest.fn(); 36 | public removeVersion = jest.fn(); 37 | public saveFilesToTemp = jest.fn(); 38 | public selectLocalVersion = jest.fn(); 39 | public sendReady = jest.fn(); 40 | public setNativeTheme = jest.fn(); 41 | public setShowMeTemplate = jest.fn(); 42 | public showWarningDialog = jest.fn(); 43 | public showWindow = jest.fn(); 44 | public startFiddle = jest.fn(); 45 | public stopFiddle = jest.fn(); 46 | public taskDone = jest.fn(); 47 | public readThemeFile = jest.fn(); 48 | public themePath = '~/.electron-fiddle/themes'; 49 | public uncacheTypes = jest.fn(); 50 | public unwatchElectronTypes = jest.fn(); 51 | } 52 | -------------------------------------------------------------------------------- /tests/mocks/electron-types.ts: -------------------------------------------------------------------------------- 1 | export class ElectronTypesMock { 2 | public setVersion = jest.fn(); 3 | public uncache = jest.fn(); 4 | } 5 | 6 | export interface NodeTypesMock { 7 | path: string; 8 | type: string; 9 | contentType: string; 10 | integrity: string; 11 | lastModified: string; 12 | size: number; 13 | files?: NodeTypesMock[]; 14 | } 15 | -------------------------------------------------------------------------------- /tests/mocks/electron-versions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InstallState, 3 | RunnableVersion, 4 | VersionSource, 5 | } from '../../src/interfaces'; 6 | 7 | export class VersionsMock { 8 | public readonly mockVersions: Record; 9 | public readonly mockVersionsArray: RunnableVersion[]; 10 | 11 | constructor() { 12 | const versions = ['2.0.3', '2.0.2', '2.0.1', '2.0.0', '1.8.7']; 13 | 14 | const arr = versions.map((version) => ({ 15 | source: VersionSource.remote, 16 | state: InstallState.installed, 17 | version, 18 | })); 19 | 20 | const obj: Record = {}; 21 | for (const ver of arr) { 22 | obj[ver.version] = ver; 23 | } 24 | 25 | this.mockVersions = obj; 26 | this.mockVersionsArray = arr; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/mocks/fiddle-core.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | import { ChildProcessMock } from './child-process'; 4 | import { InstallState } from '../../src/interfaces'; 5 | 6 | export class InstallerMock extends EventEmitter { 7 | public state = () => InstallState.installed; 8 | } 9 | 10 | export class FiddleRunnerMock { 11 | public spawn: () => Promise = jest.fn(); 12 | public static create = jest.fn(); 13 | } 14 | 15 | export class ElectronVersionsMock { 16 | public getReleaseInfo = jest.fn(); 17 | } 18 | -------------------------------------------------------------------------------- /tests/mocks/file-manager.ts: -------------------------------------------------------------------------------- 1 | export class FileManagerMock { 2 | public getFiles = jest.fn(); 3 | public openFiddle = jest.fn(); 4 | public saveToTemp = jest.fn().mockResolvedValue('/mock/temp/dir'); 5 | } 6 | -------------------------------------------------------------------------------- /tests/mocks/mocks.ts: -------------------------------------------------------------------------------- 1 | import { AppMock } from './app'; 2 | import { BisectorMock } from './bisector'; 3 | import { BrowserWindowMock } from './browser-window'; 4 | import { ChildProcessMock } from './child-process'; 5 | import { createEditorValues } from './editor-values'; 6 | import { 7 | IPCMainMock, 8 | IPCRendererMock, 9 | MenuItemMock, 10 | MenuMock, 11 | NativeImageMock, 12 | } from './electron'; 13 | import { ElectronFiddleMock } from './electron-fiddle'; 14 | import { ElectronTypesMock, NodeTypesMock } from './electron-types'; 15 | import { VersionsMock } from './electron-versions'; 16 | import { FileManagerMock } from './file-manager'; 17 | import { MonacoEditorMock, MonacoMock, MonacoModelMock } from './monaco'; 18 | import { RemoteLoaderMock } from './remote-loader'; 19 | import { RunnerMock } from './runner'; 20 | import { StateMock } from './state'; 21 | import { WebContentsMock } from './web-contents'; 22 | 23 | export { 24 | AppMock, 25 | BisectorMock, 26 | BrowserWindowMock, 27 | ChildProcessMock, 28 | ElectronFiddleMock, 29 | ElectronTypesMock, 30 | FileManagerMock, 31 | IPCMainMock, 32 | IPCRendererMock, 33 | MenuItemMock, 34 | MenuMock, 35 | MonacoEditorMock, 36 | MonacoMock, 37 | MonacoModelMock, 38 | NativeImageMock, 39 | NodeTypesMock, 40 | RemoteLoaderMock, 41 | RunnerMock, 42 | StateMock, 43 | VersionsMock, 44 | WebContentsMock, 45 | createEditorValues, 46 | }; 47 | -------------------------------------------------------------------------------- /tests/mocks/remote-loader.ts: -------------------------------------------------------------------------------- 1 | export class RemoteLoaderMock { 2 | public confirmAddFile = jest.fn(); 3 | public fetchGistAndLoad = jest.fn(); 4 | public loadFiddleFromElectronExample = jest.fn(); 5 | public loadFiddleFromGist = jest.fn(); 6 | public setElectronVersion = jest.fn(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/mocks/runner.ts: -------------------------------------------------------------------------------- 1 | export class RunnerMock { 2 | public autobisect = jest.fn(); 3 | public run = jest.fn(); 4 | public stop = jest.fn(); 5 | } 6 | -------------------------------------------------------------------------------- /tests/mocks/styles.ts: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /tests/mocks/versions-mock.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "node_id": "MDc6UmVsZWFzZTI0MTc1NzU0", 4 | "tag_name": "v10.0.0-nightly.20200303", 5 | "name": "electron v10.0.0-nightly.20200303", 6 | "prerelease": true, 7 | "published_at": "2020-03-03T18:06:05Z", 8 | "version": "10.0.0-nightly.20200303", 9 | "npm_package_name": "electron-nightly", 10 | "deps": { 11 | "node": "12.16.1", 12 | "v8": "8.2.8-electron.0", 13 | "uv": "1.34.0", 14 | "zlib": "1.2.11", 15 | "openssl": "1.1.0", 16 | "modules": "82", 17 | "chrome": "82.0.4050.0" 18 | }, 19 | "npm_dist_tags": [ 20 | "nightly" 21 | ], 22 | "total_downloads": 6 23 | }, 24 | { 25 | "node_id": "MDc6UmVsZWFzZTI0MTM1NzIw", 26 | "tag_name": "v9.0.0-beta.5", 27 | "name": "electron v9.0.0-beta.5", 28 | "prerelease": true, 29 | "published_at": "2020-03-02T18:05:08Z", 30 | "version": "9.0.0-beta.5", 31 | "npm_package_name": "electron", 32 | "deps": { 33 | "node": "12.14.1", 34 | "v8": "8.2.1-electron.0", 35 | "uv": "1.33.1", 36 | "zlib": "1.2.11", 37 | "openssl": "1.1.0", 38 | "modules": "80", 39 | "chrome": "82.0.4048.0" 40 | }, 41 | "npm_dist_tags": [ 42 | "beta", 43 | "beta-9-x-y" 44 | ], 45 | "total_downloads": 110 46 | }, 47 | { 48 | "node_id": "MDc6UmVsZWFzZTE3MTIxODMx", 49 | "tag_name": "v4.2.0", 50 | "name": "electron v4.2.0", 51 | "prerelease": false, 52 | "published_at": "2019-05-03T03:16:59Z", 53 | "version": "4.2.0", 54 | "npm_package_name": "electron", 55 | "deps": { 56 | "node": "10.11.0", 57 | "v8": "6.9.427.31-electron.0", 58 | "uv": "1.23.0", 59 | "zlib": "1.2.11", 60 | "openssl": "1.1.0", 61 | "modules": "69", 62 | "chrome": "69.0.3497.128" 63 | }, 64 | "npm_dist_tags": [], 65 | "total_downloads": 21041 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /tests/preload/preload-spec.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import { contextBridge } from 'electron'; 6 | import { mocked } from 'jest-mock'; 7 | 8 | import { setupFiddleGlobal } from '../../src/preload/preload'; 9 | 10 | describe('preload', () => { 11 | // We instantiate this in `tests/setup.js` for the main and 12 | // renderer processes, but we don't need these mocks since 13 | // these things are being instantiated within the preload 14 | beforeEach(() => { 15 | delete (window as any).ElectronFiddle; 16 | }); 17 | 18 | describe('setupGlobalWindow()', () => { 19 | it('exposes an ElectronFiddle object via the contextBridge', async () => { 20 | mocked(contextBridge.exposeInMainWorld).mockReturnValue(undefined); 21 | await setupFiddleGlobal(); 22 | 23 | expect(contextBridge.exposeInMainWorld).toHaveBeenCalledWith( 24 | 'ElectronFiddle', 25 | expect.objectContaining({ startFiddle: expect.anything() }), 26 | ); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/commands-runner-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Runner component renders InstallState.downloading 1`] = ` 4 | 11 | } 12 | id="button-run" 13 | text="Downloading" 14 | /> 15 | `; 16 | 17 | exports[`Runner component renders InstallState.installed 1`] = ` 18 | 25 | `; 26 | 27 | exports[`Runner component renders InstallState.installing 1`] = ` 28 | 34 | } 35 | id="button-run" 36 | text="Unzipping" 37 | /> 38 | `; 39 | 40 | exports[`Runner component renders InstallState.missing 1`] = ` 41 | 47 | } 48 | id="button-run" 49 | text="Checking status" 50 | /> 51 | `; 52 | 53 | exports[`Runner component renders idle 1`] = ` 54 | 61 | `; 62 | 63 | exports[`Runner component renders installing modules 1`] = ` 64 | 70 | } 71 | id="button-run" 72 | text="Installing modules" 73 | /> 74 | `; 75 | 76 | exports[`Runner component renders running 1`] = ` 77 | 85 | `; 86 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/dialog-add-theme-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AddThemeDialog component renders 1`] = ` 4 | 11 |
14 | 24 |
25 |
26 |
29 |
32 | 39 | 45 |
46 |
47 |
48 | `; 49 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/dialog-generic-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GenericDialog component renders confirmation 1`] = ` 4 | 14 |

15 | 16 | `; 17 | 18 | exports[`GenericDialog component renders success 1`] = ` 19 | 29 |

30 | 31 | `; 32 | 33 | exports[`GenericDialog component renders warning 1`] = ` 34 | 44 |

45 | 46 | `; 47 | 48 | exports[`GenericDialog component renders with an input prompt 1`] = ` 49 | 59 |

60 | 64 | 65 | `; 66 | 67 | exports[`GenericDialog component renders with an input prompt and placeholder 1`] = ` 68 | 78 |

79 | 84 | 85 | `; 86 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/dialog-token-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TokenDialog component renders 1`] = ` 4 | 10 |

13 |

14 | Generate a 15 | 16 | 19 | GitHub Personal Access Token 20 | 21 | 22 | and paste it here: 23 |

24 | 29 |
30 |
33 |
36 | 44 | 50 |
51 |
52 | 53 | `; 54 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/header-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Header component renders 1`] = ` 4 | 5 | 13 | 16 | 17 | `; 18 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-block-accelerators-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BlockAcceleratorsSettings component renders 1`] = ` 4 |
5 |

6 | Block Keyboard Shortcuts 7 |

8 | 9 | 10 |

11 | Any keyboard shortcuts checked below will be disabled. 12 |

13 | 19 | 25 |
26 |
27 |
28 | `; 29 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-console-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ConsoleSettings component renders 1`] = ` 4 |
5 |

6 | Console 7 |

8 | 9 | 10 |

11 | Enable this option to automatically clear the console whenever you run your 12 | fiddle. 13 |

14 | 18 |
19 |
20 |
21 | `; 22 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-font-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`FontSettings component renders 1`] = ` 4 |
5 |

6 | Font Settings 7 |

8 | 9 |

10 | Set a font family and size for your editors. Reload or restart for changes to take effect. 11 |

12 | 16 | 20 | 21 | 25 | 30 | 35 | 36 |
37 |
38 | `; 39 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-github-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GitHubSettings component renders when not signed in 1`] = ` 4 |
5 |

6 | GitHub 7 |

8 | 9 |

10 | Your fiddles can be published as GitHub Gists - that way you can share your fiddles with the world! 11 |

12 | 17 |
18 | 19 | 20 |

21 | Enable this option to always publish your fiddle as a revision of the 22 | default fiddle gist values. 23 |

24 | 29 |
30 |
31 |
32 | `; 33 | 34 | exports[`GitHubSettings component renders when signed in 1`] = ` 35 |
36 |

37 | GitHub 38 |

39 | 40 |

41 | Your fiddles can be published as public GitHub Gists. Using the personal access token you gave us, we logged you into GitHub as 42 | 43 | 44 | Test User 45 | 46 | . 47 |

48 | 53 |
54 | 55 | 56 |

57 | Enable this option to always publish your fiddle as a revision of the 58 | default fiddle gist values. 59 |

60 | 65 |
66 |
67 |
68 | `; 69 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-mirror-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`MirrorSettings component renders 1`] = ` 4 |
5 |

6 | Electron Mirrors 7 |

8 | 9 | 15 | 19 | 23 | 27 | 28 | 33 | 38 | 39 |
40 | `; 41 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-package-author-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`PackageAuthorSettings component renders 1`] = ` 4 |
5 |

6 | Package Author 7 |

8 | 9 | 13 | 18 | 19 | 20 |
21 | `; 22 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/settings-general-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GeneralSettings component renders 1`] = ` 4 |
5 |

6 | General Settings 7 |

8 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 36 |
37 | `; 38 | -------------------------------------------------------------------------------- /tests/renderer/components/__snapshots__/sidebar-package-manager-spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SidebarPackageManager component renders 1`] = ` 4 |
7 |
8 | Modules 9 |
10 | 18 | Search for modules here... 19 | 20 | } 21 | onItemSelect={[Function]} 22 | onQueryChange={[Function]} 23 | openOnKeyDown={false} 24 | popoverProps={ 25 | { 26 | "fill": true, 27 | "minimal": true, 28 | "usePortal": false, 29 | } 30 | } 31 | resetOnClose={false} 32 | resetOnSelect={true} 33 | /> 34 | 41 |