Fix log views not fully rendered

Fix: b/255966194
Test: npm run build:all && npm run test:all
Change-Id: Icb5c4c6064c6a2f3967ede5f251b337dc26dfec1
This commit is contained in:
Kean Mariotti
2022-10-28 06:44:20 +00:00
parent 5d96d3c955
commit d39a88a2c1
2 changed files with 86 additions and 77 deletions

View File

@@ -19,6 +19,7 @@ import {ComponentFixture, TestBed} from "@angular/core/testing";
import {MatCardModule} from "@angular/material/card";
import {TraceViewComponent} from "./trace_view.component";
import {View, Viewer, ViewType} from "viewers/viewer";
import {content} from "html2canvas/dist/types/css/property-descriptors/content";
class FakeViewer implements Viewer {
constructor(title: string, content: string) {
@@ -74,49 +75,53 @@ describe("TraceViewComponent", () => {
});
it("creates viewer tabs", () => {
const tabs: NodeList = htmlElement.querySelectorAll(".viewer-tab");
const tabs: NodeList = htmlElement.querySelectorAll(".tab");
expect(tabs.length).toEqual(2);
expect(tabs.item(0)!.textContent).toEqual("Title0");
expect(tabs.item(1)!.textContent).toEqual("Title1");
});
it("changes active viewer on click", () => {
const tabs = htmlElement.querySelectorAll(".viewer-tab");
const tabsContent =
htmlElement.querySelectorAll(".trace-view-content div");
it("changes active view on click", () => {
const getVisibleTabContents = () => {
const contents: HTMLElement[] = [];
htmlElement
.querySelectorAll(".trace-view-content div")
.forEach(content => {
if ((content as HTMLElement).style.display != "none") {
contents.push(content as HTMLElement);
}
});
return contents;
};
const tabButtons = htmlElement.querySelectorAll(".tab");
// Initially tab 0
fixture.detectChanges();
expect(tabsContent.length).toEqual(2);
expect(tabsContent[0].innerHTML).toEqual("Content0");
expect(tabsContent[1].innerHTML).toEqual("Content1");
expect((<any>tabsContent[0]).style?.display).toEqual("");
expect((<any>tabsContent[1]).style?.display).toEqual("none");
let visibleTabContents = getVisibleTabContents();
expect(visibleTabContents.length).toEqual(1);
expect(visibleTabContents[0].innerHTML).toEqual("Content0");
// Switch to tab 1
tabs[1].dispatchEvent(new Event("click"));
tabButtons[1].dispatchEvent(new Event("click"));
fixture.detectChanges();
expect(tabsContent.length).toEqual(2);
expect(tabsContent[0].innerHTML).toEqual("Content0");
expect(tabsContent[1].innerHTML).toEqual("Content1");
expect((<any>tabsContent[0]).style?.display).toEqual("none");
expect((<any>tabsContent[1]).style?.display).toEqual("");
visibleTabContents = getVisibleTabContents();
expect(visibleTabContents.length).toEqual(1);
expect(visibleTabContents[0].innerHTML).toEqual("Content1");
// Switch to tab 0
tabs[0].dispatchEvent(new Event("click"));
tabButtons[0].dispatchEvent(new Event("click"));
fixture.detectChanges();
expect(tabsContent.length).toEqual(2);
expect(tabsContent[0].innerHTML).toEqual("Content0");
expect(tabsContent[1].innerHTML).toEqual("Content1");
expect((<any>tabsContent[0]).style?.display).toEqual("");
expect((<any>tabsContent[1]).style?.display).toEqual("none");
visibleTabContents = getVisibleTabContents();
expect(visibleTabContents.length).toEqual(1);
expect(visibleTabContents[0].innerHTML).toEqual("Content0");
});
it("emits event on download button click", () => {
const spy = spyOn(component.downloadTracesButtonClick, "emit");
const downloadButton: null|HTMLButtonElement =
htmlElement.querySelector(".save-btn");
htmlElement.querySelector(".save-button");
expect(downloadButton).toBeInstanceOf(HTMLButtonElement);
downloadButton?.dispatchEvent(new Event("click"));

View File

@@ -17,25 +17,29 @@ import {Component, ElementRef, EventEmitter, Inject, Input, Output} from "@angul
import {PersistentStore} from "common/persistent_store";
import {Viewer, View, ViewType} from "viewers/viewer";
interface Tab extends View {
addedToDom: boolean;
}
@Component({
selector: "trace-view",
template: `
<div class="container-overlay">
</div>
<div class="header-items-wrapper">
<nav mat-tab-nav-bar class="viewer-nav-bar">
<nav mat-tab-nav-bar class="tabs-navigation-bar">
<a
*ngFor="let tab of viewerTabs"
*ngFor="let tab of tabs"
mat-tab-link
[active]="isCurrentActiveCard(tab.cardId)"
(click)="showViewer(tab.cardId)"
class="viewer-tab"
>{{tab.label}}</a>
[active]="isCurrentActiveTab(tab)"
(click)="onTabClick(tab)"
class="tab"
>{{tab.title}}</a>
</nav>
<button
color="primary"
mat-button
class="save-btn"
class="save-button"
(click)="downloadTracesButtonClick.emit()"
>Download all traces</button>
</div>
@@ -62,7 +66,7 @@ import {Viewer, View, ViewType} from "viewers/viewer";
align-items: center;
}
.viewer-nav-bar {
.tabs-navigation-bar {
height: 100%;
}
@@ -71,7 +75,7 @@ import {Viewer, View, ViewType} from "viewers/viewer";
flex-grow: 1;
}
.save-btn {
.save-button {
height: 100%;
}
`
@@ -83,8 +87,9 @@ export class TraceViewComponent {
@Output() downloadTracesButtonClick = new EventEmitter<void>();
private elementRef: ElementRef;
private viewerTabs: ViewerTab[] = [];
private activeViewerCardId = 0;
private tabs: Tab[] = [];
private currentActiveTab: undefined|Tab;
constructor(@Inject(ElementRef) elementRef: ElementRef) {
this.elementRef = elementRef;
@@ -95,44 +100,34 @@ export class TraceViewComponent {
this.renderViewsOverlay();
}
public showViewer(cardId: number) {
this.changeViewerVisibility(false);
this.activeViewerCardId = cardId;
this.changeViewerVisibility(true);
}
public isCurrentActiveCard(cardId: number) {
return this.activeViewerCardId === cardId;
public onTabClick(tab: Tab) {
this.showTab(tab);
}
private renderViewsTab() {
this.activeViewerCardId = 0;
this.viewerTabs = [];
const views: View[] = this.viewers
this.tabs = this.viewers
.map(viewer => viewer.getViews())
.flat()
.filter(view => (view.type === ViewType.TAB));
.filter(view => (view.type === ViewType.TAB))
.map(view => {
return {
type: view.type,
htmlElement: view.htmlElement,
title: view.title,
addedToDom: false
};
});
for (const [cardCounter, view] of views.entries()) {
if (!view) {
continue;
}
const traceViewContent = this.elementRef.nativeElement.querySelector(".trace-view-content")!;
// create tab for viewer nav bar
const tab = {
label: view.title,
cardId: cardCounter,
};
this.viewerTabs.push(tab);
this.tabs.forEach(tab => {
// TODO: setting "store" this way is a hack.
// Store should be part of View's interface.
(tab.htmlElement as any).store = this.store;
});
// add properties to view and add view to trace view card
(view as any).store = this.store;
view.htmlElement.id = `card-${cardCounter}`;
view.htmlElement.style.display = this.isActiveViewerCard(cardCounter) ? "" : "none";
const traceViewContent = this.elementRef.nativeElement.querySelector(".trace-view-content")!;
traceViewContent.appendChild(view.htmlElement);
if (this.tabs.length > 0) {
this.showTab(this.tabs[0]);
}
}
@@ -157,20 +152,29 @@ export class TraceViewComponent {
});
}
private isActiveViewerCard(cardId: number) {
return this.activeViewerCardId === cardId;
private showTab(tab: Tab) {
if (this.currentActiveTab) {
this.currentActiveTab.htmlElement.style.display = "none";
}
if (!tab.addedToDom) {
// Workaround for b/255966194:
// make sure that the first time a tab content is rendered
// (added to the DOM) it has style.display == "". This fixes the
// initialization/rendering issues with cdk-virtual-scroll-viewport
// components inside the tab contents.
const traceViewContent = this.elementRef.nativeElement.querySelector(".trace-view-content")!;
traceViewContent.appendChild(tab.htmlElement);
tab.addedToDom = true;
}
else {
tab.htmlElement.style.display = "";
}
this.currentActiveTab = tab;
}
private changeViewerVisibility(show: boolean) {
const view = document.querySelector(`#card-${this.activeViewerCardId}`);
if (view) {
(view as HTMLElement).style.display = show ? "" : "none";
(view as any).active = show;
}
private isCurrentActiveTab(tab: Tab) {
return tab === this.currentActiveTab;
}
}
interface ViewerTab {
label: string,
cardId: number
}