// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from 'chai';
import sinon from 'sinon';
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as Platform from '../../core/platform/platform.js';
import * as Root from '../../core/root/root.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import { findMenuItemWithLabel, getContextMenuForElement } from '../../testing/ContextMenuHelpers.js';
import { dispatchPasteEvent, renderElementIntoDOM } from '../../testing/DOMHelpers.js';
import { createTarget, registerNoopActions, updateHostConfig } from '../../testing/EnvironmentHelpers.js';
import { expectCall, expectCalled } from '../../testing/ExpectStubCall.js';
import { stubFileManager } from '../../testing/FileManagerHelpers.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 { AiCodeCompletionSummaryToolbar } from '../common/common.js';
import * as Console from './console.js';
const { urlString } = Platform.DevToolsPath;
describeWithMockConnection('ConsoleView', () => {
    let consoleView;
    beforeEach(() => {
        registerNoopActions(['console.clear', 'console.clear.history', 'console.create-pin']);
        consoleView = Console.ConsoleView.ConsoleView.instance({ forceNew: true, viewportThrottlerTimeout: 0 });
    });
    afterEach(() => {
        consoleView.dispose();
        Console.ConsoleView.ConsoleView.clearConsoleViewInstanceForTest();
        consoleView.detach();
    });
    it('expands a minimized drawer when toggling console', () => {
        const inspectorView = UI.InspectorView.InspectorView.instance({ forceNew: true });
        const drawerVisibleStub = sinon.stub(inspectorView, 'drawerVisible').returns(true);
        const isDrawerMinimizedStub = sinon.stub(inspectorView, 'isDrawerMinimized').returns(true);
        const setDrawerMinimizedStub = sinon.stub(inspectorView, 'setDrawerMinimized');
        const hasFocusStub = sinon.stub(consoleView, 'hasFocus').returns(false);
        const bringToFrontStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'bringToFront');
        const showStub = sinon.stub(Common.Console.Console.instance(), 'show');
        const focusPromptStub = sinon.stub(consoleView, 'focusPrompt');
        const delegate = new Console.ConsoleView.ActionDelegate();
        assert.isTrue(delegate.handleAction({}, 'console.toggle'));
        sinon.assert.calledOnceWithExactly(setDrawerMinimizedStub, false);
        sinon.assert.calledOnce(showStub);
        sinon.assert.calledOnce(focusPromptStub);
        sinon.assert.calledOnce(bringToFrontStub);
        drawerVisibleStub.restore();
        isDrawerMinimizedStub.restore();
        setDrawerMinimizedStub.restore();
        hasFocusStub.restore();
        bringToFrontStub.restore();
        showStub.restore();
        focusPromptStub.restore();
        UI.InspectorView.InspectorView.removeInstance();
    });
    it('minimizes drawer when console is already shown and focused in expanded drawer', () => {
        const inspectorView = UI.InspectorView.InspectorView.instance({ forceNew: true });
        const drawerVisibleStub = sinon.stub(inspectorView, 'drawerVisible').returns(true);
        const isDrawerMinimizedStub = sinon.stub(inspectorView, 'isDrawerMinimized').returns(false);
        const setDrawerMinimizedStub = sinon.stub(inspectorView, 'setDrawerMinimized');
        const minimizeDrawerStub = sinon.stub(inspectorView, 'minimizeDrawer');
        const isShowingStub = sinon.stub(consoleView, 'isShowing').returns(true);
        const hasFocusStub = sinon.stub(consoleView, 'hasFocus').returns(true);
        const bringToFrontStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'bringToFront');
        const showStub = sinon.stub(Common.Console.Console.instance(), 'show');
        const focusPromptStub = sinon.stub(consoleView, 'focusPrompt');
        const delegate = new Console.ConsoleView.ActionDelegate();
        assert.isTrue(delegate.handleAction({}, 'console.toggle'));
        sinon.assert.notCalled(setDrawerMinimizedStub);
        sinon.assert.calledOnce(minimizeDrawerStub);
        sinon.assert.notCalled(showStub);
        sinon.assert.notCalled(focusPromptStub);
        sinon.assert.notCalled(bringToFrontStub);
        drawerVisibleStub.restore();
        isDrawerMinimizedStub.restore();
        setDrawerMinimizedStub.restore();
        minimizeDrawerStub.restore();
        isShowingStub.restore();
        hasFocusStub.restore();
        bringToFrontStub.restore();
        showStub.restore();
        focusPromptStub.restore();
        UI.InspectorView.InspectorView.removeInstance();
    });
    it('focuses console prompt when drawer is expanded but console is not focused', () => {
        const inspectorView = UI.InspectorView.InspectorView.instance({ forceNew: true });
        const drawerVisibleStub = sinon.stub(inspectorView, 'drawerVisible').returns(true);
        const isDrawerMinimizedStub = sinon.stub(inspectorView, 'isDrawerMinimized').returns(false);
        const setDrawerMinimizedStub = sinon.stub(inspectorView, 'setDrawerMinimized');
        const minimizeDrawerStub = sinon.stub(inspectorView, 'minimizeDrawer');
        const isShowingStub = sinon.stub(consoleView, 'isShowing').returns(true);
        const hasFocusStub = sinon.stub(consoleView, 'hasFocus').returns(false);
        const bringToFrontStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'bringToFront');
        const showStub = sinon.stub(Common.Console.Console.instance(), 'show');
        const focusPromptStub = sinon.stub(consoleView, 'focusPrompt');
        const delegate = new Console.ConsoleView.ActionDelegate();
        assert.isTrue(delegate.handleAction({}, 'console.toggle'));
        sinon.assert.notCalled(minimizeDrawerStub);
        sinon.assert.notCalled(setDrawerMinimizedStub);
        sinon.assert.calledOnce(bringToFrontStub);
        sinon.assert.calledOnce(showStub);
        sinon.assert.calledOnce(focusPromptStub);
        drawerVisibleStub.restore();
        isDrawerMinimizedStub.restore();
        setDrawerMinimizedStub.restore();
        minimizeDrawerStub.restore();
        isShowingStub.restore();
        hasFocusStub.restore();
        bringToFrontStub.restore();
        showStub.restore();
        focusPromptStub.restore();
        UI.InspectorView.InspectorView.removeInstance();
    });
    it('adds a title to every checkbox label in the settings view', async () => {
        const consoleSettingsCheckboxes = consoleView.element.querySelector('devtools-toolbar').querySelectorAll('devtools-checkbox');
        assert.isOk(consoleSettingsCheckboxes, 'No checkbox found in console settings');
        for (const checkbox of consoleSettingsCheckboxes) {
            assert.isTrue(checkbox.shadowRoot?.querySelector('.devtools-checkbox-text')?.hasAttribute('title'));
        }
        // This test transitively schedules a task which may cause errors if the task
        // is run without the environments set in this test. Thus wait for its completion
        // before proceding to the next test.
        await consoleView.getScheduledRefreshPromiseForTest();
    });
    function createConsoleMessage(target, message, type = "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, level = null) {
        return new SDK.ConsoleModel.ConsoleMessage(target.model(SDK.RuntimeModel.RuntimeModel), "javascript" /* Protocol.Log.LogEntrySource.Javascript */, level, message, { type });
    }
    let globalMessageTimestamp = 0;
    function addMessage(consoleModel, target, message, type, level, timestamp) {
        const consoleMessage = createConsoleMessage(target, message, type, level);
        consoleMessage.timestamp = timestamp ?? ++globalMessageTimestamp;
        consoleModel.addMessage(consoleMessage);
    }
    it('can save to file', async () => {
        const tabTarget = createTarget({ type: SDK.Target.Type.TAB });
        createTarget({ parentTarget: tabTarget, subtype: 'prerender' });
        const target = createTarget({ parentTarget: tabTarget });
        const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
        assert.exists(consoleModel);
        consoleModel.addMessage(createConsoleMessage(target, 'message 1'));
        consoleModel.addMessage(createConsoleMessage(target, 'message 2'));
        const messagesElement = consoleView.element.querySelector('#console-messages');
        assert.exists(messagesElement);
        const contextMenu = getContextMenuForElement(messagesElement);
        const saveAsItem = findMenuItemWithLabel(contextMenu.saveSection(), 'Save as…');
        assert.exists(saveAsItem);
        const TIMESTAMP = 42;
        const URL_HOST = 'example.com';
        sinon.stub(Date, 'now').returns(TIMESTAMP);
        target.setInspectedURL(urlString `${`http://${URL_HOST}/foo`}`);
        const FILENAME = `${URL_HOST}-${TIMESTAMP}.log`;
        const fileManager = stubFileManager();
        const fileManagerCloseCall = expectCall(fileManager.close);
        contextMenu.invokeHandler(saveAsItem.id());
        assert.isTrue(fileManager.save.calledOnceWith(FILENAME, TextUtils.ContentData.EMPTY_TEXT_CONTENT_DATA, /* forceSaveAs=*/ true));
        await fileManagerCloseCall;
        assert.isTrue(fileManager.append.calledOnceWith(FILENAME, sinon.match('message 1\nmessage 2\n')));
    });
    it('can copy to clipboard', async () => {
        const tabTarget = createTarget({ type: SDK.Target.Type.TAB });
        createTarget({ parentTarget: tabTarget, subtype: 'prerender' });
        const target = createTarget({ parentTarget: tabTarget });
        const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
        assert.exists(consoleModel);
        consoleModel.addMessage(createConsoleMessage(target, 'message 1'));
        consoleModel.addMessage(createConsoleMessage(target, 'message 2'));
        const messagesElement = consoleView.element.querySelector('#console-messages');
        assert.exists(messagesElement);
        const contextMenu = getContextMenuForElement(messagesElement);
        const copy = findMenuItemWithLabel(contextMenu.saveSection(), 'Copy console');
        assert.exists(copy);
        const copyText = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'copyText').resolves();
        contextMenu.invokeHandler(copy.id());
        await expectCalled(copyText);
        sinon.assert.callCount(copyText, 1);
        assert.deepEqual(copyText.lastCall.args, ['message 1\nmessage 2\n']);
        copyText.resetHistory();
    });
    it('creates console history', () => {
        const target = createTarget();
        const id = 1;
        dispatchEvent(target, 'Runtime.executionContextCreated', {
            context: {
                id: id,
                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);
        UI.Context.Context.instance().setFlavor(SDK.RuntimeModel.ExecutionContext, executionContext);
        const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
        assert.exists(consoleModel);
        consoleModel.addMessage(createConsoleMessage(target, 'let x = 1;', SDK.ConsoleModel.FrontendMessageType.Command));
        consoleModel.addMessage(createConsoleMessage(target, 'let y = 100;', SDK.ConsoleModel.FrontendMessageType.Command));
        const consoleHistory = consoleView.getConsoleMessageHistory();
        assert.deepEqual(consoleHistory, 'let x = 1;\n\nlet y = 100;\n\n');
    });
    async function getConsoleMessages() {
        const messagesElement = consoleView.element.querySelector('#console-messages');
        assert.exists(messagesElement);
        await new Promise(resolve => setTimeout(resolve, 0));
        return [...messagesElement.querySelectorAll('.console-message-text')].map(e => e.innerText);
    }
    const messageTests = (inScope) => () => {
        let target;
        beforeEach(() => {
            target = createTarget();
            SDK.TargetManager.TargetManager.instance().setScopeTarget(inScope ? target : null);
            consoleView.markAsRoot();
            renderElementIntoDOM(consoleView);
        });
        it('adds messages', async () => {
            const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
            assert.exists(consoleModel);
            SDK.ConsoleModel.ConsoleModel.requestClearMessages();
            consoleModel.addMessage(createConsoleMessage(target, 'message 1'));
            consoleModel.addMessage(createConsoleMessage(target, 'message 2'));
            const messages = await getConsoleMessages();
            assert.deepEqual(messages, inScope ? ['message 1', 'message 2'] : []);
        });
        it('prints results', async () => {
            const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
            assert.exists(consoleModel);
            const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
            assert.exists(runtimeModel);
            SDK.ConsoleModel.ConsoleModel.requestClearMessages();
            consoleModel.dispatchEventToListeners(SDK.ConsoleModel.Events.CommandEvaluated, {
                result: new SDK.RemoteObject.RemoteObjectImpl(runtimeModel, undefined, 'number', undefined, 42),
                commandMessage: createConsoleMessage(target, '[ultimateQuestionOfLife, theUniverse, everything].join()'),
            });
            const messages = await getConsoleMessages();
            assert.deepEqual(messages, inScope ? ['42'] : []);
        });
    };
    describe('in scope', messageTests(true));
    describe('out of scope', messageTests(false));
    const handlesSwitchingScope = (preserveLog) => async () => {
        Common.Settings.Settings.instance().moduleSetting('preserve-console-log').set(preserveLog);
        const target = createTarget();
        SDK.TargetManager.TargetManager.instance().setScopeTarget(target);
        const anotherTarget = createTarget();
        consoleView.markAsRoot();
        renderElementIntoDOM(consoleView);
        const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
        assert.exists(consoleModel);
        consoleModel.addMessage(createConsoleMessage(target, 'message 1'));
        consoleModel.addMessage(createConsoleMessage(target, 'message 2'));
        const anotherConsoleModel = anotherTarget.model(SDK.ConsoleModel.ConsoleModel);
        assert.exists(anotherConsoleModel);
        anotherConsoleModel.addMessage(createConsoleMessage(anotherTarget, 'message 3'));
        assert.deepEqual(await getConsoleMessages(), ['message 1', 'message 2']);
        SDK.TargetManager.TargetManager.instance().setScopeTarget(anotherTarget);
        assert.deepEqual(await getConsoleMessages(), preserveLog ? ['message 1', 'message 2', 'message 3'] : ['message 3']);
        Common.Settings.Settings.instance().moduleSetting('preserve-console-log').set(false);
    };
    it('replaces messages when switching scope with preserve log off', handlesSwitchingScope(false));
    it('appends messages when switching scope with preserve log on', handlesSwitchingScope(true));
    describe('self-XSS warning', () => {
        let target;
        beforeEach(() => {
            target = createTarget();
            SDK.TargetManager.TargetManager.instance().setScopeTarget(target);
            consoleView.markAsRoot();
            renderElementIntoDOM(consoleView);
        });
        it('shows', async () => {
            const dt = new DataTransfer();
            dt.setData('text/plain', 'foo');
            const messagesElement = consoleView.element.querySelector('#console-messages');
            assert.instanceOf(messagesElement, HTMLElement);
            dispatchPasteEvent(messagesElement, { clipboardData: dt, bubbles: true });
            assert.strictEqual(Common.Console.Console.instance().messages()[0].text, 'Warning: Don’t paste code into the DevTools Console that you don’t understand or haven’t reviewed yourself. This could allow attackers to steal your identity or take control of your computer. Please type “allow pasting” below and press Enter to allow pasting.');
        });
        it('is turned off when console history reaches a length of 5', async () => {
            const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
            assert.exists(consoleModel);
            const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
            assert.exists(runtimeModel);
            SDK.ConsoleModel.ConsoleModel.requestClearMessages();
            const selfXssWarningDisabledSetting = Common.Settings.Settings.instance().createSetting('disable-self-xss-warning', false, "Synced" /* Common.Settings.SettingStorageType.SYNCED */);
            for (let i = 0; i < 5; i++) {
                assert.isFalse(selfXssWarningDisabledSetting.get());
                consoleModel.dispatchEventToListeners(SDK.ConsoleModel.Events.MessageAdded, createConsoleMessage(target, String(i), SDK.ConsoleModel.FrontendMessageType.Command));
            }
            assert.isTrue(selfXssWarningDisabledSetting.get());
        });
        it('is not shown when disabled via command line', () => {
            const stub = sinon.stub(Root.Runtime.Runtime, 'queryParam');
            stub.withArgs('disableSelfXssWarnings').returns('true');
            const dt = new DataTransfer();
            dt.setData('text/plain', 'foo');
            const messagesElement = consoleView.element.querySelector('#console-messages');
            assert.instanceOf(messagesElement, HTMLElement);
            dispatchPasteEvent(messagesElement, { clipboardData: dt, bubbles: true });
            assert.lengthOf(Common.Console.Console.instance().messages(), 0);
            stub.restore();
        });
    });
    it('appends commands to the history right away', async () => {
        const target = createTarget();
        SDK.TargetManager.TargetManager.instance().setScopeTarget(target);
        consoleView.markAsRoot();
        renderElementIntoDOM(consoleView);
        const consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
        assert.exists(consoleModel);
        const consoleHistorySetting = Common.Settings.Settings.instance().createLocalSetting('console-history', []);
        consoleModel.dispatchEventToListeners(SDK.ConsoleModel.Events.MessageAdded, createConsoleMessage(target, 'await new Promise(() => ())', SDK.ConsoleModel.FrontendMessageType.Command));
        assert.deepEqual(consoleHistorySetting.get(), ['await new Promise(() => ())']);
    });
    it('keeps updating the issue counter when re-attached after detaching', async () => {
        consoleView.markAsRoot();
        const spy = sinon.spy(consoleView, 'issuesCountUpdatedForTest');
        const issuesManager = IssuesManager.IssuesManager.IssuesManager.instance();
        issuesManager.dispatchEventToListeners("IssuesCountUpdated" /* IssuesManager.IssuesManager.Events.ISSUES_COUNT_UPDATED */);
        sinon.assert.calledOnce(spy);
        // Pauses updating the issue counter
        consoleView.onDetach();
        issuesManager.dispatchEventToListeners("IssuesCountUpdated" /* IssuesManager.IssuesManager.Events.ISSUES_COUNT_UPDATED */);
        sinon.assert.calledOnce(spy);
        // Continues updating the issue counter
        renderElementIntoDOM(consoleView);
        issuesManager.dispatchEventToListeners("IssuesCountUpdated" /* IssuesManager.IssuesManager.Events.ISSUES_COUNT_UPDATED */);
        sinon.assert.calledTwice(spy);
    });
    describe('ai code completion provider callbacks', () => {
        beforeEach(async () => {
            updateHostConfig({
                devToolsAiCodeCompletion: {
                    enabled: true,
                },
                aidaAvailability: {
                    enabled: true,
                    blockedByAge: false,
                    blockedByGeo: false,
                },
            });
            const aiCodeCompletionProviderStub = sinon.createStubInstance(TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider);
            aiCodeCompletionProviderStub.extension.returns([]);
            sinon.stub(TextEditor.AiCodeCompletionProvider.AiCodeCompletionProvider, 'createInstance')
                .returns(aiCodeCompletionProviderStub);
            sinon.stub(Host.AidaClient.HostConfigTracker, 'instance').returns({
                addEventListener: () => { },
                removeEventListener: () => { },
                dispose: () => { },
            });
            Common.Settings.Settings.instance().createSetting('ai-code-completion-enabled', true);
            consoleView = Console.ConsoleView.ConsoleView.instance({ forceNew: true, viewportThrottlerTimeout: 0 });
        });
        it('initializes toolbar when the feature is enabled', async () => {
            const providerConfig = consoleView.aiCodeCompletionConfig;
            assert.exists(providerConfig);
            providerConfig.onFeatureEnabled();
            assert.exists(consoleView.element.querySelector('div.ai-code-completion-summary-toolbar-container'));
        });
        it('cleans up toolbar when the feature is disabled', async () => {
            const providerConfig = consoleView.aiCodeCompletionConfig;
            assert.exists(providerConfig);
            providerConfig.onFeatureEnabled();
            assert.exists(consoleView.element.querySelector('div.ai-code-completion-summary-toolbar-container'));
            providerConfig.onFeatureDisabled();
            assert.notExists(consoleView.element.querySelector('div.ai-code-completion-summary-toolbar-container'));
        });
        it('shows a loading state when a request is triggered', async () => {
            const setLoadingSpy = sinon.stub(AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'setLoading');
            const providerConfig = consoleView.aiCodeCompletionConfig;
            assert.exists(providerConfig);
            providerConfig.onFeatureEnabled();
            providerConfig.onRequestTriggered();
            sinon.assert.calledOnce(setLoadingSpy);
            assert.isTrue(setLoadingSpy.firstCall.args[0]);
        });
        it('hides the loading indicator when a response is received', async () => {
            const setLoadingSpy = sinon.stub(AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'setLoading');
            const providerConfig = consoleView.aiCodeCompletionConfig;
            assert.exists(providerConfig);
            providerConfig.onFeatureEnabled();
            providerConfig.onRequestTriggered();
            sinon.assert.calledOnce(setLoadingSpy);
            assert.isTrue(setLoadingSpy.firstCall.args[0]);
            providerConfig.onResponseReceived();
            sinon.assert.calledTwice(setLoadingSpy);
            assert.isFalse(setLoadingSpy.secondCall.args[0]);
        });
        it('attaches the citations toolbar when a suggestion with citations is accepted', async () => {
            const updateCitationsSpy = sinon.spy(AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'updateCitations');
            const providerConfig = consoleView.aiCodeCompletionConfig;
            assert.exists(providerConfig);
            providerConfig.onFeatureEnabled();
            providerConfig.onResponseReceived();
            providerConfig.onSuggestionAccepted([{ uri: 'https://example.com/source' }]);
            sinon.assert.calledOnce(updateCitationsSpy);
            assert.deepEqual(updateCitationsSpy.firstCall.args, [['https://example.com/source']]);
        });
        it('does not attach the citations toolbar if there are no citations', async () => {
            const updateCitationsSpy = sinon.spy(AiCodeCompletionSummaryToolbar.AiCodeCompletionSummaryToolbar.prototype, 'updateCitations');
            const providerConfig = consoleView.aiCodeCompletionConfig;
            assert.exists(providerConfig);
            providerConfig.onFeatureEnabled();
            providerConfig.onResponseReceived();
            providerConfig.onSuggestionAccepted([]);
            sinon.assert.notCalled(updateCitationsSpy);
        });
    });
    describe('group visibility', () => {
        let target;
        let consoleModel;
        let messageTimestamp = 0;
        beforeEach(() => {
            target = createTarget();
            SDK.TargetManager.TargetManager.instance().setScopeTarget(target);
            consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
            assert.exists(consoleModel);
            messageTimestamp = 0;
            Common.Settings.Settings.instance().createSetting('console-group-similar', true).set(true);
        });
        afterEach(() => {
            if (target) {
                target.dispose('test cleanup');
            }
        });
        for (const level of ["error" /* Protocol.Log.LogEntryLevel.Error */,
            "warning" /* Protocol.Log.LogEntryLevel.Warning */,
            "info" /* Protocol.Log.LogEntryLevel.Info */,
            "verbose" /* Protocol.Log.LogEntryLevel.Verbose */,
        ]) {
            it(`shows collapsed group but not message when filtering for ${level}`, async () => {
                const levels = Console.ConsoleFilter.ConsoleFilter.singleLevelMask(level);
                // Setting might exist, .set() is crucial
                Common.Settings.Settings.instance().createSetting('message-level-filters', levels).set(levels);
                consoleView.markAsRoot();
                renderElementIntoDOM(consoleView);
                addMessage(consoleModel, target, 'group', "startGroupCollapsed" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroupCollapsed */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
                addMessage(consoleModel, target, 'message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, level, ++messageTimestamp);
                addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
                const messages = await getConsoleMessages();
                assert.include(messages, 'group');
                assert.notInclude(messages, 'message');
            });
            it(`shows expanded group and message when filtering for ${level}`, async () => {
                const levels = Console.ConsoleFilter.ConsoleFilter.singleLevelMask(level);
                // Setting might exist, .set() is crucial
                Common.Settings.Settings.instance().createSetting('message-level-filters', levels).set(levels);
                consoleView.markAsRoot();
                renderElementIntoDOM(consoleView);
                addMessage(consoleModel, target, 'group', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
                addMessage(consoleModel, target, 'message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, level, ++messageTimestamp);
                addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
                const messages = await getConsoleMessages();
                assert.include(messages, 'group');
                assert.include(messages, 'message');
            });
        }
        it(`does not show group when filtering for level it does not contain`, async () => {
            const levels = Console.ConsoleFilter.ConsoleFilter.singleLevelMask("warning" /* Protocol.Log.LogEntryLevel.Warning */);
            // Setting might exist, .set() is crucial
            Common.Settings.Settings.instance().createSetting('message-level-filters', levels).set(levels);
            consoleView.markAsRoot();
            renderElementIntoDOM(consoleView);
            addMessage(consoleModel, target, 'group', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            addMessage(consoleModel, target, 'message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "error" /* Protocol.Log.LogEntryLevel.Error */, ++messageTimestamp);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            const messages = await getConsoleMessages();
            assert.notInclude(messages, 'group');
            assert.notInclude(messages, 'message');
        });
        it('hides nested groups when parent is collapsed', async () => {
            consoleView.markAsRoot();
            renderElementIntoDOM(consoleView);
            /* Emulate the following scenario pasted in the console:
               console.group('A')
               console.group('B')
               console.log('C')
               console.groupEnd() // B
               console.groupEnd() // A
            */
            addMessage(consoleModel, target, 'A', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            addMessage(consoleModel, target, 'B', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            addMessage(consoleModel, target, 'C', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */, ++messageTimestamp);
            let messages = await getConsoleMessages();
            assert.include(messages, 'A', 'A should be visible');
            assert.include(messages, 'B', 'B should be visible');
            assert.include(messages, 'C', 'C should be visible');
            // Find the group A message view and collapse it
            const viewMessageA = consoleView.itemElement(0);
            viewMessageA.setCollapsed(true);
            await consoleView.getScheduledRefreshPromiseForTest();
            messages = await getConsoleMessages();
            assert.include(messages, 'A', 'A should be visible after collapsing');
            assert.notInclude(messages, 'B', 'B should be hidden after collapsing parent A');
            assert.notInclude(messages, 'C', 'C should be hidden after collapsing parent A');
        });
    });
    describe('collapse all and expand all', () => {
        let target;
        let consoleModel;
        beforeEach(() => {
            target = createTarget();
            SDK.TargetManager.TargetManager.instance().setScopeTarget(target);
            consoleModel = target.model(SDK.ConsoleModel.ConsoleModel);
            assert.exists(consoleModel);
            consoleView.markAsRoot();
            renderElementIntoDOM(consoleView);
        });
        it('collapseAll collapses expanded groups', async () => {
            addMessage(consoleModel, target, 'group 1', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'inner message 1', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'group 2', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'inner message 2', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            await consoleView.getScheduledRefreshPromiseForTest();
            // Both groups are expanded by default, so all group headers and messages are visible.
            assert.strictEqual(consoleView.itemCount(), 4);
            consoleView.collapseAll();
            // After collapsing, only the two group headers should be visible.
            assert.strictEqual(consoleView.itemCount(), 2);
        });
        it('expandAll expands collapsed groups', async () => {
            addMessage(consoleModel, target, 'group 1', "startGroupCollapsed" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroupCollapsed */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'inner message 1', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'group 2', "startGroupCollapsed" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroupCollapsed */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'inner message 2', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            await consoleView.getScheduledRefreshPromiseForTest();
            // Both groups are collapsed by default, so only group headers are visible.
            assert.strictEqual(consoleView.itemCount(), 2);
            consoleView.expandAll();
            // After expanding, all group headers and messages are visible.
            assert.strictEqual(consoleView.itemCount(), 4);
        });
        it('collapseAll then expandAll round-trips groups correctly', async () => {
            addMessage(consoleModel, target, 'expanded group', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'expanded message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'collapsed group', "startGroupCollapsed" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroupCollapsed */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'collapsed message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            await consoleView.getScheduledRefreshPromiseForTest();
            // Mixed state: one expanded group and one collapsed group.
            assert.strictEqual(consoleView.itemCount(), 3);
            consoleView.collapseAll();
            assert.strictEqual(consoleView.itemCount(), 2);
            consoleView.expandAll();
            assert.strictEqual(consoleView.itemCount(), 4);
        });
        it('collapseAll collapses nested groups', async () => {
            addMessage(consoleModel, target, 'outer', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'inner', "startGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'deep message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            await consoleView.getScheduledRefreshPromiseForTest();
            // All three are visible: outer, inner, deep message.
            assert.strictEqual(consoleView.itemCount(), 3);
            consoleView.collapseAll();
            // After collapse, only outer is visible.
            assert.strictEqual(consoleView.itemCount(), 1);
        });
        it('expandAll expands nested groups', async () => {
            addMessage(consoleModel, target, 'outer', "startGroupCollapsed" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroupCollapsed */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'inner', "startGroupCollapsed" /* Protocol.Runtime.ConsoleAPICalledEventType.StartGroupCollapsed */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, 'deep message', "log" /* Protocol.Runtime.ConsoleAPICalledEventType.Log */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            addMessage(consoleModel, target, '', "endGroup" /* Protocol.Runtime.ConsoleAPICalledEventType.EndGroup */, "info" /* Protocol.Log.LogEntryLevel.Info */);
            await consoleView.getScheduledRefreshPromiseForTest();
            // All collapsed, only outer visible.
            assert.strictEqual(consoleView.itemCount(), 1);
            consoleView.expandAll();
            // After expanding, all three are visible.
            assert.strictEqual(consoleView.itemCount(), 3);
        });
    });
});
//# sourceMappingURL=ConsoleView.test.js.map