blob: 57c29d0f1c0ec5ebaff29e4804bc66d10e1028d4 [file] [log] [blame]
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANYf KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {assertDefined} from 'common/assert_utils';
import {InMemoryStorage} from 'common/store/in_memory_storage';
import {TimestampConverterUtils} from 'common/time/test_utils';
import {
TabbedViewSwitchRequest,
TracePositionUpdate,
} from 'messaging/winscope_event';
import {Transform} from 'parsers/surface_flinger/transform_utils';
import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
import {TracesBuilder} from 'test/unit/traces_builder';
import {TraceBuilder} from 'test/unit/trace_builder';
import {UnitTestUtils} from 'test/unit/utils';
import {CustomQueryType} from 'trace/custom_query';
import {Parser} from 'trace/parser';
import {Trace} from 'trace/trace';
import {Traces} from 'trace/traces';
import {TraceRectBuilder} from 'trace/trace_rect_builder';
import {TraceType} from 'trace/trace_type';
import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node';
import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
import {NotifyLogViewCallbackType} from 'viewers/common/abstract_log_viewer_presenter';
import {AbstractLogViewerPresenterTest} from 'viewers/common/abstract_log_viewer_presenter_test';
import {VISIBLE_CHIP} from 'viewers/common/chip';
import {LogSelectFilter} from 'viewers/common/log_filters';
import {TextFilter} from 'viewers/common/text_filter';
import {LogField, LogHeader} from 'viewers/common/ui_data_log';
import {UserOptions} from 'viewers/common/user_options';
import {ViewerEvents} from 'viewers/common/viewer_events';
import {Presenter} from './presenter';
import {UiData} from './ui_data';
class PresenterInputTest extends AbstractLogViewerPresenterTest<UiData> {
override readonly expectedHeaders = [
{
header: new LogHeader(
{
name: 'Type',
cssClass: 'input-type inline',
},
new LogSelectFilter(['MOTION', 'KEY'], false, '80', '100%'),
),
},
{
header: new LogHeader(
{
name: 'Source',
cssClass: 'input-source',
},
new LogSelectFilter(['TOUCHSCREEN', 'KEYBOARD'], false, '200', '100%'),
),
},
{
header: new LogHeader(
{
name: 'Action',
cssClass: 'input-action',
},
new LogSelectFilter(
['DOWN', 'OUTSIDE', 'MOVE', 'UP'],
false,
'100',
'100%',
),
),
},
{
header: new LogHeader(
{
name: 'Device',
cssClass: 'input-device-id right-align',
},
new LogSelectFilter(['4', '2'], false, '80', '100%'),
),
},
{
header: new LogHeader(
{
name: 'Display',
cssClass: 'input-display-id right-align',
},
new LogSelectFilter(['0', '-1'], false, '80', '100%'),
),
},
{
header: new LogHeader({
name: 'Details',
cssClass: 'input-details',
}),
},
{
header: new LogHeader(
{
name: 'Target Windows',
cssClass: 'input-windows',
},
new LogSelectFilter(
Array.from({length: 6}, () => ''),
true,
'100',
'100%',
),
),
options: [
this.wrappedName('win-212'),
this.wrappedName('win-64'),
this.wrappedName('win-82'),
this.wrappedName('win-75'),
this.wrappedName('win-zero-not-98'),
this.wrappedName('98'),
],
},
];
private trace: Trace<PropertyTreeNode> | undefined;
private surfaceFlingerTrace: Trace<HierarchyTreeNode> | undefined;
private positionUpdate: TracePositionUpdate | undefined;
private layerIdToName: Array<{id: number; name: string}> = [
{id: 0, name: 'win-zero-not-98'},
{id: 212, name: 'win-212'},
{id: 64, name: 'win-64'},
{id: 82, name: 'win-82'},
{id: 75, name: 'win-75'},
// The layer name for window with id 98 is omitted to test incomplete mapping.
];
override async setUpTestEnvironment(): Promise<void> {
const parser = (await UnitTestUtils.getTracesParser([
'traces/perfetto/input-events.perfetto-trace',
])) as Parser<PropertyTreeNode>;
this.trace = new TraceBuilder<PropertyTreeNode>()
.setType(TraceType.INPUT_EVENT_MERGED)
.setParser(parser)
.build();
this.surfaceFlingerTrace = new TraceBuilder<HierarchyTreeNode>()
.setType(TraceType.SURFACE_FLINGER)
.setEntries([])
.setParserCustomQueryResult(
CustomQueryType.SF_LAYERS_ID_AND_NAME,
this.layerIdToName,
)
.build();
this.positionUpdate = TracePositionUpdate.fromTraceEntry(
this.trace.getEntry(0),
);
}
override async createPresenterWithEmptyTrace(
callback: NotifyLogViewCallbackType<UiData>,
): Promise<Presenter> {
const traces = new TracesBuilder()
.setEntries(TraceType.INPUT_EVENT_MERGED, [])
.build();
if (this.surfaceFlingerTrace !== undefined) {
traces.addTrace(this.surfaceFlingerTrace);
}
return PresenterInputTest.createPresenterWithTraces(traces, callback);
}
override async createPresenter(
callback: NotifyLogViewCallbackType<UiData>,
): Promise<Presenter> {
const traces = new Traces();
traces.addTrace(assertDefined(this.trace));
if (this.surfaceFlingerTrace !== undefined) {
traces.addTrace(this.surfaceFlingerTrace);
}
const presenter = PresenterInputTest.createPresenterWithTraces(
traces,
callback,
);
await presenter.onAppEvent(this.getPositionUpdate()); // trigger initialization
return presenter;
}
private static createPresenterWithTraces(
traces: Traces,
callback: NotifyLogViewCallbackType<UiData>,
): Presenter {
return new Presenter(
traces,
assertDefined(traces.getTrace(TraceType.INPUT_EVENT_MERGED)),
new InMemoryStorage(),
callback,
);
}
override getPositionUpdate(): TracePositionUpdate {
return assertDefined(this.positionUpdate);
}
override executePropertiesChecksForEmptyTrace(uiData: UiData) {
expect(uiData.highlightedProperty).toBeFalsy();
expect(uiData.dispatchPropertiesTree).toBeUndefined();
expect(uiData.dispatchPropertiesFilter).toBeDefined();
}
override executePropertiesChecksAfterPositionUpdate(uiData: UiData): void {
expect(uiData.entries.length).toEqual(8);
expect(uiData.currentIndex).toEqual(0);
expect(uiData.selectedIndex).toBeUndefined();
const curEntry = uiData.entries[0];
const expectedFields: LogField[] = [
{
spec: uiData.headers[0].spec,
value: 'MOTION',
propagateEntryTimestamp: true,
},
{spec: uiData.headers[1].spec, value: 'TOUCHSCREEN'},
{spec: uiData.headers[2].spec, value: 'DOWN'},
{spec: uiData.headers[3].spec, value: 4},
{spec: uiData.headers[4].spec, value: 0},
{spec: uiData.headers[5].spec, value: '[212, 64, 82, 75]'},
{
spec: uiData.headers[6].spec,
value: [
this.wrappedName('win-212'),
this.wrappedName('win-64'),
this.wrappedName('win-82'),
this.wrappedName('win-75'),
this.wrappedName('win-zero-not-98'),
].join(', '),
},
];
expectedFields.forEach((field) => {
expect(curEntry.fields).toContain(field);
});
const motionEvent = assertDefined(uiData.propertiesTree);
expect(motionEvent.getChildByName('eventId')?.getValue()).toEqual(
330184796,
);
expect(motionEvent.getChildByName('action')?.formattedValue()).toEqual(
'ACTION_DOWN',
);
const dispatchProperties = assertDefined(uiData.dispatchPropertiesTree);
expect(dispatchProperties.getAllChildren().length).toEqual(5);
expect(
dispatchProperties
.getChildByName('0')
?.getDisplayName(),
).toEqual('win-212');
}
private expectEventPresented(
uiData: UiData,
eventId: number,
action: string,
) {
const properties = assertDefined(uiData.propertiesTree);
expect(properties.getChildByName('action')?.formattedValue()).toEqual(
action,
);
expect(properties.getChildByName('eventId')?.getValue()).toEqual(eventId);
}
override executeSpecializedTests() {
describe('Specialized tests', () => {
const time0 = TimestampConverterUtils.makeRealTimestamp(0n);
const time10 = TimestampConverterUtils.makeRealTimestamp(10n);
const time19 = TimestampConverterUtils.makeRealTimestamp(19n);
const time20 = TimestampConverterUtils.makeRealTimestamp(20n);
const time25 = TimestampConverterUtils.makeRealTimestamp(25n);
const time30 = TimestampConverterUtils.makeRealTimestamp(30n);
const time35 = TimestampConverterUtils.makeRealTimestamp(35n);
const time36 = TimestampConverterUtils.makeRealTimestamp(36n);
const layerRect = new TraceRectBuilder()
.setX(0)
.setY(0)
.setWidth(1)
.setHeight(1)
.setId('layerRect')
.setName('layerRect')
.setCornerRadius(0)
.setTransform(Transform.EMPTY.matrix)
.setDepth(1)
.setGroupId(0)
.setIsVisible(true)
.setOpacity(1)
.setIsDisplay(false)
.setIsSpy(false)
.build();
const inputRect = new TraceRectBuilder()
.setX(2)
.setY(2)
.setWidth(3)
.setHeight(3)
.setId('inputRect')
.setName('inputRect')
.setCornerRadius(0)
.setTransform(Transform.EMPTY.matrix)
.setDepth(1)
.setGroupId(0)
.setIsVisible(true)
.setOpacity(1)
.setIsDisplay(false)
.setIsSpy(false)
.build();
const sfEntry0 = new HierarchyTreeBuilder()
.setId('LayerTraceEntry')
.setName('root')
.setChildren([
{
id: 1,
name: 'layer1',
rects: [layerRect],
secondaryRects: [inputRect],
},
])
.build();
const sfEntry1 = new HierarchyTreeBuilder()
.setId('LayerTraceEntry')
.setName('root')
.setChildren([
{
id: 1,
name: 'layer1',
rects: [layerRect],
secondaryRects: [inputRect],
children: [
{
id: 2,
name: 'layer2',
rects: [layerRect],
secondaryRects: [inputRect],
},
],
},
])
.build();
const sfEntry2 = new HierarchyTreeBuilder()
.setId('LayerTraceEntry')
.setName('root')
.setChildren([
{
id: 1,
name: 'layer1',
rects: [layerRect],
secondaryRects: [inputRect],
children: [
{
id: 2,
name: 'layer2',
rects: [layerRect],
secondaryRects: [inputRect],
},
],
},
{
id: 3,
name: 'layer3',
rects: [layerRect],
secondaryRects: [inputRect],
},
])
.build();
let uiData: UiData = UiData.createEmpty();
beforeEach(async () => {
uiData = UiData.createEmpty();
await this.setUpTestEnvironment();
});
it('adds events listeners', async () => {
const element = document.createElement('div');
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
presenter.addEventListeners(element);
const testId = 'testId';
let spy: jasmine.Spy = spyOn(presenter, 'onHighlightedPropertyChange');
element.dispatchEvent(
new CustomEvent(ViewerEvents.HighlightedPropertyChange, {
detail: {id: testId},
}),
);
expect(spy).toHaveBeenCalledWith(testId);
spy = spyOn(presenter, 'onHighlightedIdChange');
element.dispatchEvent(
new CustomEvent(ViewerEvents.HighlightedIdChange, {
detail: {id: testId},
}),
);
expect(spy).toHaveBeenCalledWith(testId);
spy = spyOn(presenter, 'onRectsUserOptionsChange');
const userOptions = {};
element.dispatchEvent(
new CustomEvent(ViewerEvents.RectsUserOptionsChange, {
detail: {userOptions},
}),
);
expect(spy).toHaveBeenCalledWith(userOptions);
spy = spyOn(presenter, 'onRectDoubleClick');
element.dispatchEvent(new CustomEvent(ViewerEvents.RectsDblClick));
expect(spy).toHaveBeenCalled();
spy = spyOn(presenter, 'onDispatchPropertiesFilterChange');
const filter = new TextFilter();
element.dispatchEvent(
new CustomEvent(ViewerEvents.DispatchPropertiesFilterChange, {
detail: filter,
}),
);
expect(spy).toHaveBeenCalledWith(filter);
});
it('updates selected entry', async () => {
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
const keyEntry = assertDefined(this.trace).getEntry(7);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(keyEntry),
);
this.expectEventPresented(uiData, 894093732, 'ACTION_UP');
const motionEntry = assertDefined(this.trace).getEntry(1);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(motionEntry),
);
this.expectEventPresented(uiData, 1327679296, 'ACTION_OUTSIDE');
const motionDispatchProperties = assertDefined(
uiData.dispatchPropertiesTree,
);
expect(motionDispatchProperties.getAllChildren().length).toEqual(1);
expect(
motionDispatchProperties
.getChildByName('0')
?.getChildByName('windowId')
?.getValue(),
).toEqual(98n);
});
it('finds entry by time', async () => {
const traces = new Traces();
traces.addTrace(assertDefined(this.trace));
const lastMotion = await assertDefined(this.trace).getEntry(5);
const firstKey = await assertDefined(this.trace).getEntry(6);
const diffNs =
firstKey.getTimestamp().getValueNs() -
lastMotion.getTimestamp().getValueNs();
const belowLastMotionTime = lastMotion.getTimestamp().minus(1n);
const midpointTime = lastMotion.getTimestamp().add(diffNs / 2n);
const aboveFirstKeyTime = firstKey.getTimestamp().add(1n);
const otherTrace = new TraceBuilder<string>()
.setType(TraceType.TEST_TRACE_STRING)
.setEntries(['event-log-00', 'event-log-01', 'event-log-02'])
.setTimestamps([belowLastMotionTime, midpointTime, aboveFirstKeyTime])
.build();
traces.addTrace(otherTrace);
const presenter = PresenterInputTest.createPresenterWithTraces(
traces,
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(otherTrace.getEntry(0)),
);
this.expectEventPresented(uiData, 313395000, 'ACTION_MOVE');
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(otherTrace.getEntry(1)),
);
this.expectEventPresented(uiData, 436499943, 'ACTION_UP');
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(otherTrace.getEntry(2)),
);
this.expectEventPresented(uiData, 759309047, 'ACTION_DOWN');
});
it('finds closest input event by frame', async () => {
const parser = assertDefined(this.trace).getParser();
const traces = new Traces();
// FRAME: 0 1 2
// TEST(time): 0 19 35
// INPUT(time): 10 20,25 30,36
const trace = new TraceBuilder<PropertyTreeNode>()
.setType(TraceType.INPUT_EVENT_MERGED)
.setEntries([
await parser.getEntry(0),
await parser.getEntry(1),
await parser.getEntry(2),
await parser.getEntry(3),
await parser.getEntry(4),
])
.setTimestamps([time10, time20, time25, time30, time36])
.setFrame(0, 0)
.setFrame(1, 1)
.setFrame(2, 1)
.setFrame(3, 2)
.setFrame(4, 2)
.build();
traces.addTrace(trace);
const otherTrace = new TraceBuilder<string>()
.setType(TraceType.TEST_TRACE_STRING)
.setEntries(['sf-event-00', 'sf-event-01', 'sf-event-02'])
.setTimestamps([time0, time19, time35])
.setFrame(0, 0)
.setFrame(1, 1)
.setFrame(2, 2)
.build();
traces.addTrace(otherTrace);
const presenter = PresenterInputTest.createPresenterWithTraces(
traces,
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(otherTrace.getEntry(0)),
);
this.expectEventPresented(uiData, 330184796, 'ACTION_DOWN');
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(otherTrace.getEntry(1)),
);
this.expectEventPresented(uiData, 1327679296, 'ACTION_OUTSIDE');
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(otherTrace.getEntry(2)),
);
this.expectEventPresented(uiData, 106022695, 'ACTION_MOVE');
});
it('no rects defined without SF trace', async () => {
this.surfaceFlingerTrace = undefined;
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(this.getPositionUpdate());
expect(uiData.rectsToDraw).toBeUndefined();
});
it('empty trace no rects defined without SF trace', async () => {
this.surfaceFlingerTrace = undefined;
const presenter = await this.createPresenterWithEmptyTrace(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(this.getPositionUpdate());
expect(uiData.rectsToDraw).toBeUndefined();
});
it('rects defined with SF trace', async () => {
assertDefined(this.surfaceFlingerTrace);
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(this.getPositionUpdate());
expect(uiData.rectsToDraw).toBeDefined();
expect(uiData.rectsToDraw).toEqual([]);
});
it('empty trace rects defined with SF trace', async () => {
assertDefined(this.surfaceFlingerTrace);
const presenter = await this.createPresenterWithEmptyTrace(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(this.getPositionUpdate());
expect(uiData.rectsToDraw).toBeDefined();
expect(uiData.rectsToDraw).toEqual([]);
});
it('extracts corresponding input rects from SF trace', async () => {
const parser = assertDefined(this.trace).getParser();
const traces = await getTracesWithSf(parser, this.layerIdToName);
const trace = assertDefined(
traces.getTrace(TraceType.INPUT_EVENT_MERGED),
);
const presenter = PresenterInputTest.createPresenterWithTraces(
traces,
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(trace.getEntry(0)),
);
expect(uiData.rectsToDraw).toEqual([]);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(trace.getEntry(1)),
);
expect(uiData.rectsToDraw).toHaveSize(1);
expect(uiData.rectsToDraw?.at(0)?.id).toEqual('inputRect');
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(trace.getEntry(2)),
);
expect(uiData.rectsToDraw).toHaveSize(1);
expect(uiData.rectsToDraw?.at(0)?.id).toEqual('inputRect');
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(trace.getEntry(3)),
);
expect(uiData.rectsToDraw).toHaveSize(3);
uiData.rectsToDraw?.forEach((rect) =>
expect(rect.id).toEqual('inputRect'),
);
});
it('filters dispatch properties tree', async () => {
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(this.getPositionUpdate());
await presenter.onLogEntryClick(3);
expect(
assertDefined(uiData.dispatchPropertiesTree).getAllChildren().length,
).toEqual(5);
await presenter.onDispatchPropertiesFilterChange(new TextFilter('212'));
expect(
assertDefined(uiData.dispatchPropertiesTree).getAllChildren().length,
).toEqual(1);
});
it('updates highlighted property', async () => {
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
expect(uiData.highlightedProperty).toEqual('');
const id = '4';
presenter.onHighlightedPropertyChange(id);
expect(uiData.highlightedProperty).toEqual(id);
presenter.onHighlightedPropertyChange(id);
expect(uiData.highlightedProperty).toEqual('');
});
it('updates highlighted rect', async () => {
const parser = assertDefined(this.trace).getParser();
const traces = await getTracesWithSf(parser, this.layerIdToName);
const trace = assertDefined(
traces.getTrace(TraceType.INPUT_EVENT_MERGED),
);
const presenter = PresenterInputTest.createPresenterWithTraces(
traces,
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(trace.getEntry(1)),
);
expect(uiData.rectsToDraw).toHaveSize(1);
const rect = assertDefined(uiData.rectsToDraw)[0];
await presenter.onHighlightedIdChange(rect.id);
expect(uiData.highlightedRect).toEqual(rect.id);
await presenter.onHighlightedIdChange(rect.id);
expect(uiData.highlightedRect).toEqual('');
});
it('filters rects by having content or visibility', async () => {
const userOptions: UserOptions = {
showOnlyVisible: {
name: 'Show only',
chip: VISIBLE_CHIP,
enabled: false,
},
showOnlyWithContent: {
name: 'Has input',
icon: 'pan_tool_alt',
enabled: true,
},
};
const parser = assertDefined(this.trace).getParser();
const traces = await getTracesWithSf(parser, this.layerIdToName);
const trace = assertDefined(
traces.getTrace(TraceType.INPUT_EVENT_MERGED),
);
const presenter = PresenterInputTest.createPresenterWithTraces(
traces,
(uiDataLog) => (uiData = uiDataLog as UiData),
);
await presenter.onAppEvent(
TracePositionUpdate.fromTraceEntry(trace.getEntry(1)),
);
expect(uiData.rectsToDraw).toHaveSize(1);
await presenter.onRectsUserOptionsChange(userOptions);
expect(uiData.rectsUserOptions).toEqual(userOptions);
expect(uiData.rectsToDraw).toHaveSize(0);
userOptions['showOnlyVisible'].enabled = true;
userOptions['showOnlyWithContent'].enabled = false;
await presenter.onRectsUserOptionsChange(userOptions);
expect(uiData.rectsToDraw).toHaveSize(1);
});
it('emits event on rect double click', async () => {
const presenter = await this.createPresenter(
(uiDataLog) => (uiData = uiDataLog as UiData),
);
const spy = jasmine.createSpy();
presenter.setEmitEvent(spy);
await presenter.onRectDoubleClick();
expect(spy).toHaveBeenCalledWith(
new TabbedViewSwitchRequest(assertDefined(this.surfaceFlingerTrace)),
);
});
async function getTracesWithSf(
parser: Parser<PropertyTreeNode>,
layerIdToName: Array<{
id: number;
name: string;
}>,
) {
const traces = new Traces();
// FRAME: 0 1 2 3
// INPUT(index): 0 1,2 - 3
// SF(index): - 0 1 2
const trace = new TraceBuilder<PropertyTreeNode>()
.setType(TraceType.INPUT_EVENT_MERGED)
.setEntries([
await parser.getEntry(0),
await parser.getEntry(1),
await parser.getEntry(2),
await parser.getEntry(3),
])
.setTimestamps([time10, time20, time25, time30])
.setFrame(0, 0)
.setFrame(1, 1)
.setFrame(2, 1)
.setFrame(3, 3)
.build();
traces.addTrace(trace);
const sfTrace = new TraceBuilder<HierarchyTreeNode>()
.setType(TraceType.SURFACE_FLINGER)
.setEntries([sfEntry0, sfEntry1, sfEntry2])
.setTimestamps([time0, time19, time35])
.setFrame(0, 1)
.setFrame(1, 2)
.setFrame(2, 3)
.setParserCustomQueryResult(
CustomQueryType.SF_LAYERS_ID_AND_NAME,
layerIdToName,
)
.build();
traces.addTrace(sfTrace);
return traces;
}
});
}
private wrappedName(name: string): string {
return `\u{200C}${name}\u{200C}`;
}
}
describe('PresenterInput', async () => {
new PresenterInputTest().execute();
});