From 8c03a6be31858bacb08a4f97b08ea96869b3ffb3 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Fri, 23 Jun 2017 14:52:43 -0700 Subject: [PATCH 1/2] cor-tab-panel emits first registered tab ID if active tab is undefined adding e2e tests for cor-tabs --- .../cor-tab-pane.component.spec.ts | 25 ++++++++- .../cor-tab-pane/cor-tab-pane.component.ts | 11 ++-- .../cor-tab-panel.component.spec.ts | 29 +++++++++-- .../cor-tab-panel/cor-tab-panel.component.ts | 7 ++- .../cor-tab/cor-tab.component.spec.ts | 21 +++++--- .../ui/cor-tabs/cor-tab/cor-tab.component.ts | 23 +++++---- .../ui/cor-tabs/cor-tabs.view-object.ts | 9 ++++ static/test/e2e/image-repo.scenario.ts | 51 +++++++++++++++++++ static/test/protractor.conf.ts | 5 +- 9 files changed, 153 insertions(+), 28 deletions(-) create mode 100644 static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts create mode 100644 static/test/e2e/image-repo.scenario.ts diff --git a/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.spec.ts b/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.spec.ts index 46441848a..a86296d93 100644 --- a/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.spec.ts +++ b/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.spec.ts @@ -12,11 +12,12 @@ describe("CorTabPaneComponent", () => { beforeEach(() => { activeTab = new BehaviorSubject(null); - spyOn(activeTab, "subscribe").and.returnValue(null); + spyOn(activeTab, "subscribe").and.callThrough(); panelMock = new Mock(); panelMock.setup(mock => mock.activeTab).is(activeTab); component = new CorTabPaneComponent(panelMock.Object); + component.id = 'description'; }); describe("ngOnInit", () => { @@ -36,5 +37,27 @@ describe("CorTabPaneComponent", () => { expect((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); + }); }); }); diff --git a/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.ts index 95867aa3b..5f64c7add 100644 --- a/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.ts +++ b/static/js/directives/ui/cor-tabs/cor-tab-pane/cor-tab-pane.component.ts @@ -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); + }); } } diff --git a/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.spec.ts b/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.spec.ts index c26634472..e0a616d92 100644 --- a/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.spec.ts +++ b/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.spec.ts @@ -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(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((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((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"; - (component.activeTab.subscribe).calls.argsFor(0)[0](tabId); + component.activeTab.next(tabId); - expect((component.tabChange.emit).calls.argsFor(0)[0]).toEqual(tabId); + expect((component.tabChange.emit).calls.argsFor(1)[0]).toEqual(tabId); }); }); diff --git a/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.ts b/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.ts index 3d3321054..01a1740a6 100644 --- a/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.ts +++ b/static/js/directives/ui/cor-tabs/cor-tab-panel/cor-tab-panel.component.ts @@ -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); + } }); } diff --git a/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.spec.ts b/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.spec.ts index ead20c980..6f471beb1 100644 --- a/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.spec.ts +++ b/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.spec.ts @@ -12,7 +12,7 @@ describe("CorTabComponent", () => { beforeEach(() => { activeTab = new BehaviorSubject(null); - spyOn(activeTab, "subscribe").and.returnValue(null); + spyOn(activeTab, "subscribe").and.callThrough(); panelMock = new Mock(); panelMock.setup(mock => mock.activeTab).is(activeTab); @@ -35,16 +35,25 @@ describe("CorTabComponent", () => { expect((panelMock.Object.activeTab.subscribe)).toHaveBeenCalled(); }); + it("does nothing if active tab ID is undefined", () => { + component.ngOnInit(); + panelMock.Object.activeTab.next(null); + + expect(component.tabInit.emit).not.toHaveBeenCalled(); + expect(component.tabShow.emit).not.toHaveBeenCalled(); + expect(component.tabHide.emit).not.toHaveBeenCalled(); + }); + it("emits output event for tab init if it is new active tab", () => { component.ngOnInit(); - (panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](component.tabId); + panelMock.Object.activeTab.next(component.tabId); expect(component.tabInit.emit).toHaveBeenCalled(); }); it("emits output event for tab show if it is new active tab", () => { component.ngOnInit(); - (panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](component.tabId); + panelMock.Object.activeTab.next(component.tabId); expect(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 - (panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](component.tabId); - (panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](newTabId); + panelMock.Object.activeTab.next(component.tabId); + panelMock.Object.activeTab.next(newTabId); expect(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(); - (panelMock.Object.activeTab.subscribe).calls.argsFor(0)[0](newTabId); + panelMock.Object.activeTab.next(newTabId); expect(component.tabHide.emit).not.toHaveBeenCalled(); }); diff --git a/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.ts b/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.ts index 2bb832194..0fc76f7fd 100644 --- a/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.ts +++ b/static/js/directives/ui/cor-tabs/cor-tab/cor-tab.component.ts @@ -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); } diff --git a/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts b/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts new file mode 100644 index 000000000..f34e51cd5 --- /dev/null +++ b/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts @@ -0,0 +1,9 @@ +import { element, by, browser, $, ElementFinder, ExpectedConditions as until } from 'protractor'; + + +export class CorTabsViewObject { + + public selectTabByTitle(title: string): Promise { + return Promise.resolve($(`cor-tab[tab-title="${title}"] a`).click()); + } +} diff --git a/static/test/e2e/image-repo.scenario.ts b/static/test/e2e/image-repo.scenario.ts new file mode 100644 index 000000000..9e9122334 --- /dev/null +++ b/static/test/e2e/image-repo.scenario.ts @@ -0,0 +1,51 @@ +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", () => { + + beforeAll((done) => { + repoTabs.selectTabByTitle('Information').then(() => done()); + }); + + it("displays repository description", () => { + expect(element(by.cssContainingText('h4', 'Description')).isDisplayed()).toBe(true); + }); + }); + + describe("tags tab", () => { + + beforeAll((done) => { + repoTabs.selectTabByTitle('Tags').then(() => done()); + }); + + it("displays repository description", () => { + expect(element(by.cssContainingText('h4', 'Description')).isDisplayed()).toBe(true); + }); + }); +}); diff --git a/static/test/protractor.conf.ts b/static/test/protractor.conf.ts index 9016c1743..1f59fdd12 100644 --- a/static/test/protractor.conf.ts +++ b/static/test/protractor.conf.ts @@ -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: { @@ -61,6 +61,7 @@ export const config: Config = { }, specs: [ './e2e/sanity.scenario.ts', - './e2e/trigger-creation.scenario.ts' + // './e2e/trigger-creation.scenario.ts', + './e2e/image-repo.scenario.ts', ], }; From f78e1fb679a2ea45141f5d6afc7bb3347add268e Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Mon, 26 Jun 2017 16:43:18 -0700 Subject: [PATCH 2/2] added end-to-end tests for cor-tabs in image repo view --- .../ui/cor-tabs/cor-tabs.view-object.ts | 4 + static/test/e2e/image-repo.scenario.ts | 85 ++++++++++++++++++- static/test/protractor.conf.ts | 2 +- 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts b/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts index f34e51cd5..c07ea281f 100644 --- a/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts +++ b/static/js/directives/ui/cor-tabs/cor-tabs.view-object.ts @@ -6,4 +6,8 @@ export class CorTabsViewObject { public selectTabByTitle(title: string): Promise { return Promise.resolve($(`cor-tab[tab-title="${title}"] a`).click()); } + + public isActiveTab(title: string): Promise { + return Promise.resolve($(`cor-tab[tab-title="${title}"] .cor-tab-itself.active`).isPresent()); + } } diff --git a/static/test/e2e/image-repo.scenario.ts b/static/test/e2e/image-repo.scenario.ts index 9e9122334..ff000e3e0 100644 --- a/static/test/e2e/image-repo.scenario.ts +++ b/static/test/e2e/image-repo.scenario.ts @@ -28,24 +28,101 @@ describe("Image Repository", () => { }); describe("information tab", () => { + const tabTitle: string = 'Information'; beforeAll((done) => { - repoTabs.selectTabByTitle('Information').then(() => 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('Tags').then(() => done()); + repoTabs.selectTabByTitle(tabTitle).then(() => done()); }); - it("displays repository description", () => { - expect(element(by.cssContainingText('h4', 'Description')).isDisplayed()).toBe(true); + 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); }); }); }); diff --git a/static/test/protractor.conf.ts b/static/test/protractor.conf.ts index 1f59fdd12..e69f9753d 100644 --- a/static/test/protractor.conf.ts +++ b/static/test/protractor.conf.ts @@ -60,7 +60,7 @@ export const config: Config = { browser.close(); }, specs: [ - './e2e/sanity.scenario.ts', + // './e2e/sanity.scenario.ts', // './e2e/trigger-creation.scenario.ts', './e2e/image-repo.scenario.ts', ],