blob: bb6bdfbef2e80e94d009147df5dd55bb73e9f9f6 [file]
// Copyright 2024 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 i18n from '../../core/i18n/i18n.js';
import {
raf,
renderElementIntoDOM,
} from '../../testing/DOMHelpers.js';
import {deinitializeGlobalVars, initializeGlobalVars, setupActionRegistry} from '../../testing/EnvironmentHelpers.js';
import {expectCalled} from '../../testing/ExpectStubCall.js';
import {createViewFunctionStub, type ViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Application from './application.js';
describe('KeyValueStorageItemsView', () => {
before(async () => {
await initializeGlobalVars();
});
after(async () => {
await deinitializeGlobalVars();
});
beforeEach(() => {
UI.ActionRegistration.maybeRemoveActionExtension('ai-assistance.storage-floating-button');
UI.ActionRegistration.registerActionExtension({
actionId: 'ai-assistance.storage-floating-button',
category: UI.ActionRegistration.ActionCategory.GLOBAL,
title: i18n.i18n.lockedLazyString('Ask AI'),
});
});
setupActionRegistry();
let keyValueStorageItemsView: TestKeyValueStorageItemsView;
let viewFunction: ViewFunctionStub<typeof Application.KeyValueStorageItemsView.KeyValueStorageItemsView>;
const MOCK_ITEMS = [
{key: 'foo', value: 'value1'},
{key: 'bar', value: 'value2'},
];
const createPreviewFunc = sinon.stub<[string, string]>();
const expectCreatePreviewCalled = async (expectedKey: string, expectedValue: string) => {
createPreviewFunc.resetHistory();
await expectCalled(createPreviewFunc, {
fakeFn: async (key: string, value: string) => {
assert.strictEqual(key, expectedKey);
assert.strictEqual(value, expectedValue);
return new UI.EmptyWidget.EmptyWidget(`${key}:${value}`, '');
},
});
};
class TestKeyValueStorageItemsView extends Application.KeyValueStorageItemsView.KeyValueStorageItemsView {
override setItem(_key: string, _value: string): void {
// Do nothing.
}
override removeItem(_key: string): void {
// Do nothing.
}
override createPreview(key: string, value: string): Promise<UI.Widget.Widget|null> {
return createPreviewFunc(key, value);
}
override isAiButtonEnabled(): boolean {
return UI.ActionRegistry.ActionRegistry.instance().hasAction('ai-assistance.storage-floating-button');
}
override populateContextMenu(item: {key: string, value: string}, contextMenu: UI.ContextMenu.ContextMenu): void {
super.populateContextMenu(item, contextMenu);
}
override onAiButtonClick(item: {key: string, value: string}, event: Event): void {
super.onAiButtonClick(item, event);
}
}
beforeEach(() => {
const container = new UI.Widget.VBox();
const div = document.createElement('div');
renderElementIntoDOM(div);
container.markAsRoot();
container.show(div);
viewFunction = createViewFunctionStub(Application.KeyValueStorageItemsView.KeyValueStorageItemsView);
keyValueStorageItemsView =
new TestKeyValueStorageItemsView('Items', 'key-value-storage-items-view', true, viewFunction);
keyValueStorageItemsView.showItems(MOCK_ITEMS);
});
afterEach(() => {
keyValueStorageItemsView.detach();
});
it('updates preview when key selected', async () => {
const {key, value} = MOCK_ITEMS[0];
const createPreviewPromise = expectCreatePreviewCalled(key, value);
// Select the first item by key.
viewFunction.input.onSelect({key, value});
// Check createPreview function was called.
await createPreviewPromise;
assert.include(viewFunction.input.preview.element.innerText, `${key}:${value}`);
});
it('shows empty preview when no row is selected', async () => {
viewFunction.input.onSelect(MOCK_ITEMS[0]);
await raf();
viewFunction.input.onSelect(null);
// Check preview was updated.
assert.include(viewFunction.input.preview.element.innerText, 'No value selectedSelect a value to preview');
});
it('preview changed when value changes', async () => {
const {key, value} = MOCK_ITEMS[0];
let createPreviewPromise = expectCreatePreviewCalled(key, value);
// Select the first item.
viewFunction.input.onSelect({key, value});
// Check createPreview function was called.
await createPreviewPromise;
// Update the item data (in reality, this would happen since a user edit
// would trigger the refreshItems callback - which would fetch new data and
// call showItems again).
const updatedItems = structuredClone(MOCK_ITEMS);
updatedItems[0].value = 'newValue';
createPreviewPromise = expectCreatePreviewCalled(key, 'newValue');
keyValueStorageItemsView.showItems(updatedItems);
// Check createPreview function was called.
await createPreviewPromise;
// Check preview was updated.
await raf();
assert.include(viewFunction.input.preview.element.innerText, `${key}:newValue`);
});
it('clicking Ask AI button calls onAiButtonClick', () => {
const onAiButtonClickSpy = sinon.spy(keyValueStorageItemsView, 'onAiButtonClick');
keyValueStorageItemsView.performUpdate();
const dummyEvent = new Event('click');
viewFunction.input.onAiButtonClick?.(MOCK_ITEMS[0], dummyEvent);
sinon.assert.calledOnceWithExactly(onAiButtonClickSpy, MOCK_ITEMS[0], dummyEvent);
});
it('right clicking calls populateContextMenu', () => {
const populateContextMenuSpy = sinon.spy(keyValueStorageItemsView, 'populateContextMenu');
keyValueStorageItemsView.performUpdate();
const dummyEvent = new Event('contextmenu');
const contextMenu = new UI.ContextMenu.ContextMenu(dummyEvent);
viewFunction.input.onContextMenu?.(MOCK_ITEMS[0], contextMenu);
sinon.assert.calledOnceWithExactly(populateContextMenuSpy, MOCK_ITEMS[0], contextMenu);
});
});