diff --git a/e2e/renderer/package.json b/e2e/renderer/package.json index f9e33fe7e..94403d63c 100644 --- a/e2e/renderer/package.json +++ b/e2e/renderer/package.json @@ -10,14 +10,14 @@ } }, "dependencies": { - "@angular/animations": "~4.2.0", - "@angular/common": "~4.2.0", - "@angular/compiler": "~4.2.0", - "@angular/core": "~4.2.0", - "@angular/forms": "~4.2.0", - "@angular/http": "~4.2.0", - "@angular/platform-browser": "~4.2.0", - "@angular/router": "~4.2.0", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "file:../../nativescript-angular", "nativescript-intl": "^3.0.0", "reflect-metadata": "~0.1.8", diff --git a/e2e/router/.gitignore b/e2e/router/.gitignore index d3df84cc8..8e824499e 100644 --- a/e2e/router/.gitignore +++ b/e2e/router/.gitignore @@ -1,8 +1,10 @@ +.vscode + platforms node_modules hooks -app/**/*.js -e2e/**/*.js -test-results.xml - +/**/*.js +/**/*.map +e2e/reports +test-results.xml \ No newline at end of file diff --git a/e2e/router/e2e/config/appium.capabilities.json b/e2e/router/e2e/config/appium.capabilities.json index 676f5e070..4f1b4407d 100644 --- a/e2e/router/e2e/config/appium.capabilities.json +++ b/e2e/router/e2e/config/appium.capabilities.json @@ -1,84 +1,100 @@ { "nexus5": { "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "6.0", "deviceName": "device", "udid": "077e4a47003b7698", - "-lt": 60000, - "automationName": "Appium", "appActivity": "com.tns.NativeScriptActivity", "app": "" }, "android19": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "4.4", "deviceName": "Emulator-Api19-Default", "avd": "Emulator-Api19-Default", - "-lt": 60000, - "automationName": "Appium", + "lt": 60000, "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "android21": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "5.0", "deviceName": "Emulator-Api21-Default", "avd": "Emulator-Api21-Default", - "-lt": 60000, - "automationName": "Appium", + "lt": 60000, "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "android23": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "6.0", "deviceName": "Emulator-Api23-Default", "avd": "Emulator-Api23-Default", - "-lt": 60000, - "automationName": "Appium", + "lt": 60000, "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "android24": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "Android", "platformVersion": "7.0", "deviceName": "Emulator-Api24-Default", "avd": "Emulator-Api24-Default", - "-lt": 60000, + "lt": 60000, + "appActivity": "com.tns.NativeScriptActivity", + "newCommandTimeout": 720, + "noReset": false, + "fullReset": false, + "app": "" + }, + "android25": { + "platformName": "Android", + "platformVersion": "7.1", + "deviceName": "Emulator-Api25-Google", + "avd": "Emulator-Api25-Google", + "lt": 60000, + "appActivity": "com.tns.NativeScriptActivity", + "newCommandTimeout": 720, + "noReset": false, + "fullReset": false, + "app": "" + }, + "android26": { + "platformName": "Android", + "platformVersion": "8.0", + "deviceName": "Emulator-Api26-Google", + "avd": "Emulator-Api26-Google", + "lt": 60000, "automationName": "UIAutomator2", "appActivity": "com.tns.NativeScriptActivity", "newCommandTimeout": 720, - "noReset": true, + "noReset": false, "fullReset": false, "app": "" }, "sim.iPhone7.iOS100": { - "browserName": "", - "appium-version": "1.6.5", "platformName": "iOS", "platformVersion": "10.0", "deviceName": "iPhone 7 100", - "noReset": true, + "noReset": false, + "fullReset": false, + "app": "" + }, + "sim.iPhone7.iOS110": { + "platformName": "iOS", + "platformVersion": "11.0", + "deviceName": "iPhone 7 110", + "noReset": false, "fullReset": false, "app": "" } -} +} \ No newline at end of file diff --git a/e2e/router/e2e/helpers/appium-elements.ts b/e2e/router/e2e/helpers/appium-elements.ts deleted file mode 100644 index 26a020a4d..000000000 --- a/e2e/router/e2e/helpers/appium-elements.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AppiumDriver } from "nativescript-dev-appium"; - -import { UIElement } from "nativescript-dev-appium/ui-element"; - -export class ExtendedUIElement extends UIElement { - refetch(): Promise { - return Promise.resolve(this); - } -} - -const refetchable = () => - (target: any, propertyKey: string, descriptor: PropertyDescriptor): any => { - const originalMethod = descriptor.value; - const patchRefetch = async (args, fetchMethod) => { - const result = await fetchMethod() as ExtendedUIElement; - result.refetch = () => patchRefetch(args, fetchMethod); - - return result; - } - - descriptor.value = async function (...args: any[]): Promise { - const fetchMethod = () => originalMethod.apply(this, args); - const result = await patchRefetch(args, fetchMethod); - - return result; - } - - return descriptor; - }; - -export class DriverWrapper { - constructor(private driver: AppiumDriver) { - } - - @refetchable() - async findElementByText(...args: any[]): Promise { - const result = await (this.driver).findElementByText(...args); - - return result; - } -} diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts index c6ae36cb6..82dcda30c 100644 --- a/e2e/router/e2e/router.e2e-spec.ts +++ b/e2e/router/e2e/router.e2e-spec.ts @@ -1,453 +1,398 @@ import { AppiumDriver, + UIElement, createDriver, SearchOptions, } from "nativescript-dev-appium"; -import { DriverWrapper, ExtendedUIElement } from "./helpers/appium-elements"; - describe("Simple navigate and back", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1); - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1); + await assureNestedMasterComponent(driver); }); it("should navigate back to First", async () => { - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate inside nested outlet", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/detail(1) and back", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 1", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 1", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 1); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 1); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/detail(2) and back", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 2", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 2", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 2); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 2); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate back to First", async () => { - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate to same component with different param", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(2)/master", async () => { const navigationButton = - await driverWrapper.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); + await driver.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); navigationButton.click(); - await assureSecondComponent(driverWrapper, 2) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 2) + await assureNestedMasterComponent(driver); }); it("should navigate back to Second(1)/master", async () => { - await goBack(driverWrapper); + await goBack(driver); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate back to First", async () => { - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Nested navigation + page navigation", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/detail(1)", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 1", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 1", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 1); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 1); }); it("should navigate to Second(2)/master", async () => { const navigationButton = - await driverWrapper.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); + await driver.findElementByText("GO TO NEXT SECOND", SearchOptions.exact); navigationButton.click(); - await assureSecondComponent(driverWrapper, 2) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 2) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(2)/detail(2)", async () => { - const detailBtn = await driverWrapper.findElementByText("DETAIL 2", SearchOptions.exact); + const detailBtn = await driver.findElementByText("DETAIL 2", SearchOptions.exact); detailBtn.click(); - await assureSecondComponent(driverWrapper, 2) - await assureNestedDetailComponent(driverWrapper, 2); + await assureSecondComponent(driver, 2) + await assureNestedDetailComponent(driver, 2); }); it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); + await findAndClick(driver, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 2) - await assureNestedDetailComponent(driverWrapper, 2); + await goBack(driver); + await assureSecondComponent(driver, 2) + await assureNestedDetailComponent(driver, 2); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 2) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 2) + await assureNestedMasterComponent(driver); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedDetailComponent(driverWrapper, 1); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedDetailComponent(driver, 1); - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await goBack(driver); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Shouldn't be able to navigate back on startup", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("shouldn't be able to go back", async () => { - await goBack(driverWrapper); - await driverWrapper.findElementByText("canGoBack() - false", SearchOptions.exact); + await goBack(driver); + await driver.findElementByText("canGoBack() - false", SearchOptions.exact); }); }); describe("Shouldn't be able to navigate back after cleared history", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO SECOND"); + await findAndClick(driver, "GO TO SECOND"); - await assureSecondComponent(driverWrapper, 1) - await assureNestedMasterComponent(driverWrapper); + await assureSecondComponent(driver, 1) + await assureNestedMasterComponent(driver); }); it("should navigate to Second(1)/master", async () => { - await findAndClick(driverWrapper, "GO TO FIRST(CLEAR)"); + await findAndClick(driver, "GO TO FIRST(CLEAR)"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("shouldn't be able to go back", async () => { - await goBack(driverWrapper); - await driverWrapper.findElementByText("canGoBack() - false", SearchOptions.exact); + await goBack(driver); + await driver.findElementByText("canGoBack() - false", SearchOptions.exact); }); }); describe("Navigate to componentless route", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to ComponentlessSecond(100)/detail(200)", async () => { const navigationButton = - await driverWrapper.findElementByText("GO TO C-LESS SECOND", SearchOptions.exact); + await driver.findElementByText("GO TO C-LESS SECOND", SearchOptions.exact); navigationButton.click(); - await assureSecondComponent(driverWrapper, 100) - await assureNestedDetailComponent(driverWrapper, 200); + await assureSecondComponent(driver, 100) + await assureNestedDetailComponent(driver, 200); }); it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); + await findAndClick(driver, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureSecondComponent(driverWrapper, 100) - await assureNestedDetailComponent(driverWrapper, 200); + await goBack(driver); + await assureSecondComponent(driver, 100) + await assureNestedDetailComponent(driver, 200); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate to lazy module", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to lazy/home", async () => { - await findAndClick(driverWrapper, "GO TO LAZY HOME"); - - await assureLazyComponent(driverWrapper); + await findAndClick(driver, "GO TO LAZY HOME"); + await assureLazyComponent(driver); }); it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await findAndClick(driver, "GO TO FIRST"); + await assureFirstComponent(driver); }); it("should navigate back to lazy/home", async () => { - await goBack(driverWrapper); - await assureLazyComponent(driverWrapper); + await goBack(driver); + await assureLazyComponent(driver); }); it("should navigate to First again", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await findAndClick(driver, "GO TO FIRST"); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureLazyComponent(driverWrapper); + await goBack(driver); + await assureLazyComponent(driver); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); describe("Navigate to componentless lazy module route", () => { let driver: AppiumDriver; - let driverWrapper: DriverWrapper; before(async () => { driver = await createDriver(); - driverWrapper = new DriverWrapper(driver); - }); - - after(async () => { - await driver.quit(); - console.log("Driver quits!"); + await driver.resetApp(); }); it("should find First", async () => { - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate to nest/more (componentless lazy route)", async () => { - await findAndClick(driverWrapper, "GO TO C-LESS LAZY"); + await findAndClick(driver, "GO TO C-LESS LAZY"); - await assureComponentlessLazyComponent(driverWrapper); + await assureComponentlessLazyComponent(driver); }); it("should navigate to lazy/home", async () => { - await findAndClick(driverWrapper, "GO TO LAZY HOME"); + await findAndClick(driver, "GO TO LAZY HOME"); - await assureLazyComponent(driverWrapper); + await assureLazyComponent(driver); }); - + it("should navigate to First", async () => { - await findAndClick(driverWrapper, "GO TO FIRST"); + await findAndClick(driver, "GO TO FIRST"); - await assureFirstComponent(driverWrapper); + await assureFirstComponent(driver); }); it("should navigate the whole stack", async () => { - await goBack(driverWrapper); - await assureLazyComponent(driverWrapper); + await goBack(driver); + await assureLazyComponent(driver); - await goBack(driverWrapper); - await assureComponentlessLazyComponent(driverWrapper); + await goBack(driver); + await assureComponentlessLazyComponent(driver); - await goBack(driverWrapper); - await assureFirstComponent(driverWrapper); + await goBack(driver); + await assureFirstComponent(driver); }); }); -async function assureFirstComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("FirstComponent", SearchOptions.exact); +async function assureFirstComponent(driver: AppiumDriver) { + await driver.findElementByText("FirstComponent", SearchOptions.exact); } -async function assureLazyComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("LazyComponent", SearchOptions.exact); +async function assureLazyComponent(driver: AppiumDriver) { + await driver.findElementByText("LazyComponent", SearchOptions.exact); } -async function assureComponentlessLazyComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("Lazy Componentless Route", SearchOptions.exact); +async function assureComponentlessLazyComponent(driver: AppiumDriver) { + await driver.findElementByText("Lazy Componentless Route", SearchOptions.exact); } -async function assureSecondComponent(driverWrapper: DriverWrapper, param: number) { - await driverWrapper.findElementByText("SecondComponent", SearchOptions.exact); - await driverWrapper.findElementByText(`param: ${param}`, SearchOptions.exact); +async function assureSecondComponent(driver: AppiumDriver, param: number) { + await driver.findElementByText("SecondComponent", SearchOptions.exact); + await driver.findElementByText(`param: ${param}`, SearchOptions.exact); } -async function assureNestedMasterComponent(driverWrapper: DriverWrapper) { - await driverWrapper.findElementByText("NestedMaster", SearchOptions.exact); +async function assureNestedMasterComponent(driver: AppiumDriver) { + await driver.findElementByText("NestedMaster", SearchOptions.exact); } -async function assureNestedDetailComponent(driverWrapper: DriverWrapper, param: number) { - await driverWrapper.findElementByText("NestedDetail", SearchOptions.exact); - await driverWrapper.findElementByText(`nested-param: ${param}`, SearchOptions.exact); +async function assureNestedDetailComponent(driver: AppiumDriver, param: number) { + await driver.findElementByText("NestedDetail", SearchOptions.exact); + await driver.findElementByText(`nested-param: ${param}`, SearchOptions.exact); } -async function goBack(driverWrapper: DriverWrapper) { - const backButton = await driverWrapper.findElementByText("BACK", SearchOptions.exact); +async function goBack(driver: AppiumDriver) { + const backButton = await driver.findElementByText("BACK", SearchOptions.exact); await backButton.click(); + //await driver.navBack(); } -async function findAndClick(driverWrapper: DriverWrapper, text: string) { +async function findAndClick(driver: AppiumDriver, text: string) { const navigationButton = - await driverWrapper.findElementByText(text, SearchOptions.exact); + await driver.findElementByText(text, SearchOptions.exact); navigationButton.click(); } \ No newline at end of file diff --git a/e2e/router/e2e/setup.ts b/e2e/router/e2e/setup.ts index 8b26e66e9..0c0add14a 100644 --- a/e2e/router/e2e/setup.ts +++ b/e2e/router/e2e/setup.ts @@ -1,9 +1,19 @@ -import { startServer, stopServer } from "nativescript-dev-appium"; +import { startServer, stopServer , createDriver , AppiumDriver } from "nativescript-dev-appium"; + +let driver: AppiumDriver; before("start server", async () => { await startServer(); + driver = await createDriver(); }); after("stop server", async () => { + await driver.quit(); await stopServer(); }); + +afterEach(async function () { + if (this.currentTest.state === "failed") { + await driver.logScreenshot(this.currentTest.title); + } +}); \ No newline at end of file diff --git a/e2e/router/package.json b/e2e/router/package.json index 922c26304..e99f48117 100644 --- a/e2e/router/package.json +++ b/e2e/router/package.json @@ -13,14 +13,14 @@ } }, "dependencies": { - "@angular/animations": "~4.2.0", - "@angular/common": "~4.2.0", - "@angular/compiler": "~4.2.0", - "@angular/core": "~4.2.0", - "@angular/forms": "~4.2.0", - "@angular/http": "~4.2.0", - "@angular/platform-browser": "~4.2.0", - "@angular/router": "~4.2.0", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "file:../../nativescript-angular", "nativescript-intl": "^3.0.0", "reflect-metadata": "~0.1.8", @@ -42,12 +42,13 @@ "mocha": "~3.5.0", "mocha-junit-reporter": "^1.13.0", "mocha-multi": "^0.11.0", - "nativescript-dev-appium": "2.1.0-2017-8-17", + "nativescript-dev-appium": "next", "nativescript-dev-typescript": "~0.4.0", "tslib": "^1.7.1", "typescript": "~2.2.1" }, "scripts": { - "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts" + "e2e": "tsc -p e2e && mocha --opts ./e2e/config/mocha.opts", + "compile-tests-w": "tsc -p e2e --watch" } } diff --git a/nativescript-angular/http-client/http-client.module.ts b/nativescript-angular/http-client/http-client.module.ts new file mode 100644 index 000000000..ea6623d5b --- /dev/null +++ b/nativescript-angular/http-client/http-client.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from "@angular/core"; + +// IMPORTant: Importing "@angular/common/http" for the first time overwrites the +// global.__extends function. +const cachedExtends = global.__extends; +import { HttpClientModule, HttpBackend } from "@angular/common/http"; +global.__extends = cachedExtends; + +import { NSFileSystem } from "../file-system/ns-file-system"; +import { NsHttpBackEnd } from "./ns-http-backend"; + +@NgModule({ + providers: [ + NSFileSystem, + NsHttpBackEnd, + { provide: HttpBackend, useExisting: NsHttpBackEnd }, + ], + imports: [ + HttpClientModule, + ], + exports: [ + HttpClientModule, + ] +}) +export class NativeScriptHttpClientModule { +} diff --git a/nativescript-angular/http-client/http-utils.ts b/nativescript-angular/http-client/http-utils.ts new file mode 100644 index 000000000..ae0ecc6f8 --- /dev/null +++ b/nativescript-angular/http-client/http-utils.ts @@ -0,0 +1,52 @@ +import { NSFileSystem } from "../file-system/ns-file-system"; + +import { Observable } from "rxjs/Observable"; +import { Observer } from "rxjs/Observer"; +import { path } from "tns-core-modules/file-system/file-system"; + +export type httpResponseFactory = (url: string, body: any, status: number) => T; +export type httpErrorFactory = (url: string, body: any, status: number) => any; + +export function isLocalRequest(url: string): boolean { + return url.indexOf("~") === 0 || url.indexOf("/") === 0; +} + +export function getAbsolutePath(url: string, nsFileSystem: NSFileSystem): string { + url = url.replace("~", "").replace("/", ""); + url = path.join(nsFileSystem.currentApp().path, url); + return url; +} + +export function processLocalFileRequest( + url: string, + nsFileSystem: NSFileSystem, + successResponse: httpResponseFactory, + errorResponse: httpErrorFactory): Observable { + + url = getAbsolutePath(url, nsFileSystem); + + // request from local app resources + return new Observable((observer: Observer) => { + if (nsFileSystem.fileExists(url)) { + const localFile = nsFileSystem.fileFromPath(url); + localFile.readText() + .then((data) => { + try { + const json = JSON.parse(data); + observer.next(successResponse(url, json, 200)); + observer.complete(); + } catch (error) { + // Even though the response status was 2xx, this is still an error. + // The parse error contains the text of the body that failed to parse. + const errorResult = { error, text: data }; + observer.error(errorResponse(url, errorResult, 200)); + } + }, (err: Object) => { + observer.error(errorResponse(url, err, 400)); + + }); + } else { + observer.error(errorResponse(url, "Not Found", 404)); + } + }); +} diff --git a/nativescript-angular/http-client/index.ts b/nativescript-angular/http-client/index.ts new file mode 100644 index 000000000..da2e04db0 --- /dev/null +++ b/nativescript-angular/http-client/index.ts @@ -0,0 +1,2 @@ +export * from "./http-client.module"; +export * from "./ns-http-backend"; diff --git a/nativescript-angular/http-client/ns-http-backend.ts b/nativescript-angular/http-client/ns-http-backend.ts new file mode 100644 index 000000000..6cf2f2bf4 --- /dev/null +++ b/nativescript-angular/http-client/ns-http-backend.ts @@ -0,0 +1,62 @@ +import { Injectable } from "@angular/core"; +import { + HttpRequest, HttpEvent, + XhrFactory, HttpResponse, + HttpErrorResponse, HttpXhrBackend +} from "@angular/common/http"; +import { Observable } from "rxjs/Observable"; + +import { NSFileSystem } from "../file-system/ns-file-system"; +import { isLocalRequest, processLocalFileRequest } from "./http-utils"; + +@Injectable() +export class NsHttpBackEnd extends HttpXhrBackend { + constructor(xhrFactory: XhrFactory, private nsFileSystem: NSFileSystem) { + super(xhrFactory); + } + + handle(req: HttpRequest): Observable> { + let result: Observable>; + + if (isLocalRequest(req.url)) { + result = this.handleLocalFileRequest(req.url); + } else { + result = super.handle(req); + } + + return result; + } + + private handleLocalFileRequest(url: string): Observable> { + return processLocalFileRequest( + url, + this.nsFileSystem, + createSuccessResponse, + createErrorResponse + ); + } +} + +function createSuccessResponse( + url: string, + body: any, + status: number): HttpEvent { + return new HttpResponse({ + url, + body, + status, + statusText: "OK" + }); +} + +function createErrorResponse( + url: string, + body: any, + status: number): HttpErrorResponse { + return new HttpErrorResponse({ + url, + error: body, + status, + statusText: "ERROR" + }); +} diff --git a/nativescript-angular/http.ts b/nativescript-angular/http.ts deleted file mode 100644 index 5f6583a02..000000000 --- a/nativescript-angular/http.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - Http, XHRBackend, RequestOptions -} from "@angular/http"; -import { NSXSRFStrategy, NSHttp } from "./http/ns-http"; -import { NSFileSystem } from "./file-system/ns-file-system"; - -import { NgModule } from "@angular/core"; -import { HttpModule, XSRFStrategy } from "@angular/http"; - -export { NSHttp } from "./http/ns-http"; - -export function nsHttpFactory(backend, options, nsFileSystem) { - return new NSHttp(backend, options, nsFileSystem); -} - -export function nsXSRFStrategyFactory() { - return new NSXSRFStrategy(); -} - -@NgModule({ - providers: [ - {provide: XSRFStrategy, useFactory: nsXSRFStrategyFactory}, - NSFileSystem, - {provide: Http, useFactory: nsHttpFactory, - deps: [XHRBackend, RequestOptions, NSFileSystem]} - ], - imports: [ - HttpModule, - ], - exports: [ - HttpModule, - ] -}) -export class NativeScriptHttpModule { -} diff --git a/nativescript-angular/http/http.module.ts b/nativescript-angular/http/http.module.ts new file mode 100644 index 000000000..b6d35172c --- /dev/null +++ b/nativescript-angular/http/http.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from "@angular/core"; +import { Http, XHRBackend, RequestOptions, HttpModule, XSRFStrategy } from "@angular/http"; + +import { NSXSRFStrategy, NSHttp } from "./ns-http"; +import { NSFileSystem } from "../file-system/ns-file-system"; + +export { NSHttp } from "./ns-http"; + +export function nsHttpFactory(backend, options, nsFileSystem) { + return new NSHttp(backend, options, nsFileSystem); +} + +export function nsXSRFStrategyFactory() { + return new NSXSRFStrategy(); +} + +@NgModule({ + providers: [ + { provide: XSRFStrategy, useFactory: nsXSRFStrategyFactory }, + NSFileSystem, + { + provide: Http, useFactory: nsHttpFactory, + deps: [XHRBackend, RequestOptions, NSFileSystem] + } + ], + imports: [ + HttpModule, + ], + exports: [ + HttpModule, + ] +}) +export class NativeScriptHttpModule { +} diff --git a/nativescript-angular/http/index.ts b/nativescript-angular/http/index.ts new file mode 100644 index 000000000..720657322 --- /dev/null +++ b/nativescript-angular/http/index.ts @@ -0,0 +1 @@ +export * from "./http.module"; diff --git a/nativescript-angular/http/ns-http.ts b/nativescript-angular/http/ns-http.ts index d8d6453b0..e0f04b661 100644 --- a/nativescript-angular/http/ns-http.ts +++ b/nativescript-angular/http/ns-http.ts @@ -11,81 +11,62 @@ import { } from "@angular/http"; import { Observable } from "rxjs/Observable"; import "rxjs/add/observable/fromPromise"; + +import { isLocalRequest, processLocalFileRequest } from "../http-client/http-utils"; + import { NSFileSystem } from "../file-system/ns-file-system"; export class NSXSRFStrategy { - public configureRequest(_req: any) { - // noop - } + public configureRequest(_req: any) { + // noop + } } @Injectable() export class NSHttp extends Http { - constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private nsFileSystem: NSFileSystem) { - super(backend, defaultOptions); - } - - /** - * Performs a request with `request` http method. - */ - request(req: string | Request, options?: RequestOptionsArgs): Observable { - const urlString = typeof req === "string" ? req : req.url; - if (isLocalRequest(urlString)) { - return this._requestLocalUrl(urlString); - } else { - return super.request(req, options); + constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private nsFileSystem: NSFileSystem) { + super(backend, defaultOptions); } - } - /** - * Performs a request with `get` http method. - */ - get(url: string, options?: RequestOptionsArgs): Observable { - if (isLocalRequest(url)) { - return this._requestLocalUrl(url); - } else { - return super.get(url, options); + /** + * Performs a request with `request` http method. + */ + request(req: string | Request, options?: RequestOptionsArgs): Observable { + const urlString = typeof req === "string" ? req : req.url; + if (isLocalRequest(urlString)) { + return this.requestLocalFile(urlString); + } else { + return super.request(req, options); + } } - } - /** - * Uses a local file if `~/` resource is requested. - * @param url - */ - private _requestLocalUrl(url: string): Observable { - // normalize url - url = normalizeLocalUrl(url); - // request from local app resources - return Observable.fromPromise(new Promise((resolve, reject) => { - let app = this.nsFileSystem.currentApp(); - let localFile = app.getFile(url); - if (localFile) { - localFile.readText().then((data) => { - resolve(responseOptions(data, 200, url)); - }, (err: Object) => { - reject(responseOptions(err, 400, url)); - }); - } else { - reject(responseOptions("Not Found", 404, url)); - } - })); - } -} - -function isLocalRequest(url: string): boolean { - return url.indexOf("~") === 0 || url.indexOf("/") === 0; -} + /** + * Performs a request with `get` http method. + */ + get(url: string, options?: RequestOptionsArgs): Observable { + if (isLocalRequest(url)) { + return this.requestLocalFile(url); + } else { + return super.get(url, options); + } + } -function normalizeLocalUrl(url: string): string { - return url.replace("~", "").replace("/", ""); + private requestLocalFile(url: string): Observable { + return processLocalFileRequest( + url, + this.nsFileSystem, + createResponse, + createResponse + ); + } } -function responseOptions(body: string | Object, status: number, url: string): Response { - return new Response(new ResponseOptions({ - body: body, - status: status, - statusText: "OK", - type: status === 200 ? ResponseType.Default : ResponseType.Error, - url: url - })); +function createResponse(url: string, body: string | Object, status: number): Response { + return new Response(new ResponseOptions({ + body: body, + status: status, + statusText: "OK", + type: status === 200 ? ResponseType.Default : ResponseType.Error, + url: url + })); } diff --git a/nativescript-angular/nativescript.module.ts b/nativescript-angular/nativescript.module.ts index e5b34007e..305890ab4 100644 --- a/nativescript-angular/nativescript.module.ts +++ b/nativescript-angular/nativescript.module.ts @@ -21,7 +21,7 @@ import { NativeScriptRendererFactory } from "./renderer"; import { DetachedLoader } from "./common/detached-loader"; export function errorHandlerFactory() { - return new ErrorHandler(true); + return new ErrorHandler(); } @NgModule({ diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index 4165ad184..c56aacd12 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -43,32 +43,32 @@ "reflect-metadata": "^0.1.8" }, "peerDependencies": { - "@angular/common": "~4.2.5", - "@angular/compiler": "~4.2.5", - "@angular/core": "~4.2.5", - "@angular/forms": "~4.2.5", - "@angular/http": "~4.2.5", - "@angular/platform-browser": "~4.2.5", - "@angular/router": "~4.2.5", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "rxjs": "^5.0.1", "tns-core-modules": "^3.1.0 || >3.3.0-", "zone.js": "^0.8.4" }, "devDependencies": { - "@angular/animations": "~4.2.5", - "@angular/common": "~4.2.5", - "@angular/compiler": "~4.2.5", - "@angular/compiler-cli": "~4.2.5", - "@angular/core": "~4.2.5", - "@angular/forms": "~4.2.5", - "@angular/http": "~4.2.5", - "@angular/platform-browser": "~4.2.5", - "@angular/router": "~4.2.5", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/compiler-cli": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/router": "~4.4.1", "codelyzer": "^3.1.2", "rxjs": "^5.4.2", "tns-core-modules": "next", "tslint": "^5.5.0", - "typescript": "^2.4.1", + "typescript": "^2.5.1", "zone.js": "^0.8.12" } } diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts index 374d9276c..a5fbb67e1 100644 --- a/nativescript-angular/platform-common.ts +++ b/nativescript-angular/platform-common.ts @@ -18,18 +18,17 @@ import { EventEmitter, Provider, Sanitizer, - OpaqueToken + InjectionToken } from "@angular/core"; - -// Work around a TS bug requiring an import of OpaqueToken without using it -if ((global).___TS_UNUSED) { - (() => { - return OpaqueToken; - })(); -} +import { DOCUMENT } from "@angular/common"; import { rendererLog, rendererError } from "./trace"; -import { PAGE_FACTORY, PageFactory, defaultPageFactoryProvider, setRootPage } from "./platform-providers"; +import { + PAGE_FACTORY, + PageFactory, + defaultPageFactoryProvider, + setRootPage +} from "./platform-providers"; import { start, setCssFileName } from "tns-core-modules/application"; import { topmost, NavigationEntry } from "tns-core-modules/ui/frame"; @@ -43,6 +42,12 @@ export const onAfterLivesync = new EventEmitter>(); let lastBootstrappedModule: WeakRef>; type BootstrapperAction = () => Promise>; +// Work around a TS bug requiring an import of OpaqueToken without using it +if ((global).___TS_UNUSED) { + (() => { + return InjectionToken; + })(); +} export interface AppOptions { bootInExistingPage?: boolean; cssFile?: string; @@ -57,9 +62,16 @@ export class NativeScriptSanitizer extends Sanitizer { } } +export class NativeScriptDocument { + createElement(tag: string) { + throw new Error("NativeScriptDocument is not DOM Document. There is no createElement() method."); + } +} + export const COMMON_PROVIDERS = [ defaultPageFactoryProvider, { provide: Sanitizer, useClass: NativeScriptSanitizer }, + { provide: DOCUMENT, useClass: NativeScriptDocument }, ]; export class NativeScriptPlatformRef extends PlatformRef { @@ -82,7 +94,7 @@ export class NativeScriptPlatformRef extends PlatformRef { bootstrapModule( moduleType: Type, compilerOptions: CompilerOptions | CompilerOptions[] = [] - ): Promise> { + ): Promise> { this._bootstrapper = () => this.platform.bootstrapModule(moduleType, compilerOptions); this.bootstrapApp(); diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts index 466fa54c9..86d47e0a2 100644 --- a/nativescript-angular/platform-providers.ts +++ b/nativescript-angular/platform-providers.ts @@ -1,19 +1,13 @@ +import { InjectionToken } from "@angular/core"; + import { topmost, Frame } from "tns-core-modules/ui/frame"; +import { View } from "tns-core-modules/ui/core/view"; import { Page } from "tns-core-modules/ui/page"; -import { OpaqueToken } from "@angular/core"; import { device, Device } from "tns-core-modules/platform"; -import * as platform from "tns-core-modules/platform"; - -export const APP_ROOT_VIEW = new OpaqueToken("App Root View"); -export const DEVICE = new OpaqueToken("platfrom device"); -export const PAGE_FACTORY = new OpaqueToken("page factory"); -// Work around a TS bug requiring an import of platform.Device without using it -if ((global).___TS_UNUSED) { - (() => { - return platform; - })(); -} +export const APP_ROOT_VIEW = new InjectionToken("App Root View"); +export const DEVICE = new InjectionToken("platform device"); +export const PAGE_FACTORY = new InjectionToken("page factory"); let _rootPageRef: WeakRef; diff --git a/ng-sample/app/app.ts b/ng-sample/app/app.ts index 581f039fe..5e860f71c 100644 --- a/ng-sample/app/app.ts +++ b/ng-sample/app/app.ts @@ -3,10 +3,13 @@ import { platformNativeScriptDynamic } from "nativescript-angular/platform"; import { NativeScriptAnimationsModule } from "nativescript-angular/animations"; import { onAfterLivesync, onBeforeLivesync } from "nativescript-angular/platform-common"; import { NgModule } from "@angular/core"; +import { DOCUMENT } from '@angular/common'; import { Router } from "@angular/router"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; import { NativeScriptHttpModule } from "nativescript-angular/http"; +import { NativeScriptHttpClientModule } from "nativescript-angular/http-client"; + import { rendererTraceCategory, routerTraceCategory, @@ -34,6 +37,7 @@ import { ListTemplateSelectorTest } from "./examples/list/template-selector"; import { ListTestAsync, ListTestFilterAsync } from "./examples/list/list-test-async"; import { ImageTest } from "./examples/image/image-test"; import { HttpTest } from "./examples/http/http-test"; +import { HttpClientTest } from "./examples/http-client/http-client-test"; import { ActionBarTest } from "./examples/action-bar/action-bar-test"; import { ModalTest } from "./examples/modal/modal-test"; import { PlatfromDirectivesTest } from "./examples/platform-directives/platform-directives-test"; @@ -53,6 +57,7 @@ import { AnimationNgClassTest } from "./examples/animation/animation-ngclass-tes import { AnimationStatesTest } from "./examples/animation/animation-states-test"; import { AnimationStatesMultiTest } from "./examples/animation/animation-states-multi-test"; + @NgModule({ declarations: [ ], @@ -60,12 +65,14 @@ import { AnimationStatesMultiTest } from "./examples/animation/animation-states- NativeScriptModule, NativeScriptFormsModule, NativeScriptHttpModule, + NativeScriptHttpClientModule, NativeScriptRouterModule, ], exports: [ NativeScriptModule, NativeScriptFormsModule, NativeScriptHttpModule, + NativeScriptHttpClientModule, NativeScriptRouterModule, ], providers: [] @@ -130,6 +137,7 @@ const customPageFactoryProvider = { // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ImageTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ModalTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpTest)); +platformNativeScriptDynamic().bootstrapModule(makeExampleModule(HttpClientTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PlatfromDirectivesTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ActionBarTest)); @@ -137,7 +145,7 @@ const customPageFactoryProvider = { // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RouterOutletAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(PageRouterOutletNestedAppComponent)); -platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ClearHistoryAppComponent)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(ClearHistoryAppComponent)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(LoginAppComponent)); // animations diff --git a/ng-sample/app/examples/http-client/data.json b/ng-sample/app/examples/http-client/data.json new file mode 100644 index 000000000..52d4169d4 --- /dev/null +++ b/ng-sample/app/examples/http-client/data.json @@ -0,0 +1,8 @@ +{ + "results": [ + { + "title": "Test", + "description": "Testing Http local and remote." + } + ] +} diff --git a/ng-sample/app/examples/http-client/http-client-test.ts b/ng-sample/app/examples/http-client/http-client-test.ts new file mode 100644 index 000000000..7af6fb9d6 --- /dev/null +++ b/ng-sample/app/examples/http-client/http-client-test.ts @@ -0,0 +1,122 @@ +import { Component, Inject, Injectable } from "@angular/core"; +import { + HttpClient, HTTP_INTERCEPTORS, HttpEventType, HttpErrorResponse, + HttpEvent, HttpInterceptor, HttpHandler, HttpRequest +} from "@angular/common/http"; +import { Observable } from "rxjs/Observable"; +import "rxjs/add/operator/do"; + +@Injectable() +export class CustomInterceptor implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + console.log(`[CustomInterceptor] intercept url: ${req.url}`); + + return next.handle(req) + .do(event => { + console.log(`[CustomInterceptor] handled type: ${HttpEventType[event.type]} url: ${req.url}`); + }); + } +} + +interface DataResults { + results: Array; +} + +interface LocalData { + title: string; + description: string; +} + +interface RemoteData { + name: { first: string }; + email: string; +} + +@Component({ + selector: "http-client-test", + template: ` + + + + + + + + + + + + `, + styles: [` + #title { margin-top:20; } + Label { margin: 5 20; } + `], +}) +export class HttpClientTest { + static providers = [ + { + provide: HTTP_INTERCEPTORS, + useClass: CustomInterceptor, + multi: true, + } + ]; + + public title: string; + public description: string; + public error: string; + + constructor(private http: HttpClient) { + } + + public loadLocal() { + this.http.get>("~/examples/http/data.json") + .subscribe((response) => { + let user = response.results[0]; + this.onSuccess(user.title, user.description); + }, (error) => { + this.onError(error); + }); + } + + public loadRemote() { + this.http.get>(`https://randomuser.me/api/?results=1&nat=us`) + .subscribe((response) => { + const user = response.results[0]; + this.onSuccess(user.email, user.name.first); + }, (error) => { + this.onError(error); + }); + } + + public loadNonexistentLocal() { + this.http.get>("~/non/existent/app/folder/data.json") + .subscribe((response) => { + this.onSuccess("strange?!", ""); + }, (error) => { + this.onError(error); + }); + } + + public loadNonexistentRemote() { + this.http.get>("https://google.com/non/existent/url/data.json") + .subscribe((response) => { + this.onSuccess("strange?!", ""); + }, (error) => { + this.onError(error); + }); + } + + private onSuccess(title: string, description: string) { + this.title = title; + this.description = description; + this.error = ""; + } + + private onError(error: HttpErrorResponse) { + console.log("onError " + error); + console.dir(error); + this.title = ""; + this.description = ""; + this.error = error.message; + } +} diff --git a/ng-sample/package.json b/ng-sample/package.json index 1d07d71b5..6009d22fd 100644 --- a/ng-sample/package.json +++ b/ng-sample/package.json @@ -32,15 +32,15 @@ }, "homepage": "https://github.com/NativeScript/template-hello-world", "dependencies": { - "@angular/animations": "~4.2.4", - "@angular/common": "~4.2.4", - "@angular/compiler": "~4.2.4", - "@angular/core": "~4.2.4", - "@angular/forms": "~4.2.4", - "@angular/http": "~4.2.4", - "@angular/platform-browser": "~4.2.4", - "@angular/platform-browser-dynamic": "~4.2.4", - "@angular/router": "~4.2.4", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/platform-browser-dynamic": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "file:../nativescript-angular", "rxjs": "^5.3.0", "tns-core-modules": "next", diff --git a/tests/app/tests/http-client-ns-backend.ts b/tests/app/tests/http-client-ns-backend.ts new file mode 100644 index 000000000..e5baf8d2c --- /dev/null +++ b/tests/app/tests/http-client-ns-backend.ts @@ -0,0 +1,80 @@ +// make sure you import mocha-config before @angular/core +import { assert } from "./test-config"; +import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system"; +import { NsHttpBackEnd } from "nativescript-angular/http-client"; + +import { XhrFactory, HttpRequest, HttpResponse, HttpErrorResponse } from "@angular/common/http"; +import { File } from "tns-core-modules/file-system"; + +class NSFileSystemMock implements NSFileSystem { + public currentApp(): any { + return { path: "/app/dir" }; + } + + public fileFromPath(path: string): any { + if (path === "/app/dir/data.json") { + return { + readText: () => { return Promise.resolve(` { "result": "success" } `); } + }; + } + throw new Error("Opening non-existing file"); + } + + public fileExists(path: string): boolean { + return path === "/app/dir/data.json"; + } +} +class XhrFactoryMock implements XhrFactory { + build(): XMLHttpRequest { + throw new Error("Hi, from XhrFactoryMock!"); + } +} + +describe("NsHttpBackEnd ", () => { + let backend: NsHttpBackEnd; + + before(() => { + backend = new NsHttpBackEnd(new XhrFactoryMock(), new NSFileSystemMock()); + }); + + it("should work with local files prefixed with '~'", (done) => { + const req = new HttpRequest("GET", "~/data.json"); + let nextCalled = false; + backend.handle(req).subscribe( + (response: HttpResponse<{ result: string }>) => { + assert.equal(response.body.result, "success"); + nextCalled = true; + }, (error) => { + done(error); + }, () => { + assert.isTrue(nextCalled, "next callback should be called with result."); + done(); + }); + }); + + it("should return 404 for non-existing local files prefixed with '~'", (done) => { + const req = new HttpRequest("GET", "~/non/existing/file.json"); + backend.handle(req).subscribe( + (response) => { + assert.fail("next callback should not be called for non existing file."); + }, (error: HttpErrorResponse) => { + assert.equal(error.status, 404); + done(); + }, () => { + assert.fail("next callback should not be called for non existing file."); + }); + }); + + it("should fallback to XHR backend when requesting remote files", (done) => { + const req = new HttpRequest("GET", "https://nativescript.org/"); + backend.handle(req).subscribe( + (response) => { + assert.fail("next callback should not be called for non existing file."); + }, (error: Error) => { + assert.equal(error.message, "Hi, from XhrFactoryMock!"); + done(); + }, () => { + assert.fail("next callback should not be called for non existing file."); + }); + }); +}); diff --git a/tests/app/tests/http.ts b/tests/app/tests/http.ts index e3dd55794..e02af2ad0 100644 --- a/tests/app/tests/http.ts +++ b/tests/app/tests/http.ts @@ -1,16 +1,39 @@ // make sure you import mocha-config before @angular/core -import {assert} from "./test-config"; +import { assert } from "./test-config"; import { async, inject, } from "@angular/core/testing"; -import {ReflectiveInjector} from "@angular/core"; -import {Request, BaseRequestOptions, ConnectionBackend, Http, Response, ResponseOptions} from "@angular/http"; +import { ReflectiveInjector, Injectable } from "@angular/core"; +import { Request, BaseRequestOptions, ConnectionBackend, Http, Response, ResponseOptions } from "@angular/http"; import "rxjs/add/operator/map"; -import {MockBackend} from "@angular/http/testing"; -import {NSHttp} from "nativescript-angular/http/ns-http"; -import {NSFileSystem} from "nativescript-angular/file-system/ns-file-system"; -import {NSFileSystemMock, FileResponses} from "./mocks/ns-file-system.mock"; +import { MockBackend } from "@angular/http/testing"; +import { NSHttp } from "nativescript-angular/http/ns-http"; +import { NSFileSystem } from "nativescript-angular/file-system/ns-file-system"; + +const AWESOME_TEAM: string = '[{"name":"Alex"}, {"name":"Rosen"}, {"name":"Panayot"}]'; + +// Filesystem mock +@Injectable() +export class NSFileSystemMock implements NSFileSystem { + public currentApp(): any { + return { path: "/app/dir" }; + } + + public fileFromPath(path: string): any { + if (path === "/app/dir/test.json") { + return { + readText: () => { return Promise.resolve(AWESOME_TEAM); } + }; + } + throw new Error("Opening non-existing file"); + } + + public fileExists(path: string): boolean { + return path === "/app/dir/test.json"; + } + +} describe("Http", () => { let http: Http; @@ -22,15 +45,15 @@ describe("Http", () => { MockBackend, { provide: NSFileSystem, useClass: NSFileSystemMock }, { - provide: Http, - useFactory: function ( - connectionBackend: ConnectionBackend, - defaultOptions: BaseRequestOptions, - nsFileSystem: NSFileSystem) { + provide: Http, + useFactory: function ( + connectionBackend: ConnectionBackend, + defaultOptions: BaseRequestOptions, + nsFileSystem: NSFileSystem) { // HACK: cast backend to any to work around an angular typings problem return new NSHttp(connectionBackend, defaultOptions, nsFileSystem); - }, - deps: [MockBackend, BaseRequestOptions, NSFileSystem] + }, + deps: [MockBackend, BaseRequestOptions, NSFileSystem] } ]); @@ -78,6 +101,6 @@ describe("Http", () => { assert.strictEqual(3, response.length); assert.strictEqual("Rosen", response[1].name); }); - connection.mockRespond(new Response(new ResponseOptions({ body: FileResponses.AWESOME_TEAM }))); + connection.mockRespond(new Response(new ResponseOptions({ body: AWESOME_TEAM }))); }); }); diff --git a/tests/app/tests/mocks/ns-file-system.mock.ts b/tests/app/tests/mocks/ns-file-system.mock.ts deleted file mode 100644 index 0f841aff5..000000000 --- a/tests/app/tests/mocks/ns-file-system.mock.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Injectable} from "@angular/core"; -import {ResponseType, Response, ResponseOptions} from "@angular/http"; - -export class FileResponses { - public static AWESOME_TEAM: string = '[{"name":"Alex"}, {"name":"Rosen"}, {"name":"Panayot"}]'; -} - -// Folder mock -class Folder { - public getFile(url: string): any { - let data; - switch (url) { - case "test.json": - data = FileResponses.AWESOME_TEAM; - break; - default: - throw (new Error("Unsupported file for the testing mock - ns-file-system-mock")); - } - return { - readText: () => { - return new Promise((resolve) => { - resolve(data); - }); - } - }; - } -} - -// Filesystem mock -@Injectable() -export class NSFileSystemMock { - public currentApp(): Folder { - return new Folder(); - } -} - diff --git a/tests/package.json b/tests/package.json index 89db53ad8..9cfb0bb66 100644 --- a/tests/package.json +++ b/tests/package.json @@ -26,15 +26,15 @@ ], "homepage": "http://nativescript.org", "dependencies": { - "@angular/animations": "~4.2.4", - "@angular/common": "~4.2.4", - "@angular/compiler": "~4.2.4", - "@angular/core": "~4.2.4", - "@angular/forms": "~4.2.4", - "@angular/http": "~4.2.4", - "@angular/platform-browser": "~4.2.4", - "@angular/platform-browser-dynamic": "~4.2.4", - "@angular/router": "~4.2.4", + "@angular/animations": "~4.4.1", + "@angular/common": "~4.4.1", + "@angular/compiler": "~4.4.1", + "@angular/core": "~4.4.1", + "@angular/forms": "~4.4.1", + "@angular/http": "~4.4.1", + "@angular/platform-browser": "~4.4.1", + "@angular/platform-browser-dynamic": "~4.4.1", + "@angular/router": "~4.4.1", "nativescript-angular": "../nativescript-angular", "nativescript-unit-test-runner": "^0.3.4", "rxjs": "^5.2.0",