Merge pull request #2726 from alecmerdler/fix-cor-tabs-back-button

Fix UI Tabs To Play Nicely with Browser Back Button
This commit is contained in:
Alec Merdler 2017-06-26 16:55:14 -07:00 committed by GitHub
commit e45ffb39d1
9 changed files with 235 additions and 29 deletions

View file

@ -12,11 +12,12 @@ describe("CorTabPaneComponent", () => {
beforeEach(() => {
activeTab = new BehaviorSubject<string>(null);
spyOn(activeTab, "subscribe").and.returnValue(null);
spyOn(activeTab, "subscribe").and.callThrough();
panelMock = new Mock<CorTabPanelComponent>();
panelMock.setup(mock => mock.activeTab).is(activeTab);
component = new CorTabPaneComponent(panelMock.Object);
component.id = 'description';
});
describe("ngOnInit", () => {
@ -36,5 +37,27 @@ describe("CorTabPaneComponent", () => {
expect((<Spy>panelMock.Object.activeTab.subscribe)).toHaveBeenCalled();
});
it("does nothing if active tab ID is undefined", () => {
component.ngOnInit();
component.isActiveTab = true;
panelMock.Object.activeTab.next(null);
expect(component.isActiveTab).toEqual(true);
});
it("sets self as active if active tab ID matches tab ID", () => {
component.ngOnInit();
panelMock.Object.activeTab.next(component.id);
expect(component.isActiveTab).toEqual(true);
});
it("sets self as inactive if active tab ID does not match tab ID", () => {
component.ngOnInit();
panelMock.Object.activeTab.next(component.id.split('').reverse().join(''));
expect(component.isActiveTab).toEqual(false);
});
});
});

View file

