Add the Navigation Timing panel
Provides the navigation timing toolbar. This records key moments in the page load experience. Additionally: Updates Karma config to capture tests in folders Updates the demo to have styles and use a real panel Gives buttons a click action and a parent Adds Formatter to handle formatting durations Adds a few extra interfaces to handle panels being constructed with configurations Adds PanelConfigMerger to merge user configs and default configs Adds PanelFrame to hold and show/hide panels Updates Toolbar to construct from panel classes and configurations Cleans up a bug where the wrong container was cleared in the toolbar render Removes a few tslint rules that were causing more problems than they were worth
This commit is contained in:
Коммит
ecea25b431
83
index.html
83
index.html
|
@ -2,6 +2,55 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Demo for WebPerfToolbar</title>
|
||||
<style>
|
||||
/** TODO: These need to be moved into JS (for ease of usage in an app). They're inline in the demo for ease of development. */
|
||||
|
||||
#PTB_buttons {
|
||||
position:fixed;
|
||||
bottom: 0;
|
||||
left: 50px;
|
||||
border:1px solid black;
|
||||
border-bottom: none;
|
||||
|
||||
list-style:none;
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
#PTB_buttons li {
|
||||
display:inline-block;
|
||||
line-height:1.6em;
|
||||
margin-left:0.5em;
|
||||
padding:0.2em;
|
||||
cursor:pointer;
|
||||
}
|
||||
#PTB_buttons li:first-child {
|
||||
margin-left:0;
|
||||
}
|
||||
|
||||
#PTB_frame {
|
||||
position:fixed;
|
||||
width:30%;
|
||||
min-width:300px;
|
||||
right:0;
|
||||
top:0;
|
||||
height:100%;
|
||||
border-left:1px solid black;
|
||||
}
|
||||
|
||||
#PTB_frame table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
border: 1px solid black;
|
||||
}
|
||||
#PTB_frame th {
|
||||
font-weight: bold;
|
||||
}
|
||||
#PTB_frame th,
|
||||
#PTB_frame td {
|
||||
border: 1px solid black;
|
||||
padding:0.2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script defer>
|
||||
|
@ -15,34 +64,16 @@
|
|||
}
|
||||
|
||||
var startFunc = function() {
|
||||
/** Fake a panel with a single button */
|
||||
var fakePanel = {
|
||||
getButtons: function () {
|
||||
return [new PerfToolbar.Button({
|
||||
emoji: '#',
|
||||
getValue: function () { return '1'},
|
||||
getColor: function () { return 'gray'}
|
||||
})];
|
||||
}
|
||||
};
|
||||
|
||||
/** Fake another panel with a single button */
|
||||
var fakePanel2 = {
|
||||
getButtons: function () {
|
||||
return [new PerfToolbar.Button({
|
||||
emoji: '#',
|
||||
getValue: function () { return '2'},
|
||||
getColor: function () { return 'lightgray'}
|
||||
})];
|
||||
}
|
||||
};
|
||||
|
||||
/** Configure this to include the panels you need */
|
||||
(new PerfToolbar.Toolbar([
|
||||
fakePanel,
|
||||
fakePanel2
|
||||
])).render();
|
||||
/** Configure this to include the panels you need */
|
||||
{
|
||||
panel: PerfToolbar.NavigationTimingsPanel,
|
||||
config: {
|
||||
goalMs: 25
|
||||
}
|
||||
},
|
||||
/** End configuration */
|
||||
])).render();
|
||||
}
|
||||
|
||||
if('requestIdleCallback' in window)
|
||||
|
|
|
@ -19,7 +19,7 @@ module.exports = function(config) {
|
|||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
'test/*.ts',
|
||||
'test/**/*.ts'
|
||||
],
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import { IPanel } from "./ipanel";
|
||||
|
||||
/**
|
||||
* The configuration options for constructing a button.
|
||||
*/
|
||||
export interface IButtonConfiguration {
|
||||
/** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes */
|
||||
/** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes. */
|
||||
emoji?: string;
|
||||
|
||||
/** The panel that owns this button. */
|
||||
parent?: IPanel;
|
||||
|
||||
/** Gets the background color for the button. */
|
||||
getColor?(): string;
|
||||
|
||||
|
@ -11,7 +19,7 @@ export interface IButtonConfiguration {
|
|||
|
||||
/** Describes a button to be displayed in the collapsed toolbar. */
|
||||
export class Button {
|
||||
/** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes */
|
||||
/** The icon for the button. The intention is to use a single character emoji but it's just a string, so anything goes. */
|
||||
public readonly emoji: string;
|
||||
|
||||
/** Gets the background color for the button. */
|
||||
|
@ -20,11 +28,15 @@ export class Button {
|
|||
/** Gets the displayed value for the button. */
|
||||
public readonly getValue: (() => string);
|
||||
|
||||
/** The panel that provides this button. */
|
||||
public readonly parent: IPanel | undefined;
|
||||
|
||||
/**
|
||||
* Create the button.
|
||||
*/
|
||||
public constructor(config: IButtonConfiguration = {}) {
|
||||
this.emoji = config.emoji !== undefined ? config.emoji : "";
|
||||
this.parent = config.parent;
|
||||
/* tslint:disable no-unbound-method */
|
||||
this.getValue = config.getValue !== undefined ? config.getValue : (): string => "";
|
||||
this.getColor = config.getColor !== undefined ? config.getColor : (): string => "";
|
||||
|
@ -40,6 +52,12 @@ export class Button {
|
|||
li.setAttribute("style", `background-color:${this.getColor()}`);
|
||||
li.innerText = `${this.emoji} ${this.getValue()}`;
|
||||
|
||||
li.addEventListener("click", () => {
|
||||
if (this.parent) {
|
||||
this.parent.toggle();
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/** The level of precision we want to see for numbers */
|
||||
export const DECIMAL_PLACES: number = 2;
|
||||
|
||||
/**
|
||||
* Formats a duration for output. Makes sure the numbers are valid and only returns a certain number of decimal places.
|
||||
* Invalid input returns a dash.
|
||||
* @param end The end timestamp
|
||||
* @param start The start timestamp
|
||||
* @param decimalPlaces The number of decimal places to show.
|
||||
*/
|
||||
export const duration: (end: number, start: number, decimalPlaces?: number) => string =
|
||||
(end: number, start: number, decimalPlaces: number = DECIMAL_PLACES): string => {
|
||||
if (isNaN(end) || isNaN(start)) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return (end - start).toFixed(decimalPlaces);
|
||||
};
|
|
@ -1,2 +1,3 @@
|
|||
export { Toolbar } from "./toolbar";
|
||||
export { Button } from "./button";
|
||||
export { NavigationTimingsPanel } from "./panels/navigation-timing";
|
||||
|
|
|
@ -1,7 +1,25 @@
|
|||
import { Button } from "./button";
|
||||
import { PanelFrame } from "./panelframe";
|
||||
|
||||
export interface IPanelWithConfiguration<C, P> {
|
||||
config: C;
|
||||
panel: IPanelConstructor<C, P>;
|
||||
}
|
||||
|
||||
export interface IPanelConstructor<C, P> {
|
||||
new (frame: PanelFrame, config: C): P;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
export interface IPanelConfig {
|
||||
|
||||
}
|
||||
|
||||
/** Describes a panel within the opened toolbar. */
|
||||
export interface IPanel {
|
||||
/**
|
||||
* The name of the panel.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
|
@ -14,4 +32,7 @@ export interface IPanel {
|
|||
* @param target The HTML element to contain this panel.
|
||||
*/
|
||||
render(target: HTMLElement): void;
|
||||
|
||||
/** Toggles the visibility of this panel */
|
||||
toggle(): void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import { IPanel } from "./ipanel";
|
||||
|
||||
/**
|
||||
* Responsible for holding and displaying panels
|
||||
*/
|
||||
export class PanelFrame {
|
||||
/** The element that represents the frame in the DOM. */
|
||||
private readonly frame: HTMLDivElement;
|
||||
|
||||
/** The root of the toolbar */
|
||||
private readonly toolbarRoot: HTMLElement;
|
||||
|
||||
/**
|
||||
* Creates the panel frame.
|
||||
* @param toolbarRoot The DOM element to contain the frame. Should be the root of the toolbar.
|
||||
*/
|
||||
public constructor(toolbarRoot: HTMLElement) {
|
||||
this.toolbarRoot = toolbarRoot;
|
||||
|
||||
this.frame = document.createElement("div");
|
||||
this.frame.setAttribute("id", "PTB_frame");
|
||||
}
|
||||
|
||||
/** Show the provided panel, or hide the displayed panel. */
|
||||
public toggle(panel: IPanel): void {
|
||||
if (this.frame.parentNode === null) {
|
||||
panel.render(this.frame);
|
||||
this.toolbarRoot.appendChild(this.frame);
|
||||
} else {
|
||||
this.frame.parentNode.removeChild(this.frame);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import { Button } from "../button";
|
||||
import * as Formatter from "../formatter";
|
||||
import { IPanel, IPanelConfig } from "../ipanel";
|
||||
import { PanelFrame } from "../panelframe";
|
||||
|
||||
/** Describes the configuration options available for the network panel */
|
||||
export interface INavigationTimingsPanelConfig extends IPanelConfig {
|
||||
/** The goal for the load duration */
|
||||
goalMs: number;
|
||||
/** The performance timing object, can be included in the config to enable injection of a mock object for testing */
|
||||
timings: PerformanceTiming;
|
||||
}
|
||||
|
||||
/** A set of default configuration options for the navigation timings panel */
|
||||
const navigationTimingsPanelDefaultConfig: INavigationTimingsPanelConfig = {
|
||||
goalMs: 500,
|
||||
timings: performance.timing,
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides a panel that shows the navigation timings for a page
|
||||
*/
|
||||
export class NavigationTimingsPanel implements IPanel {
|
||||
/** The name of the panel */
|
||||
public readonly name: string = "Navigation Timings";
|
||||
|
||||
/** The settings for this panel. */
|
||||
private readonly config: INavigationTimingsPanelConfig;
|
||||
|
||||
/** The frame that displays this panel. */
|
||||
private readonly frame: PanelFrame;
|
||||
|
||||
public constructor(frame: PanelFrame, config?: INavigationTimingsPanelConfig) {
|
||||
this.frame = frame;
|
||||
this.config = { ...navigationTimingsPanelDefaultConfig, ...config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the buttons to be displayed
|
||||
* @see IPanel.getButtons
|
||||
*/
|
||||
public getButtons(): Button[] {
|
||||
return [new Button({
|
||||
parent: this,
|
||||
emoji: "⏱️",
|
||||
getValue: (): string => `${Formatter.duration(this.config.timings.loadEventEnd, this.config.timings.navigationStart)} ms`,
|
||||
getColor: (): string => this.config.timings.loadEventEnd - this.config.timings.navigationStart <= this.config.goalMs ? "green" : "red",
|
||||
})];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the contents of the panel
|
||||
* @see IPanel.render
|
||||
*/
|
||||
public render(target: HTMLElement): void {
|
||||
const t: PerformanceTiming = this.config.timings;
|
||||
|
||||
target.innerHTML = `
|
||||
<table>
|
||||
<tr>
|
||||
<th>Get Connected</th>
|
||||
<td>${Formatter.duration(t.connectEnd, t.domainLookupStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DNS Lookup</td>
|
||||
<td>${Formatter.duration(t.domainLookupEnd, t.domainLookupStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSL</td>
|
||||
<td>${Formatter.duration(t.connectEnd, t.connectStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Get Content</th>
|
||||
<td>${Formatter.duration(t.responseEnd, t.requestStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Waiting for Server</td>
|
||||
<td>${Formatter.duration(t.responseStart, t.requestStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Time To Download</td>
|
||||
<td>${Formatter.duration(t.responseEnd, t.responseStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan=2>Get Ready</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Parse Content</td>
|
||||
<td>${Formatter.duration(t.domInteractive, t.responseEnd)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deferred Scripts</td>
|
||||
<td>${Formatter.duration(t.domContentLoadedEventEnd, t.domInteractive)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DOM Complete</td>
|
||||
<td>${Formatter.duration(t.domComplete, t.domContentLoadedEventEnd)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Load Event</td>
|
||||
<td>${Formatter.duration(t.loadEventEnd, t.loadEventStart)} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Total Load</th>
|
||||
<td>${Formatter.duration(t.loadEventEnd, t.navigationStart)} ms</td>
|
||||
</tr>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the display of this panel.
|
||||
*/
|
||||
public toggle(): void {
|
||||
this.frame.toggle(this);
|
||||
}
|
||||
}
|
|
@ -1,42 +1,42 @@
|
|||
import { IPanel } from "./ipanel";
|
||||
import { IPanel, IPanelConfig, IPanelWithConfiguration } from "./ipanel";
|
||||
import { PanelFrame } from "./panelframe";
|
||||
|
||||
/** Describes the toolbar. */
|
||||
export class Toolbar {
|
||||
/** The container that will hold the toolbar */
|
||||
private container: HTMLElement;
|
||||
|
||||
/** The panels that will be displayed in the toolbar */
|
||||
private panels: IPanel[];
|
||||
|
||||
/** The root element of the toolbar. */
|
||||
private root: HTMLElement;
|
||||
private toolbarRoot: HTMLElement;
|
||||
|
||||
/**
|
||||
* Creates the toolbar.
|
||||
* @param panels The panels to be displayed when the toolbar is opened.
|
||||
* @param container Optional parameter that defaults to the body of the HTML page.
|
||||
* @param panels Classes for the panels to be displayed when the toolbar is opened.
|
||||
* @param container Optional parameter for the element that contains the toolbar. It defaults to the body of the HTML page.
|
||||
*/
|
||||
public constructor(panels: IPanel[], container: HTMLElement = window.document.body) {
|
||||
this.panels = panels;
|
||||
this.container = container;
|
||||
this.root = document.createElement("div");
|
||||
container.appendChild(this.root);
|
||||
public constructor(panels: Array<IPanelWithConfiguration<IPanelConfig, IPanel>>, container: HTMLElement = window.document.body) {
|
||||
this.toolbarRoot = document.createElement("div");
|
||||
this.toolbarRoot.setAttribute("id", "PTB_root");
|
||||
container.appendChild(this.toolbarRoot);
|
||||
|
||||
// Construct the frame and the panels that use it
|
||||
const frame: PanelFrame = new PanelFrame(this.toolbarRoot);
|
||||
this.panels = panels.map((panelWithConfig: IPanelWithConfiguration<IPanelConfig, IPanel>): IPanel =>
|
||||
new panelWithConfig.panel(frame, panelWithConfig.config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the toolbar.
|
||||
*/
|
||||
public render(): void {
|
||||
// Clear all children
|
||||
this.container.innerHTML = "";
|
||||
|
||||
const ul: HTMLUListElement = document.createElement("ul");
|
||||
for (const p of this.panels) {
|
||||
for (const b of p.getButtons()) {
|
||||
b.render(ul);
|
||||
const listOfButtons: HTMLUListElement = document.createElement("ul");
|
||||
listOfButtons.setAttribute("id", "PTB_buttons");
|
||||
for (const panel of this.panels) {
|
||||
for (const button of panel.getButtons()) {
|
||||
button.render(listOfButtons);
|
||||
}
|
||||
}
|
||||
|
||||
this.container.appendChild(ul);
|
||||
this.toolbarRoot.appendChild(listOfButtons);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { assert, expect } from "chai";
|
||||
import "mocha";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as Formatter from "../src/formatter";
|
||||
|
||||
describe("Formatter class", () => {
|
||||
describe("duration", () => {
|
||||
it("should return a dash for invalid input in the first parameter", () => {
|
||||
expect(Formatter.duration(undefined, 0)).to.equal("-");
|
||||
});
|
||||
|
||||
it("should return a dash for invalid input in the second parameter", () => {
|
||||
expect(Formatter.duration(0, undefined)).to.equal("-");
|
||||
});
|
||||
|
||||
it("should default to two decimal places", () => {
|
||||
expect(Formatter.duration(0, 0)).to.equal("0.00");
|
||||
});
|
||||
|
||||
it("should allow custom number of decimals", () => {
|
||||
expect(Formatter.duration(0, 0, 1)).to.equal("0.0");
|
||||
});
|
||||
|
||||
it("should do subtract the start from the end", () => {
|
||||
expect(Formatter.duration(1, 0, 0)).to.equal("1");
|
||||
expect(Formatter.duration(0, 1, 0)).to.equal("-1");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,18 +1,31 @@
|
|||
import { Button } from "../../src/button";
|
||||
import { IPanel } from "../../src/ipanel";
|
||||
import { PanelFrame } from "../../src/panelframe";
|
||||
|
||||
export interface IMockPanelConfig {
|
||||
getButtons(): Button[];
|
||||
}
|
||||
|
||||
export const mockPanelConfig: IMockPanelConfig = {
|
||||
getButtons: (): Button[] => [new Button({})],
|
||||
};
|
||||
|
||||
export class MockPanel implements IPanel {
|
||||
public name: string = "Mock Panel";
|
||||
|
||||
public constructor(getButtons: () => Button[]) {
|
||||
this.getButtons = getButtons;
|
||||
public constructor(frame: PanelFrame, config: IMockPanelConfig) {
|
||||
this.getButtons = config.getButtons;
|
||||
}
|
||||
|
||||
public getButtons(): Button[] {
|
||||
return undefined;
|
||||
throw new Error("Method must be overridden by passing a new getter in the constructor.");
|
||||
}
|
||||
|
||||
public render(target: HTMLElement): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public toggle(): void {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
import { assert, expect } from "chai";
|
||||
import "mocha";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { Button } from "../../src/button";
|
||||
import { PanelConfigMerger } from "../../src/panelconfigmerger";
|
||||
import { PanelFrame } from "../../src/panelframe";
|
||||
import { NavigationTimingsPanel } from "../../src/panels/navigation-timing";
|
||||
|
||||
describe("Navigation timing panel class", () => {
|
||||
|
||||
it("should provide a single button that is green when below the goal", () => {
|
||||
const rootElement: HTMLElement = document.createElement("div");
|
||||
const frame: PanelFrame = new PanelFrame(rootElement);
|
||||
const panel: NavigationTimingsPanel = new NavigationTimingsPanel(frame, {
|
||||
goalMs: 200,
|
||||
timings: getMockTimings({
|
||||
navigationStart: 0,
|
||||
loadEventEnd: 100,
|
||||
}),
|
||||
});
|
||||
|
||||
const buttons: Button[] = panel.getButtons();
|
||||
|
||||
expect(buttons.length).to.equal(1, "there should only be one button for this panel");
|
||||
expect(buttons[0].getColor()).to.equal("green", "the button should be green");
|
||||
});
|
||||
|
||||
it("should provide a single button that is green when at the goal", () => {
|
||||
const rootElement: HTMLElement = document.createElement("div");
|
||||
const frame: PanelFrame = new PanelFrame(rootElement);
|
||||
const panel: NavigationTimingsPanel = new NavigationTimingsPanel(frame, {
|
||||
goalMs: 100,
|
||||
timings: getMockTimings({
|
||||
navigationStart: 0,
|
||||
loadEventEnd: 100,
|
||||
}),
|
||||
});
|
||||
|
||||
const buttons: Button[] = panel.getButtons();
|
||||
|
||||
expect(buttons.length).to.equal(1, "there should only be one button for this panel");
|
||||
expect(buttons[0].getColor()).to.equal("green", "the button should be green");
|
||||
});
|
||||
|
||||
it("should provide a button that is red when above the goal", () => {
|
||||
const rootElement: HTMLElement = document.createElement("div");
|
||||
const frame: PanelFrame = new PanelFrame(rootElement);
|
||||
const panel: NavigationTimingsPanel = new NavigationTimingsPanel(frame, {
|
||||
goalMs: 50,
|
||||
timings: getMockTimings({
|
||||
navigationStart: 0,
|
||||
loadEventEnd: 100,
|
||||
}),
|
||||
});
|
||||
|
||||
const buttons: Button[] = panel.getButtons();
|
||||
|
||||
expect(buttons.length).to.equal(1, "there should only be one button for this panel");
|
||||
expect(buttons[0].getColor()).to.equal("red", "the button should be red");
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const rootElement: HTMLElement = document.createElement("div");
|
||||
const frame: PanelFrame = new PanelFrame(rootElement);
|
||||
const panel: NavigationTimingsPanel = new NavigationTimingsPanel(frame, {
|
||||
goalMs: 90,
|
||||
timings: getMockTimings(),
|
||||
});
|
||||
|
||||
const target: HTMLElement = document.createElement("div");
|
||||
panel.render(target);
|
||||
|
||||
expect(target.childElementCount).to.be.above(0, "it should render to the DOM");
|
||||
|
||||
});
|
||||
|
||||
// Provides mocked performance timings for tests
|
||||
const getMockTimings: (overrides?: Partial<PerformanceTiming>) => PerformanceTiming =
|
||||
(overrides: Partial<PerformanceTiming> = {}): PerformanceTiming => { // tslint:disable-line:arrow-return-shorthand cyclomatic-complexity
|
||||
return {
|
||||
connectEnd: overrides.connectEnd || 0,
|
||||
connectStart: overrides.connectStart || 0,
|
||||
domainLookupEnd: overrides.domainLookupEnd || 0,
|
||||
domainLookupStart: overrides.domainLookupStart || 0,
|
||||
domComplete: overrides.domComplete || 0,
|
||||
domContentLoadedEventEnd: overrides.domContentLoadedEventEnd || 0,
|
||||
domContentLoadedEventStart: overrides.domContentLoadedEventStart || 0,
|
||||
domInteractive: overrides.domInteractive || 0,
|
||||
domLoading: overrides.domLoading || 0,
|
||||
fetchStart: overrides.fetchStart || 0,
|
||||
loadEventEnd: overrides.loadEventEnd || 0,
|
||||
loadEventStart: overrides.loadEventStart || 0,
|
||||
msFirstPaint: overrides.msFirstPaint || 0,
|
||||
navigationStart: overrides.navigationStart || 0,
|
||||
redirectEnd: overrides.redirectEnd || 0,
|
||||
redirectStart: overrides.redirectStart || 0,
|
||||
requestStart: overrides.requestStart || 0,
|
||||
responseEnd: overrides.responseEnd || 0,
|
||||
responseStart: overrides.responseStart || 0,
|
||||
secureConnectionStart: overrides.secureConnectionStart || 0,
|
||||
unloadEventEnd: overrides.unloadEventEnd || 0,
|
||||
unloadEventStart: overrides.unloadEventStart || 0,
|
||||
toJSON: (): string => "",
|
||||
};
|
||||
};
|
||||
|
||||
});
|
|
@ -3,9 +3,9 @@ import "mocha";
|
|||
import * as sinon from "sinon";
|
||||
|
||||
import { Button } from "../src/button";
|
||||
import { IPanel } from "../src/ipanel";
|
||||
import { IPanel, IPanelConstructor, IPanelWithConfiguration } from "../src/ipanel";
|
||||
import { Toolbar } from "../src/toolbar";
|
||||
import { MockPanel } from "./mock/panel.mock";
|
||||
import { IMockPanelConfig, MockPanel, mockPanelConfig } from "./mock/panel.mock";
|
||||
|
||||
describe("Toolbar class", () => {
|
||||
|
||||
|
@ -17,13 +17,18 @@ describe("Toolbar class", () => {
|
|||
});
|
||||
|
||||
it("can render buttons", () => {
|
||||
const panel: IPanel = new MockPanel((): Button[] => [new Button({})]);
|
||||
const container: HTMLElement = document.createElement("div");
|
||||
const toolbar: Toolbar = new Toolbar([panel], container);
|
||||
|
||||
const mockPanelWithConfig: IPanelWithConfiguration<IMockPanelConfig, MockPanel> = {
|
||||
panel: MockPanel,
|
||||
config: mockPanelConfig,
|
||||
};
|
||||
|
||||
const toolbar: Toolbar = new Toolbar([mockPanelWithConfig], container);
|
||||
|
||||
toolbar.render();
|
||||
|
||||
const expectedList: Element = container.firstElementChild;
|
||||
const expectedList: Element = container.firstElementChild.children.item(0);
|
||||
expect(expectedList).instanceof(HTMLUListElement, "We expect the toolbar to be a list");
|
||||
expect(expectedList.childElementCount).equals(1, "We expect that list to have one item");
|
||||
});
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"prefer-function-over-method": false,
|
||||
"no-implicit-dependencies": [true, "dev"],
|
||||
"no-import-side-effect": false,
|
||||
"strict-boolean-expressions": [true, "allow-undefined-union"]
|
||||
"strict-boolean-expressions": [true, "allow-undefined-union"],
|
||||
"interface-over-type-literal": false
|
||||
},
|
||||
"jsRules": {
|
||||
"max-line-length": {
|
||||
|
|
Загрузка…
Ссылка в новой задаче