mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
740 lines
27 KiB
TypeScript
740 lines
27 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
vi.mock('electron', async () =>
|
|
(await import('./createMainWindow-test-harness')).electronModuleMock()
|
|
)
|
|
vi.mock('./macos-tahoe-release', async () =>
|
|
(await import('./createMainWindow-test-harness')).macosTahoeReleaseMock()
|
|
)
|
|
vi.mock('../app-icon', async () => (await import('./createMainWindow-test-harness')).appIconMock())
|
|
vi.mock('../browser/browser-manager', async () =>
|
|
(await import('./createMainWindow-test-harness')).browserManagerMock()
|
|
)
|
|
vi.mock('../browser/browser-route-session-runtime', async () => ({
|
|
browserRouteSessionRegistry: {
|
|
isAllowedPartition: (await import('./createMainWindow-test-harness')).routePartitionAllowedMock
|
|
},
|
|
browserRouteWebContentsRegistry: {
|
|
attachGuest: (await import('./createMainWindow-test-harness')).attachRouteGuestMock
|
|
}
|
|
}))
|
|
vi.mock('../browser/browser-client-page-renderer-runtime', async () => {
|
|
const harness = await import('./createMainWindow-test-harness')
|
|
return {
|
|
attachBrowserClientPageRenderer: harness.attachClientPageRendererMock,
|
|
retireBrowserClientPageRenderer: harness.retireClientPageRendererMock
|
|
}
|
|
})
|
|
|
|
import { createMainWindow, loadMainWindow } from './createMainWindow'
|
|
import { ipcMain } from 'electron'
|
|
import { getBrowserClientHostId } from '../browser/browser-client-host-id'
|
|
import {
|
|
formatBrowserClientHostIdArgument,
|
|
readBrowserClientHostIdArgument
|
|
} from '../../shared/browser-client-host-id-argument'
|
|
import { resetExpectedTeardownStateForTest } from '../crash-reporting/expected-teardown-state'
|
|
import {
|
|
attachGuestPoliciesMock,
|
|
attachRouteGuestMock,
|
|
browserWindowMock,
|
|
macosTahoeMock,
|
|
openExternalMock,
|
|
powerMonitorOnMock,
|
|
resetMainWindowMocks,
|
|
routePartitionAllowedMock,
|
|
withPlatform
|
|
} from './createMainWindow-test-harness'
|
|
|
|
describe('createMainWindow', () => {
|
|
beforeEach(() => {
|
|
resetMainWindowMocks()
|
|
resetExpectedTeardownStateForTest()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('can defer renderer loading until startup IPC handlers are registered', () => {
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => true),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
const win = createMainWindow(null, { deferLoad: true })
|
|
|
|
expect(browserWindowInstance.loadFile).not.toHaveBeenCalled()
|
|
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
|
|
|
|
loadMainWindow(win)
|
|
|
|
expect(browserWindowInstance.loadFile).toHaveBeenCalledTimes(1)
|
|
expect(browserWindowInstance.loadURL).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('enables renderer sandboxing and opens external links safely', () => {
|
|
// Retain all will-navigate listeners; one slot per event lets registration order hide a missing guard.
|
|
const windowHandlers: Record<string, ((...args: any[]) => void)[]> = {}
|
|
const fire = (event: string, ...args: any[]): void => {
|
|
for (const handler of windowHandlers[event] ?? []) {
|
|
handler(...args)
|
|
}
|
|
}
|
|
let windowOpenHandler: (...args: any[]) => unknown = () => undefined
|
|
const record = (event: string, handler: (...args: any[]) => void): void => {
|
|
;(windowHandlers[event] ??= []).push(handler)
|
|
}
|
|
const webContents = {
|
|
getURL: vi.fn(() => 'file:///opt/orca/renderer/index.html'),
|
|
isDestroyed: vi.fn(() => false),
|
|
mainFrame: {},
|
|
on: vi.fn((event, handler) => {
|
|
record(event, handler)
|
|
}),
|
|
once: vi.fn((event, handler) => {
|
|
record(event, handler)
|
|
}),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
setWindowOpenHandler: vi.fn((handler) => {
|
|
windowOpenHandler = handler
|
|
}),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => true),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
createMainWindow(null)
|
|
|
|
expect(browserWindowMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
webPreferences: expect.objectContaining({ sandbox: true })
|
|
})
|
|
)
|
|
const browserWindowOptions = browserWindowMock.mock.calls[0]?.[0]
|
|
// acceptFirstMouse lets the app-activating click focus the floating workspace without a second click.
|
|
expect(browserWindowOptions.acceptFirstMouse).toBe(true)
|
|
if (process.platform === 'darwin') {
|
|
expect(browserWindowOptions).toMatchObject({
|
|
titleBarStyle: 'hiddenInset'
|
|
})
|
|
} else if (process.platform === 'win32') {
|
|
expect(browserWindowOptions).toMatchObject({
|
|
titleBarStyle: 'hidden'
|
|
})
|
|
} else {
|
|
// Linux frame:false avoids stacking native and renderer titlebars.
|
|
expect(browserWindowOptions.titleBarStyle).toBeUndefined()
|
|
expect(browserWindowOptions.frame).toBe(false)
|
|
}
|
|
|
|
expect(windowOpenHandler({ url: 'https://example.com' })).toEqual({ action: 'deny' })
|
|
expect(windowOpenHandler({ url: 'localhost:3000' })).toEqual({ action: 'deny' })
|
|
expect(windowOpenHandler({ url: 'file:///etc/passwd' })).toEqual({ action: 'deny' })
|
|
expect(windowOpenHandler({ url: 'not a url' })).toEqual({ action: 'deny' })
|
|
|
|
expect(openExternalMock).toHaveBeenCalledTimes(2)
|
|
expect(openExternalMock).toHaveBeenCalledWith('https://example.com/')
|
|
expect(openExternalMock).toHaveBeenCalledWith('http://localhost:3000/')
|
|
|
|
const preventDefault = vi.fn()
|
|
fire('will-navigate', { preventDefault } as never, 'https://example.com/docs')
|
|
expect(preventDefault).toHaveBeenCalledTimes(1)
|
|
expect(openExternalMock).toHaveBeenCalledTimes(3)
|
|
expect(openExternalMock).toHaveBeenLastCalledWith('https://example.com/docs')
|
|
|
|
const localhostPreventDefault = vi.fn()
|
|
fire('will-navigate', { preventDefault: localhostPreventDefault } as never, 'localhost:3000')
|
|
expect(localhostPreventDefault).toHaveBeenCalledTimes(1)
|
|
expect(openExternalMock).toHaveBeenCalledTimes(4)
|
|
expect(openExternalMock).toHaveBeenLastCalledWith('http://localhost:3000/')
|
|
|
|
const fileNavigationPreventDefault = vi.fn()
|
|
fire(
|
|
'will-navigate',
|
|
{ preventDefault: fileNavigationPreventDefault } as never,
|
|
'file:///etc/passwd'
|
|
)
|
|
expect(fileNavigationPreventDefault).toHaveBeenCalledTimes(1)
|
|
expect(openExternalMock).toHaveBeenCalledTimes(4)
|
|
|
|
const allowBlankEvent = { preventDefault: vi.fn() }
|
|
const allowBlankPrefs = { partition: 'persist:orca-browser' }
|
|
fire(
|
|
'will-attach-webview',
|
|
allowBlankEvent as never,
|
|
allowBlankPrefs as never,
|
|
{ src: 'data:text/html,' } as never
|
|
)
|
|
expect(allowBlankEvent.preventDefault).not.toHaveBeenCalled()
|
|
expect(allowBlankPrefs).toMatchObject({
|
|
disableHtmlFullscreenWindowResize: true,
|
|
partition: 'persist:orca-browser',
|
|
preload: expect.stringMatching(/browser-window-close-preload\.js$/),
|
|
sandbox: true
|
|
})
|
|
|
|
routePartitionAllowedMock.mockImplementation(
|
|
(partition) => partition === 'persist:orca-browser-v1-route-partition'
|
|
)
|
|
const allowRouteEvent = { preventDefault: vi.fn() }
|
|
const allowRoutePrefs = { partition: 'persist:orca-browser-v1-route-partition' }
|
|
fire(
|
|
'will-attach-webview',
|
|
allowRouteEvent as never,
|
|
allowRoutePrefs as never,
|
|
{ src: 'about:blank' } as never
|
|
)
|
|
expect(allowRouteEvent.preventDefault).not.toHaveBeenCalled()
|
|
expect(allowRoutePrefs).toMatchObject({
|
|
partition: 'persist:orca-browser-v1-route-partition',
|
|
sandbox: true
|
|
})
|
|
const denyRouteNavigationEvent = { preventDefault: vi.fn() }
|
|
fire(
|
|
'will-attach-webview',
|
|
denyRouteNavigationEvent as never,
|
|
{ partition: 'persist:orca-browser-v1-route-partition' } as never,
|
|
{ src: 'https://example.com/' } as never
|
|
)
|
|
expect(denyRouteNavigationEvent.preventDefault).toHaveBeenCalledOnce()
|
|
|
|
const denyInlineHtmlEvent = { preventDefault: vi.fn() }
|
|
fire(
|
|
'will-attach-webview',
|
|
denyInlineHtmlEvent as never,
|
|
{ partition: 'persist:orca-browser' } as never,
|
|
{ src: 'data:text/html,<script>alert(1)</script>' } as never
|
|
)
|
|
expect(denyInlineHtmlEvent.preventDefault).toHaveBeenCalledTimes(1)
|
|
|
|
const guest = { marker: 'guest' }
|
|
fire('did-attach-webview', {} as never, guest as never)
|
|
expect(attachGuestPoliciesMock).toHaveBeenCalledWith(guest)
|
|
expect(attachRouteGuestMock).toHaveBeenCalledWith(guest)
|
|
expect(attachGuestPoliciesMock.mock.invocationCallOrder[0]).toBeLessThan(
|
|
attachRouteGuestMock.mock.invocationCallOrder[0]
|
|
)
|
|
|
|
const untrustedPreloadParams = {
|
|
src: 'data:text/html,',
|
|
preload: 'file:///tmp/untrusted-preload.js'
|
|
}
|
|
const hardenedPrefs = {
|
|
partition: 'persist:orca-browser',
|
|
preload: '/tmp/untrusted-preload.js'
|
|
}
|
|
fire(
|
|
'will-attach-webview',
|
|
{ preventDefault: vi.fn() } as never,
|
|
hardenedPrefs as never,
|
|
untrustedPreloadParams as never
|
|
)
|
|
expect(untrustedPreloadParams.preload).toBeUndefined()
|
|
expect(hardenedPrefs.preload).toMatch(/browser-window-close-preload\.js$/)
|
|
expect(hardenedPrefs.preload).not.toContain('untrusted-preload')
|
|
|
|
const secondGuest = { marker: 'second-guest' }
|
|
fire('did-attach-webview', {} as never, secondGuest as never)
|
|
expect(attachGuestPoliciesMock).toHaveBeenLastCalledWith(secondGuest)
|
|
expect(attachRouteGuestMock).toHaveBeenLastCalledWith(secondGuest)
|
|
})
|
|
|
|
// Stamp host identity before the first session snapshot; sendSync cannot answer before listener registration.
|
|
it('stamps the browser host id into the renderer that owns the guests, and strips it from a guest that carries one', () => {
|
|
const windowHandlers: Record<string, (...args: any[]) => void> = {}
|
|
const webContents = {
|
|
getURL: vi.fn(() => 'file:///opt/orca/renderer/index.html'),
|
|
isDestroyed: vi.fn(() => false),
|
|
mainFrame: {},
|
|
on: vi.fn((event, handler) => {
|
|
windowHandlers[event] = handler
|
|
}),
|
|
once: vi.fn((event, handler) => {
|
|
windowHandlers[event] = handler
|
|
}),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return {
|
|
webContents,
|
|
on: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => true),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
})
|
|
|
|
createMainWindow(null)
|
|
|
|
const stamped = browserWindowMock.mock.calls[0]?.[0].webPreferences?.additionalArguments
|
|
expect(stamped).toEqual([formatBrowserClientHostIdArgument(getBrowserClientHostId())])
|
|
expect(readBrowserClientHostIdArgument(stamped ?? [])).toBe(getBrowserClientHostId())
|
|
|
|
// Electron 43 does not inherit embedder arguments; inject a hostile stamp to prove the handler strips it.
|
|
const guestPreferences = {
|
|
partition: 'persist:orca-browser',
|
|
additionalArguments: [...(stamped ?? [])]
|
|
}
|
|
windowHandlers['will-attach-webview'](
|
|
{ preventDefault: vi.fn() } as never,
|
|
guestPreferences as never,
|
|
{ src: 'data:text/html,' } as never
|
|
)
|
|
|
|
expect(guestPreferences.additionalArguments).toBeUndefined()
|
|
})
|
|
|
|
it('sets platform-specific titlebar and frame options for every desktop platform', () => {
|
|
for (const [platform, expected] of [
|
|
['darwin', { titleBarStyle: 'hiddenInset', frame: undefined }],
|
|
['win32', { titleBarStyle: 'hidden', frame: undefined }],
|
|
['linux', { titleBarStyle: undefined, frame: false }]
|
|
] satisfies [
|
|
NodeJS.Platform,
|
|
{ titleBarStyle: string | undefined; frame: boolean | undefined }
|
|
][]) {
|
|
browserWindowMock.mockReset()
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => true),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
setWindowButtonPosition: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform(platform, () => createMainWindow(null))
|
|
|
|
const browserWindowOptions = browserWindowMock.mock.calls[0]?.[0]
|
|
expect(browserWindowOptions.titleBarStyle).toBe(expected.titleBarStyle)
|
|
expect(browserWindowOptions.frame).toBe(expected.frame)
|
|
}
|
|
})
|
|
|
|
it('never requests macOS vibrancy or transparency when window blur is enabled (#8482)', () => {
|
|
for (const [platform, expected] of [
|
|
['darwin', { backgroundMaterial: undefined }],
|
|
['win32', { backgroundMaterial: 'acrylic' }],
|
|
['linux', { backgroundMaterial: undefined }]
|
|
] satisfies [NodeJS.Platform, { backgroundMaterial: string | undefined }][]) {
|
|
browserWindowMock.mockReset()
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => false),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
getBounds: vi.fn(() => ({ x: 10, y: 20, width: 1000, height: 700 })),
|
|
setSize: vi.fn(),
|
|
setWindowButtonPosition: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform(platform, () =>
|
|
createMainWindow({
|
|
getUI: () => ({}),
|
|
getSettings: () => ({ windowBackgroundBlur: true }),
|
|
updateUI: vi.fn()
|
|
} as never)
|
|
)
|
|
|
|
const browserWindowOptions = browserWindowMock.mock.calls[0]?.[0]
|
|
expect(browserWindowOptions.vibrancy).toBeUndefined()
|
|
expect(browserWindowOptions.transparent).toBeUndefined()
|
|
expect(browserWindowOptions.backgroundMaterial).toBe(expected.backgroundMaterial)
|
|
expect(browserWindowOptions.backgroundColor).toBe('#ffffff')
|
|
}
|
|
})
|
|
|
|
it('keeps macOS background throttling enabled while repainting visibility transitions', () => {
|
|
vi.useFakeTimers()
|
|
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
|
|
let windowSize: [number, number] = [1200, 800]
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
|
const handlers = windowHandlers.get(event) ?? []
|
|
handlers.push(handler)
|
|
windowHandlers.set(event, handlers)
|
|
}),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => false),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => windowSize),
|
|
setSize: vi.fn((width: number, height: number) => {
|
|
windowSize = [width, height]
|
|
}),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform('darwin', () => createMainWindow(null))
|
|
|
|
expect(webContents.setBackgroundThrottling).toHaveBeenCalledWith(true)
|
|
expect(webContents.setBackgroundThrottling).not.toHaveBeenCalledWith(false)
|
|
expect(windowHandlers.get('restore')).toHaveLength(1)
|
|
expect(windowHandlers.get('show')).toHaveLength(1)
|
|
expect(windowHandlers.get('focus')).toHaveLength(1)
|
|
|
|
windowHandlers.get('show')?.[0]?.()
|
|
windowHandlers.get('restore')?.[0]?.()
|
|
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
|
|
// Defer size nudges outside show/restore dispatch.
|
|
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
|
|
|
|
vi.advanceTimersByTime(0)
|
|
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(1, 1201, 800)
|
|
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(1)
|
|
|
|
vi.advanceTimersByTime(32)
|
|
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(2, 1200, 800)
|
|
|
|
vi.advanceTimersByTime(217)
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
|
|
|
|
vi.advanceTimersByTime(1)
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(3)
|
|
|
|
// Focus only invalidates; size jiggles would resize terminals on every focus.
|
|
const setSizeCalls = browserWindowInstance.setSize.mock.calls.length
|
|
windowHandlers.get('focus')?.[0]?.()
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(4)
|
|
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(setSizeCalls)
|
|
})
|
|
|
|
it('runs a full repaint when the renderer relays a genuine window reveal (STA-2383)', () => {
|
|
vi.useFakeTimers()
|
|
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
|
|
let windowSize: [number, number] = [1200, 800]
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
|
const handlers = windowHandlers.get(event) ?? []
|
|
handlers.push(handler)
|
|
windowHandlers.set(event, handlers)
|
|
}),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => false),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => windowSize),
|
|
setSize: vi.fn((width: number, height: number) => {
|
|
windowSize = [width, height]
|
|
}),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform('darwin', () => createMainWindow(null))
|
|
|
|
const revealHandler = vi
|
|
.mocked(ipcMain.on)
|
|
.mock.calls.find(([channel]) => channel === 'ui:window-revealed')?.[1]
|
|
expect(revealHandler).toBeTypeOf('function')
|
|
|
|
// A reveal from another WebContents must not repaint this window.
|
|
revealHandler?.({ sender: {} } as never)
|
|
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
|
|
expect(webContents.invalidate).not.toHaveBeenCalled()
|
|
|
|
// Genuine reveal uses the pre-Tahoe jiggle that bare focus avoids.
|
|
revealHandler?.({ sender: webContents } as never)
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(1)
|
|
// Defer compositor nudge outside event dispatch.
|
|
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
|
|
vi.advanceTimersByTime(0)
|
|
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(1, 1201, 800)
|
|
vi.advanceTimersByTime(32)
|
|
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(2, 1200, 800)
|
|
|
|
// Coalesce reveal jiggles so repeated signals do not multiply terminal resizes.
|
|
revealHandler?.({ sender: webContents } as never)
|
|
revealHandler?.({ sender: webContents } as never)
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(3)
|
|
vi.advanceTimersByTime(0)
|
|
expect(browserWindowInstance.setSize).toHaveBeenNthCalledWith(3, 1201, 800)
|
|
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(3)
|
|
|
|
// Never restore stale bounds over a concurrent user resize.
|
|
windowSize = [1400, 900]
|
|
vi.advanceTimersByTime(32)
|
|
expect(browserWindowInstance.setSize).toHaveBeenCalledTimes(3)
|
|
|
|
windowHandlers.get('closed')?.[0]?.()
|
|
expect(ipcMain.removeListener).toHaveBeenCalledWith('ui:window-revealed', revealHandler)
|
|
})
|
|
|
|
it('repaints without the size nudge on macOS 26+ where re-entrant frame updates can deadlock AppKit', () => {
|
|
vi.useFakeTimers()
|
|
macosTahoeMock.value = true
|
|
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
|
const handlers = windowHandlers.get(event) ?? []
|
|
handlers.push(handler)
|
|
windowHandlers.set(event, handlers)
|
|
}),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => false),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform('darwin', () => createMainWindow(null))
|
|
|
|
windowHandlers.get('show')?.[0]?.()
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(1)
|
|
|
|
// Tahoe's delayed repaint must also avoid setSize.
|
|
vi.advanceTimersByTime(300)
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
|
|
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('invalidates a maximized macOS 26 window without changing its frame', () => {
|
|
vi.useFakeTimers()
|
|
macosTahoeMock.value = true
|
|
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
|
const handlers = windowHandlers.get(event) ?? []
|
|
handlers.push(handler)
|
|
windowHandlers.set(event, handlers)
|
|
}),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => true),
|
|
isFullScreen: vi.fn(() => true),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform('darwin', () => createMainWindow(null))
|
|
|
|
windowHandlers.get('show')?.[0]?.()
|
|
vi.advanceTimersByTime(300)
|
|
|
|
expect(webContents.invalidate).toHaveBeenCalledTimes(2)
|
|
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('invalidates without frame or device emulation when macOS 26 wakes from sleep', () => {
|
|
vi.useFakeTimers()
|
|
macosTahoeMock.value = true
|
|
const windowHandlers = new Map<string, ((...args: any[]) => void)[]>()
|
|
const webContents = {
|
|
on: vi.fn(),
|
|
setZoomLevel: vi.fn(),
|
|
setBackgroundThrottling: vi.fn(),
|
|
invalidate: vi.fn(),
|
|
isDestroyed: vi.fn(() => false),
|
|
setWindowOpenHandler: vi.fn(),
|
|
send: vi.fn(),
|
|
isDevToolsOpened: vi.fn(),
|
|
openDevTools: vi.fn(),
|
|
closeDevTools: vi.fn(),
|
|
enableDeviceEmulation: vi.fn(),
|
|
disableDeviceEmulation: vi.fn()
|
|
}
|
|
const browserWindowInstance = {
|
|
webContents,
|
|
on: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
|
const handlers = windowHandlers.get(event) ?? []
|
|
handlers.push(handler)
|
|
windowHandlers.set(event, handlers)
|
|
}),
|
|
isDestroyed: vi.fn(() => false),
|
|
isMaximized: vi.fn(() => false),
|
|
isFullScreen: vi.fn(() => false),
|
|
getSize: vi.fn(() => [1200, 800]),
|
|
setSize: vi.fn(),
|
|
maximize: vi.fn(),
|
|
show: vi.fn(),
|
|
loadFile: vi.fn(() => Promise.resolve()),
|
|
loadURL: vi.fn(() => Promise.resolve())
|
|
}
|
|
browserWindowMock.mockImplementation(function () {
|
|
return browserWindowInstance
|
|
})
|
|
|
|
withPlatform('darwin', () => createMainWindow(null))
|
|
|
|
const resumeHandler = powerMonitorOnMock.mock.calls.find(
|
|
([event]) => event === 'resume'
|
|
)?.[1] as (() => void) | undefined
|
|
expect(resumeHandler).toBeDefined()
|
|
resumeHandler?.()
|
|
|
|
expect(webContents.invalidate).toHaveBeenCalled()
|
|
vi.advanceTimersByTime(300)
|
|
expect(browserWindowInstance.setSize).not.toHaveBeenCalled()
|
|
expect(webContents.enableDeviceEmulation).not.toHaveBeenCalled()
|
|
expect(webContents.disableDeviceEmulation).not.toHaveBeenCalled()
|
|
})
|
|
})
|