| // Copyright 2025 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 {afterEach, beforeEach, it} from 'mocha'; | |
| import sinon from 'sinon'; | |
| import * as Common from '../../../core/common/common.js'; | |
| import * as Host from '../../../core/host/host.js'; | |
| import * as Root from '../../../core/root/root.js'; | |
| import {renderElementIntoDOM} from '../../../testing/DOMHelpers.js'; | |
| import {describeWithEnvironment, updateHostConfig} from '../../../testing/EnvironmentHelpers.js'; | |
| import {createViewFunctionStub} from '../../../testing/ViewFunctionHelpers.js'; | |
| import * as UI from '../../legacy/legacy.js'; | |
| import * as Dialogs from '../dialogs/dialogs.js'; | |
| import * as Snackbars from '../snackbars/snackbars.js'; | |
| import * as TextEditor from './text_editor.js'; | |
| const {AiCodeCompletionTeaser} = TextEditor.AiCodeCompletionTeaser; | |
| const {FreDialog} = Dialogs.FreDialog; | |
| describeWithEnvironment('AiCodeCompletionTeaser', () => { | |
| let showFreDialogStub: sinon.SinonStub<Parameters<typeof FreDialog.show>, Promise<boolean>>; | |
| let checkAccessPreconditionsStub: sinon.SinonStub; | |
| beforeEach(() => { | |
| showFreDialogStub = sinon.stub(FreDialog, 'show'); | |
| checkAccessPreconditionsStub = sinon.stub(Host.AidaClient.AidaClient, 'checkAccessPreconditions'); | |
| checkAccessPreconditionsStub.resolves(Host.AidaClient.AidaAccessPreconditions.AVAILABLE); | |
| }); | |
| async function createTeaser() { | |
| const view = createViewFunctionStub(AiCodeCompletionTeaser); | |
| const widget = new AiCodeCompletionTeaser({onDetach: sinon.stub()}, view); | |
| widget.markAsRoot(); | |
| renderElementIntoDOM(widget); | |
| await view.nextInput; | |
| return {view, widget}; | |
| } | |
| afterEach(() => { | |
| Common.Settings.Settings.instance().settingForTest('ai-code-completion-teaser-dismissed').set(false); | |
| Common.Settings.Settings.instance().settingForTest('ai-code-completion-enabled').set(false); | |
| }); | |
| it('should dismiss and open snackbar on dismiss click', async () => { | |
| const {view, widget} = await createTeaser(); | |
| const showSnackbar = sinon.stub(Snackbars.Snackbar.Snackbar, 'show'); | |
| assert.isTrue(widget.isShowing()); | |
| assert.exists(view.input.onDismiss); | |
| view.input.onDismiss(new Event('click')); | |
| await widget.updateComplete; | |
| sinon.assert.calledOnce(showSnackbar); | |
| assert.isTrue(Common.Settings.Settings.instance().settingForTest('ai-code-completion-teaser-dismissed').get()); | |
| assert.isFalse(widget.isShowing()); | |
| }); | |
| it('should open settings on snackbar action click', async () => { | |
| const {view, widget} = await createTeaser(); | |
| const showSnackbar = sinon.stub(Snackbars.Snackbar.Snackbar, 'show'); | |
| const showViewStub = sinon.stub(UI.ViewManager.ViewManager.instance(), 'showView'); | |
| assert.exists(view.input.onDismiss); | |
| view.input.onDismiss(new Event('click')); | |
| sinon.assert.calledOnce(showSnackbar); | |
| const snackbarOptions = showSnackbar.firstCall.args[0]; | |
| assert.exists(snackbarOptions.actionProperties); | |
| snackbarOptions.actionProperties.onClick(); | |
| assert.isTrue(showViewStub.calledOnceWith('chrome-ai')); | |
| widget.detach(); | |
| }); | |
| it('should FRE text include no logging case when the enterprise policy value is ALLOW_WITHOUT_LOGGING', async () => { | |
| updateHostConfig( | |
| {aidaAvailability: {enterprisePolicyValue: Root.Runtime.GenAiEnterprisePolicyValue.ALLOW_WITHOUT_LOGGING}}); | |
| const {view, widget} = await createTeaser(); | |
| view.input.onAction(new Event('')); | |
| sinon.assert.called(showFreDialogStub); | |
| assert.exists(showFreDialogStub.lastCall.args[0].reminderItems.find( | |
| reminderItem => | |
| reminderItem.content.toString().includes('This data won’t be used to improve Google’s AI models.'))); | |
| widget.detach(); | |
| }); | |
| it('should FRE text not include no logging case when the enterprise policy value is ALLOW', async () => { | |
| updateHostConfig({aidaAvailability: {enterprisePolicyValue: Root.Runtime.GenAiEnterprisePolicyValue.ALLOW}}); | |
| const {view, widget} = await createTeaser(); | |
| view.input.onAction(new Event('')); | |
| sinon.assert.called(showFreDialogStub); | |
| assert.notExists(showFreDialogStub.lastCall.args[0].reminderItems.find( | |
| reminderItem => | |
| reminderItem.content.toString().includes('This data won’t be used to improve Google’s AI models.'))); | |
| widget.detach(); | |
| }); | |
| it('renders when AIDA becomes available', async () => { | |
| checkAccessPreconditionsStub.resolves(Host.AidaClient.AidaAccessPreconditions.NO_ACCOUNT_EMAIL); | |
| const {view, widget} = await createTeaser(); | |
| assert.strictEqual(view.input.aidaAvailability, Host.AidaClient.AidaAccessPreconditions.NO_ACCOUNT_EMAIL); | |
| Host.AidaClient.HostConfigTracker.instance().dispatchEventToListeners( | |
| Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, Host.AidaClient.AidaAccessPreconditions.AVAILABLE); | |
| await view.nextInput; | |
| assert.strictEqual(view.input.aidaAvailability, Host.AidaClient.AidaAccessPreconditions.AVAILABLE); | |
| widget.detach(); | |
| }); | |
| it('does not render when AIDA becomes unavailable', async () => { | |
| checkAccessPreconditionsStub.resolves(Host.AidaClient.AidaAccessPreconditions.AVAILABLE); | |
| const {view, widget} = await createTeaser(); | |
| assert.strictEqual(view.input.aidaAvailability, Host.AidaClient.AidaAccessPreconditions.AVAILABLE); | |
| Host.AidaClient.HostConfigTracker.instance().dispatchEventToListeners( | |
| Host.AidaClient.Events.AIDA_AVAILABILITY_CHANGED, Host.AidaClient.AidaAccessPreconditions.NO_ACCOUNT_EMAIL); | |
| await view.nextInput; | |
| assert.strictEqual(view.input.aidaAvailability, Host.AidaClient.AidaAccessPreconditions.NO_ACCOUNT_EMAIL); | |
| widget.detach(); | |
| }); | |
| }); |