@ -1,5 +1,6 @@
import { Component, Input, Inject, Host, OnInit } from 'ng-metadata/core';
import { CorTabPanelComponent } from '../cor-tab-panel/cor-tab-panel.component';
import 'rxjs/add/operator/filter';
/**
@ -16,7 +17,7 @@ export class CorTabPaneComponent implements OnInit {
@Input('@') public id: string;
private isActiveTab: boolean = false;
public isActiveTab: boolean = false;
constructor(@Host() @Inject(CorTabPanelComponent) private panel: CorTabPanelComponent) {
@ -25,8 +26,10 @@ export class CorTabPaneComponent implements OnInit {
public ngOnInit(): void {
this.panel.addTabPane(this);
this.panel.activeTab.subscribe((tabId: string) => {
this.isActiveTab = (this.id === tabId);
});
this.panel.activeTab
.filter(tabId => tabId != undefined)
.subscribe((tabId: string) => {
this.isActiveTab = (this.id === tabId);
});
}
}

View file

@ -12,9 +12,16 @@ describe("CorTabPanelComponent", () => {
});
describe("ngOnInit", () => {
var tabs: CorTabComponent[] = [];
beforeEach(() => {
spyOn(component.activeTab, "subscribe").and.returnValue(null);
// Add tabs to panel
tabs.push(new CorTabComponent(component));
tabs[0].tabId = "info";
tabs.forEach((tab) => component.addTab(tab));
spyOn(component.activeTab, "subscribe").and.callThrough();
spyOn(component.activeTab, "next").and.callThrough();
spyOn(component.tabChange, "emit").and.returnValue(null);
});
@ -24,12 +31,26 @@ describe("CorTabPanelComponent", () => {
expect(<Spy>component.activeTab.subscribe).toHaveBeenCalled();
});
it("emits output event for tab change when ", () => {
it("emits next active tab with tab ID of first registered tab if given tab ID is null", () => {
component.ngOnInit();
component.activeTab.next(null);
expect((<Spy>component.activeTab.next).calls.argsFor(1)[0]).toEqual(tabs[0].tabId);
});
it("does not emit output event for tab change if tab ID is null", () => {
component.ngOnInit();
component.activeTab.next(null);
expect((<Spy>component.tabChange.emit).calls.allArgs).not.toContain(null);
});
it("emits output event for tab change when tab ID is not null", () => {
component.ngOnInit();
const tabId: string = "description";
(<Spy>component.activeTab.subscribe).calls.argsFor(0)[0](tabId);
component.activeTab.next(tabId);
expect((<Spy>component.tabChange.emit).calls.argsFor(0)[0]).toEqual(tabId);
expect((<Spy>component.tabChange.emit).calls.argsFor(1)[0]).toEqual(tabId);
});
});

View file

@ -28,7 +28,12 @@ export class CorTabPanelComponent implements OnInit, OnChanges {
public ngOnInit(): void {
this.activeTab.subscribe((tabId: string) => {
this.tabChange.emit(tabId);
// Catch null values and replace with tabId of first tab
if (!tabId && this.tabs[0]) {
this.activeTab.next(this.tabs[0].tabId);
} else {
this.tabChange.emit(tabId);
}
});
}

View file

@ -12,7 +12,7 @@ describe("CorTabComponent", () => {
beforeEach(() => {
activeTab = new BehaviorSubject<string>(null);
spyOn(activeTab, "subscribe").and.returnValue(null);
spyOn(activeTab, "subscribe").and.callThrough();
panelMock = new Mock<CorTabPanelComponent>();
panelMock.setup(mock => mock.activeTab).is(activeTab);
@ -35,16 +35,25 @@ describe("CorTabComponent", () => {
expect((<Spy>panelMock.Object.activeTab.subscribe)).toHaveBeenCalled();
});
it("does nothing if active tab ID is undefined", () => {
component.ngOnInit();
panelMock.Object.activeTab.next(null);
expect(<Spy>component.tabInit.emit).not.toHaveBeenCalled();
expect(<Spy>component.tabShow.emit).not.toHaveBeenCalled();
expect(<Spy>component.tabHide.emit).not.toHaveBeenCalled();
});
it("emits output event for tab init if it is new active tab", () => {
component.ngOnInit();
(<Spy>panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](component.tabId);
panelMock.Object.activeTab.next(component.tabId);
expect(<Spy>component.tabInit.emit).toHaveBeenCalled();
});
it("emits output event for tab show if it is new active tab", () => {
component.ngOnInit();
(<Spy>panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](component.tabId);
panelMock.Object.activeTab.next(component.tabId);
expect(<Spy>component.tabShow.emit).toHaveBeenCalled();
});
@ -53,8 +62,8 @@ describe("CorTabComponent", () => {
const newTabId: string = component.tabId.split('').reverse().join('');
component.ngOnInit();
// Call twice, first time to set 'isActive' to true
(<Spy>panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](component.tabId);
(<Spy>panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](newTabId);
panelMock.Object.activeTab.next(component.tabId);
panelMock.Object.activeTab.next(newTabId);
expect(<Spy>component.tabHide.emit).toHaveBeenCalled();
});
@ -62,7 +71,7 @@ describe("CorTabComponent", () => {
it("does not emit output event for tab hide if was not previously active tab", () => {
const newTabId: string = component.tabId.split('').reverse().join('');
component.ngOnInit();
(<Spy>panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](newTabId);
panelMock.Object.activeTab.next(newTabId);
expect(<Spy>component.tabHide.emit).not.toHaveBeenCalled();
});

View file

@ -1,5 +1,6 @@
import { Component, Input, Output, Inject, EventEmitter, Host, OnInit } from 'ng-metadata/core';
import { CorTabPanelComponent } from '../cor-tab-panel/cor-tab-panel.component';
import 'rxjs/add/operator/filter';
/**
@ -28,16 +29,18 @@ export class CorTabComponent implements OnInit {
}
public ngOnInit(): void {
this.panel.activeTab.subscribe((tabId: string) => {
if (!this.isActive && this.tabId === tabId) {
this.isActive = true;
this.tabInit.emit({});
this.tabShow.emit({});
} else if (this.isActive && this.tabId !== tabId) {
this.isActive = false;
this.tabHide.emit({});
}
});
this.panel.activeTab
.filter(tabId => tabId != undefined)
.subscribe((tabId: string) => {
if (!this.isActive && this.tabId === tabId) {
this.isActive = true;
this.tabInit.emit({});
this.tabShow.emit({});
} else if (this.isActive && this.tabId !== tabId) {
this.isActive = false;
this.tabHide.emit({});
}
});
this.panel.addTab(this);
}

View file

@ -0,0 +1,13 @@
import { element, by, browser, $, ElementFinder, ExpectedConditions as until } from 'protractor';
export class CorTabsViewObject {
public selectTabByTitle(title: string): Promise<void> {
return Promise.resolve($(`cor-tab[tab-title="${title}"] a`).click());
}
public isActiveTab(title: string): Promise<boolean> {
return Promise.resolve($(`cor-tab[tab-title="${title}"] .cor-tab-itself.active`).isPresent());
}
}

View file

@ -0,0 +1,128 @@
import { browser, element, by, $, $$ } from 'protractor';
import { appHost } from '../protractor.conf';
import { CorTabsViewObject } from '../../js/directives/ui/cor-tabs/cor-tabs.view-object';
describe("Image Repository", () => {
const username = 'devtable';
const password = 'password';
const repoTabs: CorTabsViewObject = new CorTabsViewObject();
beforeAll((done) => {
browser.waitForAngularEnabled(false);
// Sign in
browser.get(appHost);
$$('a[href="/signin/"]').get(1).click();
$('#signin-username').sendKeys(username);
$('#signin-password').sendKeys(password);
element(by.partialButtonText('Sign in')).click();
browser.sleep(4000);
// Navigate to image repository
browser.get(`${appHost}/repository/devtable/simple`).then(() => done());
});
afterAll(() => {
browser.waitForAngularEnabled(true);
});
describe("information tab", () => {
const tabTitle: string = 'Information';
beforeAll((done) => {
repoTabs.selectTabByTitle(tabTitle).then(() => done());
});
it("displays repository description", () => {
expect(repoTabs.isActiveTab(tabTitle)).toBe(true);
expect(element(by.cssContainingText('h4', 'Description')).isDisplayed()).toBe(true);
});
});
describe("tags tab", () => {
const tabTitle: string = 'Tags';
beforeAll((done) => {
repoTabs.selectTabByTitle(tabTitle).then(() => done());
});
it("displays repository tags", () => {
expect(repoTabs.isActiveTab(tabTitle)).toBe(true);
expect(element(by.cssContainingText('.tab-header', 'Repository Tags')).isDisplayed()).toBe(true);
});
});
describe("tag history tab", () => {
const tabTitle: string = 'Tag History';
beforeAll((done) => {
repoTabs.selectTabByTitle(tabTitle).then(() => done());
});
it("displays repository tags", () => {
expect(repoTabs.isActiveTab(tabTitle)).toBe(true);
expect(element(by.cssContainingText('.tab-header', 'Tag History')).isDisplayed()).toBe(true);
});
});
describe("builds tab", () => {
const tabTitle: string = 'Builds';
beforeAll((done) => {
repoTabs.selectTabByTitle(tabTitle).then(() => done());
});
it("displays repository tags", () => {
expect(repoTabs.isActiveTab(tabTitle)).toBe(true);
expect(element(by.cssContainingText('.tab-header', 'Repository Builds')).isDisplayed()).toBe(true);
});
});
describe("usage logs tab", () => {
const tabTitle: string = 'Usage Logs';
beforeAll((done) => {
repoTabs.selectTabByTitle(tabTitle).then(() => done());
});
it("displays repository tags", () => {
expect(repoTabs.isActiveTab(tabTitle)).toBe(true);
expect(element(by.cssContainingText('h3', 'Usage Logs')).isDisplayed()).toBe(true);
});
});
describe("settings tab", () => {
const tabTitle: string = 'Settings';
beforeAll((done) => {
repoTabs.selectTabByTitle(tabTitle).then(() => done());
});
it("displays repository tags", () => {
expect(repoTabs.isActiveTab(tabTitle)).toBe(true);
expect(element(by.cssContainingText('.tab-header', 'Settings')).isDisplayed()).toBe(true);
});
});
describe("tabs navigation", () => {
beforeAll((done) => {
repoTabs.selectTabByTitle('Information');
repoTabs.selectTabByTitle('Tags');
done();
});
it("back button returns to previous tab", () => {
browser.navigate().back();
expect(repoTabs.isActiveTab('Information')).toBe(true);
});
it("forward button returns to next tab", () => {
browser.navigate().forward();
expect(repoTabs.isActiveTab('Tags')).toBe(true);
});
});
});

View file

@ -20,7 +20,7 @@ export const config: Config = {
framework: 'jasmine',
seleniumAddress: 'http://localhost:4444/wd/hub',
// Uncomment to run tests against local Chrome instance
// directConnect: true,
directConnect: true,
capabilities: {
browserName: 'chrome',
chromeOptions: {
@ -60,7 +60,8 @@ export const config: Config = {
browser.close();
},
specs: [
'./e2e/sanity.scenario.ts',
'./e2e/trigger-creation.scenario.ts'
// './e2e/sanity.scenario.ts',
// './e2e/trigger-creation.scenario.ts',
'./e2e/image-repo.scenario.ts',
],
};