blob: af3dae28397079c68a892c5b33b065b74f939600 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import {
createTarget,
registerNoopActions,
} from '../../testing/EnvironmentHelpers.js';
import {
describeWithMockConnection,
dispatchEvent,
} from '../../testing/MockConnection.js';
import * as TextEditor from '../../ui/components/text_editor/text_editor.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Console from './console.js';
function compileScriptResponse(exception?: string): Protocol.Runtime.CompileScriptResponse {
const exceptionDetails = exception ? {exception: {description: exception}} : undefined;
return {exceptionDetails, getError: () => {}} as unknown as Protocol.Runtime.CompileScriptResponse;
}
describeWithMockConnection('ConsoleContextSelector', () => {
let target: SDK.Target.Target;
let consolePrompt: Console.ConsolePrompt.ConsolePrompt;
let evaluateOnTarget: sinon.SinonStub;
let compileScript: sinon.SinonStub;
let editor: TextEditor.TextEditor.TextEditor;
beforeEach(() => {
sinon.stub(Host.AidaClient.HostConfigTracker.instance(), 'pollAidaAvailability').callsFake(async () => {});
sinon.stub(Host.AidaClient.AidaClient, 'checkAccessPreconditions')
.resolves(Host.AidaClient.AidaAccessPreconditions.AVAILABLE);
registerNoopActions(['console.clear', 'console.clear.history', 'console.create-pin']);
consolePrompt = new Console.ConsolePrompt.ConsolePrompt();
editor = consolePrompt.element.querySelector('devtools-text-editor')!;
setCodeMirrorContent('foo');
target = createTarget();
const targetContext = createExecutionContext(target);
UI.Context.Context.instance().setFlavor(SDK.RuntimeModel.ExecutionContext, targetContext);
evaluateOnTarget = sinon.stub(target.runtimeAgent(), 'invoke_evaluate');
compileScript = sinon.stub(target.runtimeAgent(), 'invoke_compileScript').resolves(compileScriptResponse());
Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', false);
});
let id = 0;
function createExecutionContext(target: SDK.Target.Target): SDK.RuntimeModel.ExecutionContext {
++id;
dispatchEvent(target, 'Runtime.executionContextCreated', {
context: {
id: id as Protocol.Runtime.ExecutionContextId,
origin: 'http://example.com',
name: `c${id}`,
uniqueId: `c${id}`,
auxData: {
frameId: 'f2',
},
},
});
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
assert.exists(runtimeModel);
const executionContext = runtimeModel.executionContext(id);
assert.exists(executionContext);
return executionContext;
}
function dispatchKeydown(key: string, options: Omit<KeyboardEventInit, 'key'> = {}): void {
editor.editor.contentDOM.dispatchEvent(new KeyboardEvent('keydown', {
key,
bubbles: true,
...options,
}));
}
function setCodeMirrorContent(
content: string, {selectionHead}: {selectionHead: number} = {selectionHead: content.length}) {
editor.dispatch({
changes: {from: 0, to: editor.state.doc.length, insert: content},
selection: {anchor: selectionHead},
});
}
it('evaluates on enter', async () => {
dispatchKeydown('Enter');
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.called(evaluateOnTarget);
});
it('allows user to enable pasting by typing \'allow pasting\'', async () => {
const setting = Common.Settings.Settings.instance().createSetting(
'disable-self-xss-warning', false, Common.Settings.SettingStorageType.SYNCED);
assert.isFalse(setting.get());
consolePrompt.showSelfXssWarning();
dispatchKeydown('Enter');
await new Promise(resolve => setTimeout(resolve, 0));
assert.isFalse(setting.get());
consolePrompt.showSelfXssWarning();
setCodeMirrorContent('allow pasting');
dispatchKeydown('Enter');
await new Promise(resolve => setTimeout(resolve, 0));
assert.isTrue(setting.get());
});
it('does not evaluate incomplete expression', async () => {
compileScript.resolves(compileScriptResponse('SyntaxError: Unexpected end of input'));
dispatchKeydown('Enter');
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.notCalled(evaluateOnTarget);
});
it('evaluate incomplete expression if forced', async () => {
compileScript.resolves(compileScriptResponse('SyntaxError: Unexpected end of input'));
dispatchKeydown('Ctrl-Enter');
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.called(evaluateOnTarget);
});
it('does not evaluate if the current context has changed', async () => {
const anotherTarget = createTarget();
const anotherTargetContext = createExecutionContext(target);
const evaluateOnAnotherTarget = sinon.stub(anotherTarget.runtimeAgent(), 'invoke_evaluate');
sinon.stub(anotherTarget.runtimeAgent(), 'invoke_compileScript').resolves(compileScriptResponse());
dispatchKeydown('Enter');
UI.Context.Context.instance().setFlavor(SDK.RuntimeModel.ExecutionContext, anotherTargetContext);
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.notCalled(evaluateOnAnotherTarget);
sinon.assert.notCalled(evaluateOnTarget);
});
it('handles event sequence correctly', async () => {
const stub = sinon.stub(TextEditor.TextEditorHistory.TextEditorHistory.prototype, 'moveHistory');
// Verify that ArrowUp with repeat does not move history.
dispatchKeydown('ArrowUp', {repeat: true});
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.notCalled(stub);
// Verify that ArrowUp does move history.
dispatchKeydown('ArrowUp');
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.calledOnceWithExactly(stub, TextEditor.TextEditorHistory.Direction.BACKWARD);
stub.resetHistory();
// Verify that ArrowDown with repeat does not move history.
dispatchKeydown('ArrowDown', {repeat: true});
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.notCalled(stub);
// Verify that ArrowDown does move history.
dispatchKeydown('ArrowDown');
await new Promise(resolve => setTimeout(resolve, 0));
sinon.assert.calledOnceWithExactly(stub, TextEditor.TextEditorHistory.Direction.FORWARD);
});
});